From 9daddf48724b8c2de6b7349da8c9d3636579a1ad Mon Sep 17 00:00:00 2001 From: "Ido S." Date: Thu, 22 May 2025 23:51:15 +0300 Subject: [PATCH 01/33] feat: v4 - Global CLI, you do not need to wrap active downloads with downloadSequence - Auto increase parallel stream, maximize download speed - Not reusing redirected URL by default - prevent token expires for long downloads - Performance & stability improvements - Remote CLI progress, show download progress in another process easily BREAKING CHANGE: - `partsURL` removed in favor of `partURLs` - Not reusing redirected URL by default - Different chunk size based on programType - You can recall `.download()` and it will not throw an error - will return the same promise of downloading (do *not* redownload the file) - You can change the parallel stream count after download has started --- .github/workflows/build.yml | 18 +- .releaserc.json | 6 +- README.md | 42 ++- examples/browser-log.ts | 7 +- package-lock.json | 68 +++-- package.json | 3 +- src/browser.ts | 19 +- src/download/browser-download.ts | 39 ++- .../download-file/download-engine-file.ts | 195 ++++++++---- .../base-download-program.ts | 98 ++++-- .../download-program-stream.ts | 3 +- .../download-file/downloaderProgramManager.ts | 106 +++++++ .../download-file/progress-status-file.ts | 17 +- .../engine/DownloadEngineRemote.ts | 73 +++++ .../engine/base-download-engine.ts | 64 ++-- .../engine/download-engine-multi-download.ts | 279 +++++++++++++----- .../engine/download-engine-nodejs.ts | 20 +- .../no-download-engine-provided-error.ts | 7 - .../engine/utils/concurrency.ts | 24 -- .../base-download-engine-fetch-stream.ts | 63 +++- .../download-engine-fetch-stream-fetch.ts | 84 +++++- .../download-engine-fetch-stream-xhr.ts | 56 +++- .../errors/EmptyStreamTimeoutError.ts | 3 + .../utils/smart-chunk-split.ts | 53 +++- .../utils/stream-response.ts | 2 +- .../base-download-engine-write-stream.ts | 2 +- .../download-engine-write-stream-browser.ts | 22 +- .../download-engine-write-stream-nodejs.ts | 92 ++++-- .../utils/BytesWriteDebounce.ts | 96 ++++++ src/download/download-engine/types.ts | 1 + .../download-engine/utils/concurrency.ts | 30 ++ .../utils/promiseWithResolvers.ts | 9 + src/download/node-download.ts | 43 ++- .../progress-statistics-builder.ts | 119 ++++++-- .../transfer-cli/GlobalCLI.ts | 162 ++++++++++ .../transfer-cli/cli-animation-wrapper.ts | 89 ------ .../base-loading-animation.ts | 74 ----- .../cli-spinners-loading-animation.ts | 23 -- .../multiProgressBars/BaseMultiProgressBar.ts | 17 +- .../SummaryMultiProgressBar.ts | 8 +- .../base-transfer-cli-progress-bar.ts | 17 ++ .../fancy-transfer-cli-progress-bar.ts | 91 +++--- .../transfer-cli/transfer-cli.ts | 44 +-- src/index.ts | 16 +- test/browser.test.ts | 36 ++- test/copy-file.test.ts | 4 +- test/daynamic-content-length.test.ts | 18 +- test/download.test.ts | 3 +- tsconfig.json | 6 +- 49 files changed, 1671 insertions(+), 700 deletions(-) create mode 100644 src/download/download-engine/download-file/downloaderProgramManager.ts create mode 100644 src/download/download-engine/engine/DownloadEngineRemote.ts delete mode 100644 src/download/download-engine/engine/error/no-download-engine-provided-error.ts delete mode 100644 src/download/download-engine/engine/utils/concurrency.ts create mode 100644 src/download/download-engine/streams/download-engine-fetch-stream/errors/EmptyStreamTimeoutError.ts create mode 100644 src/download/download-engine/streams/download-engine-write-stream/utils/BytesWriteDebounce.ts create mode 100644 src/download/download-engine/utils/concurrency.ts create mode 100644 src/download/download-engine/utils/promiseWithResolvers.ts create mode 100644 src/download/transfer-visualize/transfer-cli/GlobalCLI.ts delete mode 100644 src/download/transfer-visualize/transfer-cli/cli-animation-wrapper.ts delete mode 100644 src/download/transfer-visualize/transfer-cli/loading-animation/base-loading-animation.ts delete mode 100644 src/download/transfer-visualize/transfer-cli/loading-animation/cli-spinners-loading-animation.ts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 45cf7a1..445310e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,19 +22,19 @@ jobs: - name: Generate docs run: npm run generate-docs - name: Upload build artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: "build" path: "dist" - name: Upload build artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: "docs" path: "docs" release: name: Release - if: github.ref == 'refs/heads/main' + if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/beta' runs-on: ubuntu-latest concurrency: release-${{ github.ref }} environment: @@ -52,10 +52,10 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: - node-version: "20" + node-version: "22" - name: Install modules run: npm ci --ignore-scripts - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: path: artifacts - name: Move artifacts @@ -73,16 +73,16 @@ jobs: id: set-npm-url run: | if [ -f .semanticRelease.npmPackage.deployedVersion.txt ]; then - echo "npm-url=https://www.npmjs.com/package/node-llama-cpp/v/$(cat .semanticRelease.npmPackage.deployedVersion.txt)" >> $GITHUB_OUTPUT + echo "npm-url=https://www.npmjs.com/package/ipull/v/$(cat .semanticRelease.npmPackage.deployedVersion.txt)" >> $GITHUB_OUTPUT fi - name: Upload docs to GitHub Pages if: steps.set-npm-url.outputs.npm-url != '' - uses: actions/upload-pages-artifact@v2 + uses: actions/upload-pages-artifact@v3 with: name: pages-docs path: docs - name: Deploy docs to GitHub Pages - if: steps.set-npm-url.outputs.npm-url != '' - uses: actions/deploy-pages@v2 + if: steps.set-npm-url.outputs.npm-url != '' && github.ref == 'refs/heads/main' + uses: actions/deploy-pages@v4 with: artifact_name: pages-docs diff --git a/.releaserc.json b/.releaserc.json index a395035..ef8eaf5 100644 --- a/.releaserc.json +++ b/.releaserc.json @@ -1,6 +1,10 @@ { "branches": [ - "main" + "main", + { + "name": "beta", + "prerelease": true + } ], "ci": true, "plugins": [ diff --git a/README.md b/README.md index cc532b2..665f1d7 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ npx ipull http://example.com/file.large ## Features - Download using parallels connections +- Maximize download speed (automatic parallelization, 3+) - Pausing and resuming downloads - Node.js and browser support - Smart retry on fail @@ -73,8 +74,9 @@ import {downloadFileBrowser} from "ipull/dist/browser.js"; const downloader = await downloadFileBrowser({ url: 'https://example.com/file.large', - onWrite: (cursor: number, buffer: Uint8Array, options) => { - console.log(`Writing ${buffer.length} bytes at cursor ${cursor}, with options: ${JSON.stringify(options)}`); + onWrite: (cursor: number, buffers: Uint8Array[], options) => { + const totalLength = buffers.reduce((acc, buffer) => acc + buffer.length, 0); + console.log(`Writing ${totalLength} bytes at cursor ${cursor}, with options: ${JSON.stringify(options)}`); } }); @@ -234,12 +236,12 @@ If the maximum reties was reached the download will fail and an error will be th ```ts import {downloadFile} from 'ipull'; -const downloader = await downloadFile({ - url: 'https://example.com/file.large', - directory: './this/path' -}); - try { + const downloader = await downloadFile({ + url: 'https://example.com/file.large', + directory: './this/path' + }); + await downloader.download(); } catch (error) { console.error(`Download failed: ${error.message}`); @@ -250,6 +252,8 @@ try { In some edge cases, the re-try mechanism may give the illusion that the download is stuck. +(You can see this in the progress object that "retrying" is true) + To debug this, disable the re-try mechanism: ```js @@ -258,6 +262,9 @@ const downloader = await downloadFile({ directory: './this/path', retry: { retries: 0 + }, + retryFetchDownloadInfo: { + retries: 0 } }); ``` @@ -286,6 +293,27 @@ downloader.on("progress", (progress) => { }); ``` +### Remote Download Listing + +If you want to show in the CLI the progress of a file downloading in remote. + +```ts +const originaldownloader = await downloadFile({ + url: 'https://example.com/file.large', + directory: './this/path' +}); + +const remoteDownloader = downloadFileRemote({ + cliProgress: true +}); + +originaldownloader.on("progress", (progress) => { + remoteDownloader.emitRemoteProgress(progress); +}); + +await originaldownloader.download(); +``` + ### Download multiple files If you want to download multiple files, you can use the `downloadSequence` function. diff --git a/examples/browser-log.ts b/examples/browser-log.ts index fbb0c77..75b40d0 100644 --- a/examples/browser-log.ts +++ b/examples/browser-log.ts @@ -1,11 +1,12 @@ -import {downloadFileBrowser} from "ipull/dist/browser.js"; +import {downloadFileBrowser} from "ipull/browser"; const BIG_IMAGE = "https://upload.wikimedia.org/wikipedia/commons/9/9e/1_dubrovnik_pano_-_edit1.jpg"; // 40mb const downloader = await downloadFileBrowser({ url: BIG_IMAGE, - onWrite: (cursor: number, buffer: Uint8Array, options) => { - console.log(`Writing ${buffer.length} bytes at cursor ${cursor}, with options: ${JSON.stringify(options)}`); + onWrite: (cursor: number, buffers: Uint8Array[], options: DownloadEngineWriteStreamOptionsBrowser) => { + const totalLength = buffers.reduce((acc, b) => acc + b.byteLength, 0); + console.log(`Writing ${totalLength} bytes at cursor ${cursor}, with options: ${JSON.stringify(options)}`); } }); diff --git a/package-lock.json b/package-lock.json index 80da83d..4d76fa1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,8 @@ "sleep-promise": "^9.1.0", "slice-ansi": "^7.1.0", "stdout-update": "^4.0.1", - "strip-ansi": "^7.1.0" + "strip-ansi": "^7.1.0", + "uid": "^2.0.2" }, "bin": { "ipull": "dist/cli/cli.js" @@ -1247,6 +1248,15 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@lukeed/csprng": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", + "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/@material/material-color-utilities": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/@material/material-color-utilities/-/material-color-utilities-0.2.7.tgz", @@ -7307,24 +7317,6 @@ "thenify-all": "^1.0.0" } }, - "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -10530,6 +10522,25 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -12370,10 +12381,11 @@ } }, "node_modules/typescript": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", - "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -12407,6 +12419,18 @@ "node": ">=0.8.0" } }, + "node_modules/uid": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", + "integrity": "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==", + "license": "MIT", + "dependencies": { + "@lukeed/csprng": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", diff --git a/package.json b/package.json index 4616151..5655c5e 100644 --- a/package.json +++ b/package.json @@ -147,6 +147,7 @@ "sleep-promise": "^9.1.0", "slice-ansi": "^7.1.0", "stdout-update": "^4.0.1", - "strip-ansi": "^7.1.0" + "strip-ansi": "^7.1.0", + "uid": "^2.0.2" } } diff --git a/src/browser.ts b/src/browser.ts index d81a030..2c5ae9e 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -1,4 +1,4 @@ -import {downloadFileBrowser, DownloadFileBrowserOptions, downloadSequenceBrowser} from "./download/browser-download.js"; +import {downloadFileBrowser, DownloadFileBrowserOptions, downloadFileRemoteBrowser, downloadSequenceBrowser, DownloadSequenceBrowserOptions} from "./download/browser-download.js"; import DownloadEngineBrowser from "./download/download-engine/engine/download-engine-browser.js"; import EmptyResponseError from "./download/download-engine/streams/download-engine-fetch-stream/errors/empty-response-error.js"; import StatusCodeError from "./download/download-engine/streams/download-engine-fetch-stream/errors/status-code-error.js"; @@ -9,17 +9,21 @@ import FetchStreamError from "./download/download-engine/streams/download-engine import IpullError from "./errors/ipull-error.js"; import EngineError from "./download/download-engine/engine/error/engine-error.js"; import {FormattedStatus} from "./download/transfer-visualize/format-transfer-status.js"; -import DownloadEngineMultiDownload from "./download/download-engine/engine/download-engine-multi-download.js"; +import DownloadEngineMultiDownload, {DownloadEngineMultiAllowedEngines} from "./download/download-engine/engine/download-engine-multi-download.js"; import HttpError from "./download/download-engine/streams/download-engine-fetch-stream/errors/http-error.js"; import BaseDownloadEngine from "./download/download-engine/engine/base-download-engine.js"; import {InvalidOptionError} from "./download/download-engine/engine/error/InvalidOptionError.js"; import {DownloadFlags, DownloadStatus} from "./download/download-engine/download-file/progress-status-file.js"; -import {NoDownloadEngineProvidedError} from "./download/download-engine/engine/error/no-download-engine-provided-error.js"; +import {DownloadEngineRemote} from "./download/download-engine/engine/DownloadEngineRemote.js"; +import { + DownloadEngineWriteStreamOptionsBrowser +} from "./download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-browser.js"; export { DownloadFlags, DownloadStatus, downloadFileBrowser, + downloadFileRemoteBrowser, downloadSequenceBrowser, EmptyResponseError, HttpError, @@ -29,15 +33,18 @@ export { FetchStreamError, IpullError, EngineError, - InvalidOptionError, - NoDownloadEngineProvidedError + InvalidOptionError }; export type { + DownloadEngineRemote, BaseDownloadEngine, DownloadFileBrowserOptions, DownloadEngineBrowser, DownloadEngineMultiDownload, + DownloadEngineMultiAllowedEngines, FormattedStatus, - SaveProgressInfo + SaveProgressInfo, + DownloadSequenceBrowserOptions, + DownloadEngineWriteStreamOptionsBrowser }; diff --git a/src/download/browser-download.ts b/src/download/browser-download.ts index b4f4ebf..a6d61f0 100644 --- a/src/download/browser-download.ts +++ b/src/download/browser-download.ts @@ -1,34 +1,43 @@ import DownloadEngineBrowser, {DownloadEngineOptionsBrowser} from "./download-engine/engine/download-engine-browser.js"; -import DownloadEngineMultiDownload from "./download-engine/engine/download-engine-multi-download.js"; -import {NoDownloadEngineProvidedError} from "./download-engine/engine/error/no-download-engine-provided-error.js"; +import DownloadEngineMultiDownload, {DownloadEngineMultiDownloadOptions} from "./download-engine/engine/download-engine-multi-download.js"; +import BaseDownloadEngine from "./download-engine/engine/base-download-engine.js"; +import {DownloadSequenceOptions} from "./node-download.js"; +import {DownloadEngineRemote} from "./download-engine/engine/DownloadEngineRemote.js"; const DEFAULT_PARALLEL_STREAMS_FOR_BROWSER = 3; -export type DownloadFileBrowserOptions = DownloadEngineOptionsBrowser & { - /** @deprecated use partURLs instead */ - partsURL?: string[]; -}; +export type DownloadFileBrowserOptions = DownloadEngineOptionsBrowser; + +export type DownloadSequenceBrowserOptions = DownloadEngineMultiDownloadOptions; /** * Download one file in the browser environment. */ export async function downloadFileBrowser(options: DownloadFileBrowserOptions) { - // TODO: Remove in the next major version - if (!("url" in options) && options.partsURL) { - options.partURLs ??= options.partsURL; - } - options.parallelStreams ??= DEFAULT_PARALLEL_STREAMS_FOR_BROWSER; return await DownloadEngineBrowser.createFromOptions(options); } +/** + * Stream events for a download from remote session, doing so by calling `emitRemoteProgress` with the progress info. + */ +export function downloadFileRemoteBrowser() { + return new DownloadEngineRemote(); +} + /** * Download multiple files in the browser environment. */ -export async function downloadSequenceBrowser(...downloads: (DownloadEngineBrowser | Promise)[]) { - if (downloads.length === 0) { - throw new NoDownloadEngineProvidedError(); +export async function downloadSequenceBrowser(options?: DownloadSequenceBrowserOptions | DownloadEngineBrowser | Promise, ...downloads: (DownloadEngineBrowser | Promise)[]) { + let downloadOptions: DownloadSequenceOptions = {}; + if (options instanceof BaseDownloadEngine || options instanceof Promise) { + downloads.unshift(options); + } else if (options) { + downloadOptions = options; } - return await DownloadEngineMultiDownload.fromEngines(downloads); + const downloader = new DownloadEngineMultiDownload(downloadOptions); + await downloader.addDownload(...downloads); + + return downloader; } diff --git a/src/download/download-engine/download-file/download-engine-file.ts b/src/download/download-engine/download-file/download-engine-file.ts index f7b3b5c..8963f71 100644 --- a/src/download/download-engine/download-file/download-engine-file.ts +++ b/src/download/download-engine/download-file/download-engine-file.ts @@ -8,6 +8,8 @@ import {withLock} from "lifecycle-utils"; import switchProgram, {AvailablePrograms} from "./download-programs/switch-program.js"; import BaseDownloadProgram from "./download-programs/base-download-program.js"; import {pushComment} from "./utils/push-comment.js"; +import {uid} from "uid"; +import {DownloaderProgramManager} from "./downloaderProgramManager.js"; export type DownloadEngineFileOptions = { chunkSize?: number; @@ -19,8 +21,10 @@ export type DownloadEngineFileOptions = { onFinishAsync?: () => Promise onStartedAsync?: () => Promise onCloseAsync?: () => Promise + onPausedAsync?: () => Promise onSaveProgressAsync?: (progress: SaveProgressInfo) => Promise programType?: AvailablePrograms + autoIncreaseParallelStreams?: boolean /** @internal */ skipExisting?: boolean; @@ -42,9 +46,13 @@ export type DownloadEngineFileEvents = { [key: string]: any }; +const DEFAULT_CHUNKS_SIZE_FOR_CHUNKS_PROGRAM = 1024 * 1024 * 5; // 5MB +const DEFAULT_CHUNKS_SIZE_FOR_STREAM_PROGRAM = 1024 * 1024; // 1MB + const DEFAULT_OPTIONS: Omit = { - chunkSize: 1024 * 1024 * 5, - parallelStreams: 1 + chunkSize: 0, + parallelStreams: 3, + autoIncreaseParallelStreams: true }; export default class DownloadEngineFile extends EventEmitter { @@ -52,6 +60,7 @@ export default class DownloadEngineFile extends EventEmitter c.isRetrying); + thisStatus.retryingTotalAttempts = Math.max(...streamContexts.map(x => x.retryingAttempts)); + thisStatus.streamsNotResponding = streamContexts.reduce((acc, cur) => acc + (cur.isStreamNotResponding ? 1 : 0), 0); + + return thisStatus; } protected get _activePart() { @@ -113,8 +153,8 @@ export default class DownloadEngineFile extends EventEmitter acc + bytes, 0); + const streamingBytes = Object.values(this._activeStreamContext) + .reduce((acc, cur) => acc + cur.streamBytes, 0); const streamBytes = this._activeDownloadedChunkSize + streamingBytes; const streamBytesMin = Math.min(streamBytes, this._activePart.size || streamBytes); @@ -123,6 +163,13 @@ export default class DownloadEngineFile extends EventEmitter 0 ? this._progress.chunks.length : Infinity; await this._downloadSlice(0, chunksToRead); @@ -198,7 +258,7 @@ export default class DownloadEngineFile extends EventEmitter this._activeStreamContext[startChunk] ??= {streamBytes: 0, retryingAttempts: 0}; + const fetchState = this.options.fetchStream.withSubState({ chunkSize: this._progress.chunkSize, startChunk, - endChunk, + endChunk: endChunk, + lastChunkEndsFile: endChunk === Infinity || endChunk === this._progress.chunks.length, totalSize: this._activePart.size, url: this._activePart.downloadURL!, rangeSupport: this._activePart.acceptRange, onProgress: (length: number) => { - this._activeStreamBytes[startChunk] = length; + getContext().streamBytes = length; this._sendProgressDownloadPart(); } }); + fetchState.addListener("retryingOn", () => { + const context = getContext(); + context.isRetrying = true; + context.retryingAttempts++; + this._sendProgressDownloadPart(); + }); + + fetchState.addListener("retryingOff", () => { + getContext().isRetrying = false; + }); + + fetchState.addListener("streamNotRespondingOn", () => { + getContext().isStreamNotResponding = true; + this._sendProgressDownloadPart(); + }); + + fetchState.addListener("streamNotRespondingOff", () => { + getContext().isStreamNotResponding = false; + }); const downloadedPartsSize = this._downloadedPartsSize; this._progress.chunks[startChunk] = ChunkStatus.IN_PROGRESS; - return (async () => { - const allWrites = new Set>(); + const allWrites = new Set>(); - let lastChunkSize = 0; - await fetchState.fetchChunks((chunks, writePosition, index) => { - if (this._closed || this._progress.chunks[index] != ChunkStatus.IN_PROGRESS) { - return; - } + let lastChunkSize = 0, lastInProgressIndex = startChunk; + await fetchState.fetchChunks((chunks, writePosition, index) => { + if (this._closed || this._progress.chunks[index] != ChunkStatus.IN_PROGRESS) { + return; + } - for (const chunk of chunks) { - const writePromise = this.options.writeStream.write(downloadedPartsSize + writePosition, chunk); - writePosition += chunk.length; - if (writePromise) { - allWrites.add(writePromise); - writePromise.then(() => { - allWrites.delete(writePromise); - }); - } - } + const writePromise = this.options.writeStream.write(downloadedPartsSize + writePosition, chunks); + if (writePromise) { + allWrites.add(writePromise); + writePromise.then(() => { + allWrites.delete(writePromise); + }); + } - // if content length is 0, we do not know how many chunks we should have - if (this._activePart.size === 0) { - this._progress.chunks.push(ChunkStatus.NOT_STARTED); - } + // if content length is 0, we do not know how many chunks we should have + if (this._activePart.size === 0) { + this._progress.chunks.push(ChunkStatus.NOT_STARTED); + } - this._progress.chunks[index] = ChunkStatus.COMPLETE; - lastChunkSize = chunks.reduce((last, current) => last + current.length, 0); - delete this._activeStreamBytes[startChunk]; - void this._saveProgress(); + this._progress.chunks[index] = ChunkStatus.COMPLETE; + lastChunkSize = chunks.reduce((last, current) => last + current.length, 0); + getContext().streamBytes = 0; + void this._saveProgress(); - const nextChunk = this._progress.chunks[index + 1]; - const shouldReadNext = endChunk - index > 1; // grater than 1, meaning there is a next chunk + const nextChunk = this._progress.chunks[index + 1]; + const shouldReadNext = fetchState.state.endChunk - index > 1; // grater than 1, meaning there is a next chunk - if (shouldReadNext) { - if (nextChunk == null || nextChunk != ChunkStatus.NOT_STARTED) { - return fetchState.close(); - } - this._progress.chunks[index + 1] = ChunkStatus.IN_PROGRESS; + if (shouldReadNext) { + if (nextChunk == null || nextChunk != ChunkStatus.NOT_STARTED) { + return fetchState.close(); } - }); - - // On dynamic content length, we need to adjust the last chunk size - if (this._activePart.size === 0) { - this._activePart.size = this._activeDownloadedChunkSize - this.options.chunkSize + lastChunkSize; - this._progress.chunks = this._progress.chunks.filter(c => c === ChunkStatus.COMPLETE); + this._progress.chunks[lastInProgressIndex = index + 1] = ChunkStatus.IN_PROGRESS; } + }); + + if (this._progress.chunks[lastInProgressIndex] === ChunkStatus.IN_PROGRESS) { + this._progress.chunks[lastInProgressIndex] = ChunkStatus.NOT_STARTED; + } + + // On dynamic content length, we need to adjust the last chunk size + if (this._activePart.size === 0) { + this._activePart.size = this._activeDownloadedChunkSize - this.options.chunkSize + lastChunkSize; + this._progress.chunks = this._progress.chunks.filter(c => c === ChunkStatus.COMPLETE); + } - delete this._activeStreamBytes[startChunk]; - await Promise.all(allWrites); - })(); + delete this._activeStreamContext[startChunk]; + await Promise.all(allWrites); } protected _saveProgress() { @@ -288,7 +369,7 @@ export default class DownloadEngineFile extends EventEmitter { - if (thisProgress === this._latestProgressDate) { + if (thisProgress === this._latestProgressDate && !this._closed && this._downloadStatus !== DownloadStatus.Finished) { await this.options.onSaveProgressAsync?.(this._progress); } }); @@ -299,14 +380,14 @@ export default class DownloadEngineFile extends EventEmitter void; + protected _activeDownloads: Promise[] = []; protected constructor(_savedProgress: SaveProgressInfo, _downloadSlice: DownloadSlice) { this._downloadSlice = _downloadSlice; this.savedProgress = _savedProgress; + this._parallelStreams = this.savedProgress.parallelStreams; + } + + get parallelStreams() { + return this._parallelStreams; + } + + set parallelStreams(value: number) { + const needReload = value > this._parallelStreams; + this._parallelStreams = value; + if (needReload) { + this._reload?.(); + } + } + + incParallelStreams() { + this.parallelStreams = this._activeDownloads.length + 1; + } + + decParallelStreams() { + this.parallelStreams = this._activeDownloads.length - 1; + } + + waitForStreamToEnd() { + return Promise.race(this._activeDownloads); } public async download(): Promise { - if (this.savedProgress.parallelStreams === 1) { + if (this._parallelStreams === 1) { return await this._downloadSlice(0, this.savedProgress.chunks.length); } - const activeDownloads: Promise[] = []; + this._createFirstSlices(); - // eslint-disable-next-line no-constant-condition - while (true) { - while (activeDownloads.length >= this.savedProgress.parallelStreams) { - if (this._aborted) return; - await Promise.race(activeDownloads); + while (!this._aborted) { + if (this._activeDownloads.length >= this._parallelStreams) { + await this._waitForStreamEndWithReload(); + continue; } const slice = this._createOneSlice(); - if (slice == null) break; - - if (this._aborted) return; - const promise = this._downloadSlice(slice.start, slice.end); - activeDownloads.push(promise); - promise.then(() => { - activeDownloads.splice(activeDownloads.indexOf(promise), 1); - }); + if (slice == null) { + if (this._activeDownloads.length === 0) { + break; + } + await this._waitForStreamEndWithReload(); + continue; + } + this._createDownload(slice); } + } + + private async _waitForStreamEndWithReload() { + const promiseResolvers = promiseWithResolvers(); + this._reload = promiseResolvers.resolve; + return await Promise.race(this._activeDownloads.concat([promiseResolvers.promise])); + } + + private _createDownload(slice: ProgramSlice) { + const promise = this._downloadSlice(slice.start, slice.end); + this._activeDownloads.push(promise); + promise.then(() => { + this._activeDownloads.splice(this._activeDownloads.indexOf(promise), 1); + }); + } - await Promise.all(activeDownloads); + /** + * Create all the first slices at one - make sure they will not overlap to reduce stream aborts at later stages + */ + private _createFirstSlices() { + const slices: ProgramSlice[] = []; + for (let i = 0; i < this.parallelStreams; i++) { + const slice = this._createOneSlice(); + if (slice) { + const lastSlice = slices.find(x => x.end > slice.start && x.start < slice.start); + if (lastSlice) { + lastSlice.end = slice.start; + } + this.savedProgress.chunks[slice.start] = ChunkStatus.IN_PROGRESS; + slices.push(slice); + } else { + break; + } + } + + for (const slice of slices) { + this._createDownload(slice); + } } protected abstract _createOneSlice(): ProgramSlice | null; diff --git a/src/download/download-engine/download-file/download-programs/download-program-stream.ts b/src/download/download-engine/download-file/download-programs/download-program-stream.ts index 5c90138..4d3e0b1 100644 --- a/src/download/download-engine/download-file/download-programs/download-program-stream.ts +++ b/src/download/download-engine/download-file/download-programs/download-program-stream.ts @@ -11,7 +11,8 @@ export default class DownloadProgramStream extends BaseDownloadProgram { const slice = this._findChunksSlices()[0]; if (!slice) return null; const length = slice.end - slice.start; - return {start: Math.floor(slice.start + length / 2), end: slice.end}; + const start = slice.start == 0 ? slice.start : Math.floor(slice.start + length / 2); + return {start, end: slice.end}; } private _findChunksSlices() { diff --git a/src/download/download-engine/download-file/downloaderProgramManager.ts b/src/download/download-engine/download-file/downloaderProgramManager.ts new file mode 100644 index 0000000..6f15b47 --- /dev/null +++ b/src/download/download-engine/download-file/downloaderProgramManager.ts @@ -0,0 +1,106 @@ +import BaseDownloadProgram from "./download-programs/base-download-program.js"; +import DownloadEngineFile from "./download-engine-file.js"; +import {DownloadStatus, ProgressStatus} from "./progress-status-file.js"; +import sleep from "sleep-promise"; + +const BASE_AVERAGE_SPEED_TIME = 1000; +const AVERAGE_SPEED_TIME = 1000 * 8; +const ALLOW_SPEED_DECREASE_PERCENTAGE = 10; +const ADD_MORE_PARALLEL_IF_SPEED_INCREASE_PERCENTAGE = 10; + +export class DownloaderProgramManager { + // date, speed + private _speedHistory: [number, number][] = []; + private _lastResumeDate = 0; + private _lastAverageSpeed = 0; + private _increasePaused = false; + protected _removeEvent?: () => void; + + constructor(protected _program: BaseDownloadProgram, protected _download: DownloadEngineFile) { + this._initEvents(); + } + + private _initEvents() { + let lastTransferredBytes = 0; + let lastTransferredBytesDate = 0; + + let watNotActive = true; + const progressEvent = (event: ProgressStatus) => { + const now = Date.now(); + + if (event.retrying || event.downloadStatus != DownloadStatus.Active) { + watNotActive = true; + } else { + if (watNotActive) { + this._lastResumeDate = now; + watNotActive = false; + } + + const isTimeToCalculate = lastTransferredBytesDate + BASE_AVERAGE_SPEED_TIME < now; + if (lastTransferredBytesDate === 0 || isTimeToCalculate) { + if (isTimeToCalculate) { + const speedPerSec = event.transferredBytes - lastTransferredBytes; + this._speedHistory.push([now, speedPerSec]); + } + + lastTransferredBytesDate = now; + lastTransferredBytes = event.transferredBytes; + } + + } + + const deleteAllBefore = now - AVERAGE_SPEED_TIME; + this._speedHistory = this._speedHistory.filter(([date]) => date > deleteAllBefore); + + + if (!watNotActive && now - this._lastResumeDate > AVERAGE_SPEED_TIME) { + this._checkAction(); + } + }; + + this._download.on("progress", progressEvent); + this._removeEvent = () => this._download.off("progress", progressEvent); + } + + private _calculateAverageSpeed() { + const totalSpeed = this._speedHistory.reduce((acc, [, speed]) => acc + speed, 0); + return totalSpeed / (this._speedHistory.length || 1); + } + + private async _checkAction() { + const lastAverageSpeed = this._lastAverageSpeed; + const newAverageSpeed = this._calculateAverageSpeed(); + const speedDecreasedOK = (lastAverageSpeed - newAverageSpeed) / newAverageSpeed * 100 > ALLOW_SPEED_DECREASE_PERCENTAGE; + + if (!speedDecreasedOK) { + this._lastAverageSpeed = newAverageSpeed; + } + + if (this._increasePaused || newAverageSpeed > lastAverageSpeed || speedDecreasedOK) { + return; + } + + this._increasePaused = true; + this._program.incParallelStreams(); + let sleepTime = AVERAGE_SPEED_TIME; + + while (sleepTime <= AVERAGE_SPEED_TIME) { + await sleep(sleepTime); + sleepTime = Date.now() - this._lastResumeDate; + } + + const newSpeed = this._calculateAverageSpeed(); + const bestLastSpeed = Math.max(newAverageSpeed, lastAverageSpeed); + const speedIncreasedOK = newSpeed > bestLastSpeed && (newSpeed - bestLastSpeed) / bestLastSpeed * 100 > ADD_MORE_PARALLEL_IF_SPEED_INCREASE_PERCENTAGE; + + if (speedIncreasedOK) { + this._increasePaused = false; + } else { + this._program.decParallelStreams(); + } + } + + close() { + this._removeEvent?.(); + } +} diff --git a/src/download/download-engine/download-file/progress-status-file.ts b/src/download/download-engine/download-file/progress-status-file.ts index a2d7e42..d07359d 100644 --- a/src/download/download-engine/download-file/progress-status-file.ts +++ b/src/download/download-engine/download-file/progress-status-file.ts @@ -1,4 +1,5 @@ export type ProgressStatus = { + downloadId: string, totalBytes: number, totalDownloadParts: number, fileName: string, @@ -10,6 +11,9 @@ export type ProgressStatus = { transferAction: string downloadStatus: DownloadStatus downloadFlags: DownloadFlags[] + retrying: boolean + retryingTotalAttempts: number + streamsNotResponding: number }; export enum DownloadStatus { @@ -39,6 +43,10 @@ export default class ProgressStatusFile { public totalBytes: number = 0; public startTime: number = 0; public endTime: number = 0; + public downloadId: string = ""; + public retrying = false; + public retryingTotalAttempts = 0; + public streamsNotResponding = 0; public constructor( totalDownloadParts: number, @@ -68,7 +76,13 @@ export default class ProgressStatusFile { this.endTime = Date.now(); } - public createStatus(downloadPart: number, transferredBytes: number, totalBytes = this.totalBytes, downloadStatus = DownloadStatus.Active, comment = this.comment): ProgressStatusFile { + public createStatus( + downloadPart: number, + transferredBytes: number, + totalBytes = this.totalBytes, + downloadStatus = DownloadStatus.Active, + comment = this.comment + ): ProgressStatusFile { const newStatus = new ProgressStatusFile( this.totalDownloadParts, this.fileName, @@ -83,6 +97,7 @@ export default class ProgressStatusFile { newStatus.totalBytes = totalBytes; newStatus.startTime = this.startTime; newStatus.endTime = this.endTime; + newStatus.downloadId = this.downloadId; return newStatus; } diff --git a/src/download/download-engine/engine/DownloadEngineRemote.ts b/src/download/download-engine/engine/DownloadEngineRemote.ts new file mode 100644 index 0000000..e9ff5c9 --- /dev/null +++ b/src/download/download-engine/engine/DownloadEngineRemote.ts @@ -0,0 +1,73 @@ +import {BaseDownloadEngineEvents} from "./base-download-engine.js"; +import {EventEmitter} from "eventemitter3"; +import {FormattedStatus} from "../../transfer-visualize/format-transfer-status.js"; +import {DownloadStatus} from "../download-file/progress-status-file.js"; +import ProgressStatisticsBuilder from "../../transfer-visualize/progress-statistics-builder.js"; +import {promiseWithResolvers} from "../utils/promiseWithResolvers.js"; + +export class DownloadEngineRemote extends EventEmitter { + /** + * @internal + */ + _downloadEndPromise = promiseWithResolvers(); + /** + * @internal + */ + _downloadStarted = false; + + private _latestStatus: FormattedStatus = ProgressStatisticsBuilder.loadingStatusEmptyStatistics(); + + public get status() { + return this._latestStatus!; + } + + public get downloadStatues() { + return [this.status]; + } + + public get downloadSize() { + return this._latestStatus?.totalBytes ?? 0; + } + + public get fileName() { + return this._latestStatus?.fileName ?? ""; + } + + public download() { + return this._downloadEndPromise.promise; + } + + public emitRemoteProgress(progress: FormattedStatus) { + this._latestStatus = progress; + this.emit("progress", progress); + const isStatusChanged = this._latestStatus?.downloadStatus !== progress.downloadStatus; + + if (!isStatusChanged) { + return; + } + + switch (progress.downloadStatus) { + case DownloadStatus.Active: + if (this._latestStatus?.downloadStatus === DownloadStatus.Paused) { + this.emit("resumed"); + } else { + this.emit("start"); + this._downloadStarted = true; + } + break; + case DownloadStatus.Finished: + case DownloadStatus.Cancelled: + this.emit("finished"); + this.emit("closed"); + this._downloadEndPromise.resolve(); + break; + case DownloadStatus.Paused: + this.emit("paused"); + break; + } + + if (progress.downloadStatus === DownloadStatus.Active) { + this.emit("start"); + } + } +} diff --git a/src/download/download-engine/engine/base-download-engine.ts b/src/download/download-engine/engine/base-download-engine.ts index a1c6303..4b9dc90 100644 --- a/src/download/download-engine/engine/base-download-engine.ts +++ b/src/download/download-engine/engine/base-download-engine.ts @@ -3,29 +3,35 @@ import DownloadEngineFile, {DownloadEngineFileOptions} from "../download-file/do import BaseDownloadEngineFetchStream, {BaseDownloadEngineFetchStreamOptions} from "../streams/download-engine-fetch-stream/base-download-engine-fetch-stream.js"; import UrlInputError from "./error/url-input-error.js"; import {EventEmitter} from "eventemitter3"; -import ProgressStatisticsBuilder, {ProgressStatusWithIndex} from "../../transfer-visualize/progress-statistics-builder.js"; -import DownloadAlreadyStartedError from "./error/download-already-started-error.js"; +import ProgressStatisticsBuilder from "../../transfer-visualize/progress-statistics-builder.js"; import retry from "async-retry"; import {AvailablePrograms} from "../download-file/download-programs/switch-program.js"; import StatusCodeError from "../streams/download-engine-fetch-stream/errors/status-code-error.js"; import {InvalidOptionError} from "./error/InvalidOptionError.js"; +import {FormattedStatus} from "../../transfer-visualize/format-transfer-status.js"; +import {promiseWithResolvers} from "../utils/promiseWithResolvers.js"; const IGNORE_HEAD_STATUS_CODES = [405, 501, 404]; export type InputURLOptions = { partURLs: string[] } | { url: string }; -export type BaseDownloadEngineOptions = InputURLOptions & BaseDownloadEngineFetchStreamOptions & { +export type CreateDownloadFileOptions = { + reuseRedirectURL?: boolean +}; + +export type BaseDownloadEngineOptions = CreateDownloadFileOptions & InputURLOptions & BaseDownloadEngineFetchStreamOptions & { chunkSize?: number; parallelStreams?: number; retry?: retry.Options comment?: string; - programType?: AvailablePrograms + programType?: AvailablePrograms, + autoIncreaseParallelStreams?: boolean }; export type BaseDownloadEngineEvents = { start: () => void paused: () => void resumed: () => void - progress: (progress: ProgressStatusWithIndex) => void + progress: (progress: FormattedStatus) => void save: (progress: SaveProgressInfo) => void finished: () => void closed: () => void @@ -36,8 +42,16 @@ export default class BaseDownloadEngine extends EventEmitter(); + /** + * @internal + */ + _downloadStarted = false; + protected _latestStatus?: FormattedStatus; protected constructor(engine: DownloadEngineFile, options: DownloadEngineFileOptions) { super(); @@ -96,18 +110,22 @@ export default class BaseDownloadEngine extends EventEmitter { this._latestStatus = status; - return this.emit("progress", status); + this.emit("progress", status); }); } async download() { if (this._downloadStarted) { - throw new DownloadAlreadyStartedError(); + return this._downloadEndPromise.promise; } try { this._downloadStarted = true; - await this._engine.download(); + const promise = this._engine.download(); + promise + .then(this._downloadEndPromise.resolve) + .catch(this._downloadEndPromise.reject); + await promise; } finally { await this.close(); } @@ -125,7 +143,7 @@ export default class BaseDownloadEngine extends EventEmitter { try { const {length, acceptRange, newURL, fileName} = await fetchStream.fetchDownloadInfo(part); - const downloadURL = newURL ?? part; + const downloadURL = reuseRedirectURL ? (newURL ?? part) : part; const size = length || 0; downloadFile.totalSize += size; - downloadFile.parts.push({ + if (index === 0 && fileName) { + downloadFile.localFileName = fileName; + } + + return { downloadURL, size, acceptRange: size > 0 && acceptRange - }); - - if (counter++ === 0 && fileName) { - downloadFile.localFileName = fileName; - } + }; } catch (error: any) { if (error instanceof StatusCodeError && IGNORE_HEAD_STATUS_CODES.includes(error.statusCode)) { // if the server does not support HEAD request, we will skip that step - downloadFile.parts.push({ + return { downloadURL: part, size: 0, acceptRange: false - }); - continue; + }; } throw error; } - } + })); return downloadFile; } diff --git a/src/download/download-engine/engine/download-engine-multi-download.ts b/src/download/download-engine/engine/download-engine-multi-download.ts index ebdbadf..110f667 100644 --- a/src/download/download-engine/engine/download-engine-multi-download.ts +++ b/src/download/download-engine/engine/download-engine-multi-download.ts @@ -1,51 +1,126 @@ import {EventEmitter} from "eventemitter3"; import {FormattedStatus} from "../../transfer-visualize/format-transfer-status.js"; -import ProgressStatisticsBuilder, {ProgressStatusWithIndex} from "../../transfer-visualize/progress-statistics-builder.js"; +import ProgressStatisticsBuilder from "../../transfer-visualize/progress-statistics-builder.js"; import BaseDownloadEngine, {BaseDownloadEngineEvents} from "./base-download-engine.js"; -import DownloadAlreadyStartedError from "./error/download-already-started-error.js"; -import {concurrency} from "./utils/concurrency.js"; +import {concurrency} from "../utils/concurrency.js"; import {DownloadFlags, DownloadStatus} from "../download-file/progress-status-file.js"; -import {NoDownloadEngineProvidedError} from "./error/no-download-engine-provided-error.js"; +import {DownloadEngineRemote} from "./DownloadEngineRemote.js"; +import {promiseWithResolvers} from "../utils/promiseWithResolvers.js"; -const DEFAULT_PARALLEL_DOWNLOADS = 1; - -type DownloadEngineMultiAllowedEngines = BaseDownloadEngine; +export type DownloadEngineMultiAllowedEngines = BaseDownloadEngine | DownloadEngineRemote | DownloadEngineMultiDownload; type DownloadEngineMultiDownloadEvents = BaseDownloadEngineEvents & { childDownloadStarted: (engine: Engine) => void childDownloadClosed: (engine: Engine) => void + downloadAdded: (engine: Engine) => void }; export type DownloadEngineMultiDownloadOptions = { parallelDownloads?: number + /** + * Unpack inner downloads statues to the main download statues, + * useful for showing CLI progress in separate downloads or tracking download progress separately + */ + unpackInnerMultiDownloadsStatues?: boolean + /** + * Finalize download (change .ipull file to original extension) after all downloads are settled + */ + finalizeDownloadAfterAllSettled?: boolean + + /** + * Do not start download automatically + * @internal + */ + naturalDownloadStart?: boolean + + downloadName?: string + downloadComment?: string }; +const DEFAULT_OPTIONS = { + parallelDownloads: 1, + unpackInnerMultiDownloadsStatues: true, + finalizeDownloadAfterAllSettled: true +} satisfies DownloadEngineMultiDownloadOptions; + export default class DownloadEngineMultiDownload extends EventEmitter { - public readonly downloads: Engine[]; - public readonly options: DownloadEngineMultiDownloadOptions; + public readonly downloads: Engine[] = []; + protected _options: DownloadEngineMultiDownloadOptions; protected _aborted = false; protected _activeEngines = new Set(); protected _progressStatisticsBuilder = new ProgressStatisticsBuilder(); - protected _downloadStatues: (ProgressStatusWithIndex | FormattedStatus)[] = []; + protected _downloadStatues: FormattedStatus[] | FormattedStatus[][] = []; protected _closeFiles: (() => Promise)[] = []; - protected _lastStatus?: ProgressStatusWithIndex; + protected _lastStatus: FormattedStatus = null!; protected _loadingDownloads = 0; - - protected constructor(engines: (DownloadEngineMultiAllowedEngines | DownloadEngineMultiDownload)[], options: DownloadEngineMultiDownloadOptions) { + protected _reloadDownloadParallelisms?: () => void; + protected _engineWaitPromises = new Set>(); + /** + * @internal + */ + _downloadEndPromise = promiseWithResolvers(); + /** + * @internal + */ + _downloadStarted = false; + + /** + * @internal + */ + constructor(options: DownloadEngineMultiDownloadOptions = {}) { super(); - this.downloads = DownloadEngineMultiDownload._extractEngines(engines); - this.options = options; + this._options = {...DEFAULT_OPTIONS, ...options}; this._init(); } + public get activeDownloads() { + return Array.from(this._activeEngines); + } + + public get parallelDownloads() { + return this._options.parallelDownloads; + } + + public get loadingDownloads() { + if (!this._options.unpackInnerMultiDownloadsStatues) { + return this._loadingDownloads; + } + + let totalLoading = this._loadingDownloads; + for (const download of this.downloads) { + if (download instanceof DownloadEngineMultiDownload) { + totalLoading += download.loadingDownloads; + } + } + + return totalLoading; + } + + /** + * @internal + */ + public get _flatEngines(): Engine[] { + return this.downloads.map(engine => { + if (engine instanceof DownloadEngineMultiDownload) { + return engine._flatEngines; + } + return engine; + }) + .flat(); + } + + public set parallelDownloads(value) { + if (this._options.parallelDownloads === value) return; + this._options.parallelDownloads = value; + this._reloadDownloadParallelisms?.(); + } + public get downloadStatues() { - return this._downloadStatues; + const statues = this._downloadStatues.flat(); + return statues.filter(((status, index) => statues.findIndex(x => x.downloadId === status.downloadId) === index)); } public get status() { - if (!this._lastStatus) { - throw new NoDownloadEngineProvidedError(); - } return this._lastStatus; } @@ -58,82 +133,129 @@ export default class DownloadEngineMultiDownload { progress = { ...progress, + fileName: this._options.downloadName ?? progress.fileName, + comment: this._options.downloadComment ?? progress.comment, downloadFlags: progress.downloadFlags.concat([DownloadFlags.DownloadSequence]) }; this._lastStatus = progress; this.emit("progress", progress); }); - let index = 0; - for (const engine of this.downloads) { - this._addEngine(engine, index++); - } - - // Prevent multiple progress events on adding engines - this._progressStatisticsBuilder.add(...this.downloads); + const originalProgress = this._progressStatisticsBuilder.status; + this._lastStatus = { + ...originalProgress, + downloadFlags: originalProgress.downloadFlags.concat([DownloadFlags.DownloadSequence]) + }; } private _addEngine(engine: Engine, index: number) { - this._downloadStatues[index] = engine.status; + this.emit("downloadAdded", engine); + const getStatus = (defaultProgress = engine.status) => + (this._options.unpackInnerMultiDownloadsStatues && engine instanceof DownloadEngineMultiDownload ? engine.downloadStatues : defaultProgress); + + this._downloadStatues[index] = getStatus(); engine.on("progress", (progress) => { - this._downloadStatues[index] = progress; + this._downloadStatues[index] = getStatus(progress); }); - this._changeEngineFinishDownload(engine); + if (this._options.finalizeDownloadAfterAllSettled) { + this._changeEngineFinishDownload(engine); + } + + this.downloads.push(engine); + this._reloadDownloadParallelisms?.(); } - public async addDownload(engine: Engine | DownloadEngineMultiDownload | Promise>) { + public async _addDownloadNoStatisticUpdate(engine: Engine | Promise) { const index = this.downloads.length + this._loadingDownloads; this._downloadStatues[index] = ProgressStatisticsBuilder.loadingStatusEmptyStatistics(); this._loadingDownloads++; this._progressStatisticsBuilder._totalDownloadParts++; - const awaitEngine = engine instanceof Promise ? await engine : engine; - this._progressStatisticsBuilder._totalDownloadParts--; - this._loadingDownloads--; + this._progressStatisticsBuilder._sendLatestProgress(); - if (awaitEngine instanceof DownloadEngineMultiDownload) { - let countEngines = 0; - for (const subEngine of awaitEngine.downloads) { - this._addEngine(subEngine, index + countEngines++); - this.downloads.push(subEngine); - } - this._progressStatisticsBuilder.add(...awaitEngine.downloads); - } else { - this._addEngine(awaitEngine, index); - this.downloads.push(awaitEngine); - this._progressStatisticsBuilder.add(awaitEngine); + const isPromise = engine instanceof Promise; + if (isPromise) { + this._engineWaitPromises.add(engine); } - } - - public async download(): Promise { - if (this._activeEngines.size) { - throw new DownloadAlreadyStartedError(); + const awaitEngine = isPromise ? await engine : engine; + if (isPromise) { + this._engineWaitPromises.delete(engine); } + this._progressStatisticsBuilder._totalDownloadParts--; + this._loadingDownloads--; - this._progressStatisticsBuilder.downloadStatus = DownloadStatus.Active; - this.emit("start"); + this._addEngine(awaitEngine, index); + this._progressStatisticsBuilder.add(awaitEngine, true); + return awaitEngine; + } - const concurrencyCount = this.options.parallelDownloads ?? DEFAULT_PARALLEL_DOWNLOADS; - await concurrency(this.downloads, concurrencyCount, async (engine) => { - if (this._aborted) return; - this._activeEngines.add(engine); + public async addDownload(...engines: (Engine | Promise)[]) { + await Promise.all(engines.map(this._addDownloadNoStatisticUpdate.bind(this))); + } - this.emit("childDownloadStarted", engine); - await engine.download(); - this.emit("childDownloadClosed", engine); + public async download(): Promise { + if (this._downloadStarted) { + return this._downloadEndPromise.promise; + } - this._activeEngines.delete(engine); - }); + try { + this._progressStatisticsBuilder.downloadStatus = DownloadStatus.Active; + this._downloadStarted = true; + this.emit("start"); + + const concurrencyCount = this._options.parallelDownloads || DEFAULT_OPTIONS.parallelDownloads; + let continueIteration = true; + while (this._loadingDownloads > 0 || continueIteration) { + continueIteration = false; + const {reload, promise} = concurrency(this.downloads, concurrencyCount, async (engine) => { + if (this._aborted) return; + this._activeEngines.add(engine); + + this.emit("childDownloadStarted", engine); + if (engine._downloadStarted || this._options.naturalDownloadStart) { + await engine._downloadEndPromise.promise; + } else { + await engine.download(); + } + this.emit("childDownloadClosed", engine); + + this._activeEngines.delete(engine); + }); + this._reloadDownloadParallelisms = reload; + await promise; + continueIteration = this._engineWaitPromises.size > 0; + if (continueIteration) { + await Promise.race(this._engineWaitPromises); + } + } - this._progressStatisticsBuilder.downloadStatus = DownloadStatus.Finished; - this.emit("finished"); - await this._finishEnginesDownload(); - await this.close(); + this._downloadEndPromise = promiseWithResolvers(); + this._progressStatisticsBuilder.downloadStatus = DownloadStatus.Finished; + this.emit("finished"); + await this._finishEnginesDownload(); + await this.close(); + this._downloadEndPromise.resolve(); + } catch (error) { + this._downloadEndPromise.reject(error); + throw error; + } } private _changeEngineFinishDownload(engine: Engine) { + if (engine instanceof DownloadEngineMultiDownload) { + const _finishEnginesDownload = engine._finishEnginesDownload.bind(engine); + engine._finishEnginesDownload = async () => { + }; + this._closeFiles.push(_finishEnginesDownload); + return; + } + + if (!(engine instanceof BaseDownloadEngine)) { + return; + } + const options = engine._fileEngineOptions; const onFinishAsync = options.onFinishAsync; const onCloseAsync = options.onCloseAsync; @@ -153,12 +275,16 @@ export default class DownloadEngineMultiDownload engine.pause()); + this._activeEngines.forEach(engine => { + if ("pause" in engine) engine.pause(); + }); } public resume(): void { this._progressStatisticsBuilder.downloadStatus = DownloadStatus.Active; - this._activeEngines.forEach(engine => engine.resume()); + this._activeEngines.forEach(engine => { + if ("resume" in engine) engine.resume(); + }); } public async close() { @@ -170,23 +296,14 @@ export default class DownloadEngineMultiDownload engine.close()); + .map(engine => { + if ("close" in engine) { + return engine.close(); + } + return Promise.resolve(); + }); await Promise.all(closePromises); this.emit("closed"); } - - protected static _extractEngines(engines: Engine[]) { - return engines.map(engine => { - if (engine instanceof DownloadEngineMultiDownload) { - return engine.downloads; - } - return engine; - }) - .flat(); - } - - public static async fromEngines(engines: (Engine | Promise)[], options: DownloadEngineMultiDownloadOptions = {}) { - return new DownloadEngineMultiDownload(await Promise.all(engines), options); - } } diff --git a/src/download/download-engine/engine/download-engine-nodejs.ts b/src/download/download-engine/engine/download-engine-nodejs.ts index 5acdd66..a27f1e3 100644 --- a/src/download/download-engine/engine/download-engine-nodejs.ts +++ b/src/download/download-engine/engine/download-engine-nodejs.ts @@ -16,8 +16,12 @@ export const PROGRESS_FILE_EXTENSION = ".ipull"; type PathOptions = { directory: string } | { savePath: string }; export type DownloadEngineOptionsNodejs = PathOptions & BaseDownloadEngineOptions & { fileName?: string; - fetchStrategy?: "localFile" | "fetch"; + fetchStrategy?: "local" | "remote"; skipExisting?: boolean; + debounceWrite?: { + maxTime: number + maxSize: number + } }; export type DownloadEngineOptionsNodejsCustomFetch = DownloadEngineOptionsNodejs & { @@ -47,12 +51,16 @@ export default class DownloadEngineNodejs { if (this.options.skipExisting) return; - await this.options.writeStream.saveMedataAfterFile(progress); + await this.options.writeStream.saveMetadataAfterFile(progress); + }; + + this._engine.options.onPausedAsync = async () => { + await this.options.writeStream.ensureBytesSynced(); }; // Try to clone the file if it's a single part download this._engine.options.onStartedAsync = async () => { - if (this.options.skipExisting || this.options.fetchStrategy !== "localFile" || this.options.partURLs.length !== 1) return; + if (this.options.skipExisting || this.options.fetchStrategy !== "local" || this.options.partURLs.length !== 1) return; try { const {reflinkFile} = await import("@reflink/reflink"); @@ -130,7 +138,7 @@ export default class DownloadEngineNodejs(array: Value[], concurrencyCount: number, callback: (value: Value) => Promise): Promise { - return new Promise((resolve, reject) => { - let index = 0; - let activeCount = 0; - - function next() { - if (index === array.length && activeCount === 0) { - resolve(); - return; - } - - while (activeCount < concurrencyCount && index < array.length) { - activeCount++; - callback(array[index++]) - .then(() => { - activeCount--; - next(); - }, reject); - } - } - - next(); - }); -} diff --git a/src/download/download-engine/streams/download-engine-fetch-stream/base-download-engine-fetch-stream.ts b/src/download/download-engine/streams/download-engine-fetch-stream/base-download-engine-fetch-stream.ts index f0d290a..46f8593 100644 --- a/src/download/download-engine/streams/download-engine-fetch-stream/base-download-engine-fetch-stream.ts +++ b/src/download/download-engine/streams/download-engine-fetch-stream/base-download-engine-fetch-stream.ts @@ -6,10 +6,17 @@ import HttpError from "./errors/http-error.js"; import StatusCodeError from "./errors/status-code-error.js"; import sleep from "sleep-promise"; +export const STREAM_NOT_RESPONDING_TIMEOUT = 1000 * 3; + export const MIN_LENGTH_FOR_MORE_INFO_REQUEST = 1024 * 1024 * 3; // 3MB export type BaseDownloadEngineFetchStreamOptions = { retry?: retry.Options + retryFetchDownloadInfo?: retry.Options + /** + * Max wait for next data stream + */ + maxStreamWait?: number /** * If true, the engine will retry the request if the server returns a status code between 500 and 599 */ @@ -46,6 +53,7 @@ export type FetchSubState = { url: string, startChunk: number, endChunk: number, + lastChunkEndsFile: boolean, totalSize: number, chunkSize: number, rangeSupport?: boolean, @@ -57,14 +65,25 @@ export type BaseDownloadEngineFetchStreamEvents = { resumed: () => void aborted: () => void errorCountIncreased: (errorCount: number, error: Error) => void + retryingOn: (error: Error, attempt: number) => void + retryingOff: () => void + streamNotRespondingOn: () => void + streamNotRespondingOff: () => void }; export type WriteCallback = (data: Uint8Array[], position: number, index: number) => void; const DEFAULT_OPTIONS: BaseDownloadEngineFetchStreamOptions = { retryOnServerError: true, + maxStreamWait: 1000 * 15, retry: { - retries: 150, + retries: 50, + factor: 1.5, + minTimeout: 200, + maxTimeout: 5_000 + }, + retryFetchDownloadInfo: { + retries: 5, factor: 1.5, minTimeout: 200, maxTimeout: 5_000 @@ -73,8 +92,10 @@ const DEFAULT_OPTIONS: BaseDownloadEngineFetchStreamOptions = { }; export default abstract class BaseDownloadEngineFetchStream extends EventEmitter { - public readonly programType?: AvailablePrograms; + public readonly defaultProgramType?: AvailablePrograms; + public readonly availablePrograms: AvailablePrograms[] = ["chunks", "stream"]; public readonly abstract transferAction: string; + public readonly supportDynamicStreamLength: boolean = false; public readonly options: Partial = {}; public state: FetchSubState = null!; public paused?: Promise; @@ -133,14 +154,25 @@ export default abstract class BaseDownloadEngineFetchStream extends EventEmitter let throwErr: Error | null = null; const tryHeaders = "tryHeaders" in this.options && this.options.tryHeaders ? this.options.tryHeaders.slice() : []; + let retryingOn = false; const fetchDownloadInfoCallback = async (): Promise => { try { - return await this.fetchDownloadInfoWithoutRetry(url); + const response = await this.fetchDownloadInfoWithoutRetry(url); + if (retryingOn) { + retryingOn = false; + this.emit("retryingOff"); + } + return response; } catch (error: any) { + this.errorCount.value++; + this.emit("errorCountIncreased", this.errorCount.value, error); + if (error instanceof HttpError && !this.retryOnServerError(error)) { if ("tryHeaders" in this.options && tryHeaders.length) { this.options.headers = tryHeaders.shift(); + retryingOn = true; + this.emit("retryingOn", error, this.errorCount.value); await sleep(this.options.tryHeadersDelay ?? 0); return await fetchDownloadInfoCallback(); } @@ -149,10 +181,9 @@ export default abstract class BaseDownloadEngineFetchStream extends EventEmitter return null; } - this.errorCount.value++; - this.emit("errorCountIncreased", this.errorCount.value, error); - if (error instanceof StatusCodeError && error.retryAfter) { + retryingOn = true; + this.emit("retryingOn", error, this.errorCount.value); await sleep(error.retryAfter * 1000); return await fetchDownloadInfoCallback(); } @@ -161,8 +192,7 @@ export default abstract class BaseDownloadEngineFetchStream extends EventEmitter } }; - - const response = ("defaultFetchDownloadInfo" in this.options && this.options.defaultFetchDownloadInfo) || await retry(fetchDownloadInfoCallback, this.options.retry); + const response = ("defaultFetchDownloadInfo" in this.options && this.options.defaultFetchDownloadInfo) || await retry(fetchDownloadInfoCallback, this.options.retryFetchDownloadInfo); if (throwErr) { throw throwErr; } @@ -175,20 +205,29 @@ export default abstract class BaseDownloadEngineFetchStream extends EventEmitter public async fetchChunks(callback: WriteCallback) { let lastStartLocation = this.state.startChunk; let retryResolvers = retryAsyncStatementSimple(this.options.retry); + let retryingOn = false; // eslint-disable-next-line no-constant-condition while (true) { try { - return await this.fetchWithoutRetryChunks(callback); + return await this.fetchWithoutRetryChunks((...args) => { + if (retryingOn) { + retryingOn = false; + this.emit("retryingOff"); + } + callback(...args); + }); } catch (error: any) { if (error?.name === "AbortError") return; + this.errorCount.value++; + this.emit("errorCountIncreased", this.errorCount.value, error); + if (error instanceof HttpError && !this.retryOnServerError(error)) { throw error; } - this.errorCount.value++; - this.emit("errorCountIncreased", this.errorCount.value, error); - + retryingOn = true; + this.emit("retryingOn", error, this.errorCount.value); if (error instanceof StatusCodeError && error.retryAfter) { await sleep(error.retryAfter * 1000); continue; diff --git a/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-fetch.ts b/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-fetch.ts index 7d0b10f..8888480 100644 --- a/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-fetch.ts +++ b/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-fetch.ts @@ -1,15 +1,25 @@ -import BaseDownloadEngineFetchStream, {DownloadInfoResponse, FetchSubState, MIN_LENGTH_FOR_MORE_INFO_REQUEST, WriteCallback} from "./base-download-engine-fetch-stream.js"; +import BaseDownloadEngineFetchStream, { + DownloadInfoResponse, + FetchSubState, + MIN_LENGTH_FOR_MORE_INFO_REQUEST, + STREAM_NOT_RESPONDING_TIMEOUT, + WriteCallback +} from "./base-download-engine-fetch-stream.js"; import InvalidContentLengthError from "./errors/invalid-content-length-error.js"; import SmartChunkSplit from "./utils/smart-chunk-split.js"; import {parseContentDisposition} from "./utils/content-disposition.js"; import StatusCodeError from "./errors/status-code-error.js"; import {parseHttpContentRange} from "./utils/httpRange.js"; import {browserCheck} from "./utils/browserCheck.js"; +import {EmptyStreamTimeoutError} from "./errors/EmptyStreamTimeoutError.js"; +import prettyMilliseconds from "pretty-ms"; type GetNextChunk = () => Promise> | ReadableStreamReadResult; export default class DownloadEngineFetchStreamFetch extends BaseDownloadEngineFetchStream { private _fetchDownloadInfoWithHEAD = false; + private _activeController?: AbortController; public override transferAction = "Downloading"; + public override readonly supportDynamicStreamLength = true; withSubState(state: FetchSubState): this { const fetchStream = new DownloadEngineFetchStreamFetch(this.options); @@ -26,10 +36,22 @@ export default class DownloadEngineFetchStreamFetch extends BaseDownloadEngineFe headers.range = `bytes=${this._startSize}-${this._endSize - 1}`; } - const controller = new AbortController(); - const response = await fetch(this.appendToURL(this.state.url), { + if (!this._activeController?.signal.aborted) { + this._activeController?.abort(); + } + + let response: Response | null = null; + this._activeController = new AbortController(); + this.on("aborted", () => { + if (!response) { + this._activeController?.abort(); + } + }); + + + response = await fetch(this.appendToURL(this.state.url), { headers, - signal: controller.signal + signal: this._activeController.signal }); if (response.status < 200 || response.status >= 300) { @@ -42,10 +64,6 @@ export default class DownloadEngineFetchStreamFetch extends BaseDownloadEngineFe throw new InvalidContentLengthError(expectedContentLength, contentLength); } - this.on("aborted", () => { - controller.abort(); - }); - const reader = response.body!.getReader(); return await this.chunkGenerator(callback, () => reader.read()); } @@ -114,17 +132,58 @@ export default class DownloadEngineFetchStreamFetch extends BaseDownloadEngineFe // eslint-disable-next-line no-constant-condition while (true) { - const {done, value} = await getNextChunk(); + const chunkInfo = await this._wrapperStreamNotResponding(getNextChunk()); await this.paused; - if (done || this.aborted) break; + if (!chunkInfo || this.aborted || chunkInfo.done) break; - smartSplit.addChunk(value); + smartSplit.addChunk(chunkInfo.value); this.state.onProgress?.(smartSplit.savedLength); } - smartSplit.sendLeftovers(); + smartSplit.closeAndSendLeftoversIfLengthIsUnknown(); + } + + protected _wrapperStreamNotResponding(promise: Promise | T): Promise | T | void { + if (!(promise instanceof Promise)) { + return promise; + } + + return new Promise((resolve, reject) => { + let streamNotRespondedInTime = false; + let timeoutMaxStreamWaitThrows = false; + const timeoutNotResponding = setTimeout(() => { + streamNotRespondedInTime = true; + this.emit("streamNotRespondingOn"); + }, STREAM_NOT_RESPONDING_TIMEOUT); + + const timeoutMaxStreamWait = setTimeout(() => { + timeoutMaxStreamWaitThrows = true; + reject(new EmptyStreamTimeoutError(`Stream timeout after ${prettyMilliseconds(this.options.maxStreamWait!)}`)); + this._activeController?.abort(); + }, this.options.maxStreamWait); + + this.addListener("aborted", resolve); + + promise + .then(resolve) + .catch(error => { + if (timeoutMaxStreamWaitThrows || this.aborted) { + return; + } + reject(error); + }) + .finally(() => { + clearTimeout(timeoutNotResponding); + clearTimeout(timeoutMaxStreamWait); + if (streamNotRespondedInTime) { + this.emit("streamNotRespondingOff"); + } + this.removeListener("aborted", resolve); + }); + }); } + protected static convertHeadersToRecord(headers: Headers): { [key: string]: string } { const headerObj: { [key: string]: string } = {}; headers.forEach((value, key) => { @@ -132,4 +191,5 @@ export default class DownloadEngineFetchStreamFetch extends BaseDownloadEngineFe }); return headerObj; } + } diff --git a/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-xhr.ts b/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-xhr.ts index 370ea7c..322006e 100644 --- a/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-xhr.ts +++ b/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-xhr.ts @@ -1,4 +1,10 @@ -import BaseDownloadEngineFetchStream, {DownloadInfoResponse, FetchSubState, MIN_LENGTH_FOR_MORE_INFO_REQUEST, WriteCallback} from "./base-download-engine-fetch-stream.js"; +import BaseDownloadEngineFetchStream, { + DownloadInfoResponse, + FetchSubState, + MIN_LENGTH_FOR_MORE_INFO_REQUEST, + STREAM_NOT_RESPONDING_TIMEOUT, + WriteCallback +} from "./base-download-engine-fetch-stream.js"; import EmptyResponseError from "./errors/empty-response-error.js"; import StatusCodeError from "./errors/status-code-error.js"; import XhrError from "./errors/xhr-error.js"; @@ -7,11 +13,15 @@ import retry from "async-retry"; import {AvailablePrograms} from "../../download-file/download-programs/switch-program.js"; import {parseContentDisposition} from "./utils/content-disposition.js"; import {parseHttpContentRange} from "./utils/httpRange.js"; +import prettyMilliseconds from "pretty-ms"; +import {EmptyStreamTimeoutError} from "./errors/EmptyStreamTimeoutError.js"; export default class DownloadEngineFetchStreamXhr extends BaseDownloadEngineFetchStream { private _fetchDownloadInfoWithHEAD = true; - public override readonly programType: AvailablePrograms = "chunks"; + public override readonly defaultProgramType: AvailablePrograms = "chunks"; + public override readonly availablePrograms: AvailablePrograms[] = ["chunks"]; + public override transferAction = "Downloading"; withSubState(state: FetchSubState): this { @@ -43,7 +53,41 @@ export default class DownloadEngineFetchStreamXhr extends BaseDownloadEngineFetc xhr.setRequestHeader(key, value); } + let lastNotRespondingTimeoutIndex: any; + let lastMaxStreamWaitTimeoutIndex: any; + let streamNotResponding = false; + const clearStreamTimeout = () => { + if (streamNotResponding) { + this.emit("streamNotRespondingOff"); + streamNotResponding = false; + } + + if (lastNotRespondingTimeoutIndex) { + clearTimeout(lastNotRespondingTimeoutIndex); + } + + if (lastMaxStreamWaitTimeoutIndex) { + clearTimeout(lastMaxStreamWaitTimeoutIndex); + } + }; + + const createStreamTimeout = () => { + clearStreamTimeout(); + + lastNotRespondingTimeoutIndex = setTimeout(() => { + streamNotResponding = true; + this.emit("streamNotRespondingOn"); + }, STREAM_NOT_RESPONDING_TIMEOUT); + + lastMaxStreamWaitTimeoutIndex = setTimeout(() => { + reject(new EmptyStreamTimeoutError(`Stream timeout after ${prettyMilliseconds(this.options.maxStreamWait!)}`)); + xhr.abort(); + }, this.options.maxStreamWait); + }; + + xhr.onload = () => { + clearStreamTimeout(); const contentLength = parseInt(xhr.getResponseHeader("content-length")!); if (this.state.rangeSupport && contentLength !== end - start) { @@ -51,6 +95,10 @@ export default class DownloadEngineFetchStreamXhr extends BaseDownloadEngineFetc } if (xhr.status >= 200 && xhr.status < 300) { + if (xhr.response.length != contentLength) { + throw new InvalidContentLengthError(contentLength, xhr.response.length); + } + const arrayBuffer = xhr.response; if (arrayBuffer) { resolve(new Uint8Array(arrayBuffer)); @@ -63,18 +111,22 @@ export default class DownloadEngineFetchStreamXhr extends BaseDownloadEngineFetc }; xhr.onerror = () => { + clearStreamTimeout(); reject(new XhrError(`Failed to fetch ${url}`)); }; xhr.onprogress = (event) => { + createStreamTimeout(); if (event.lengthComputable) { onProgress?.(event.loaded); } }; xhr.send(); + createStreamTimeout(); this.on("aborted", () => { + clearStreamTimeout(); xhr.abort(); }); }); diff --git a/src/download/download-engine/streams/download-engine-fetch-stream/errors/EmptyStreamTimeoutError.ts b/src/download/download-engine/streams/download-engine-fetch-stream/errors/EmptyStreamTimeoutError.ts new file mode 100644 index 0000000..0d53a14 --- /dev/null +++ b/src/download/download-engine/streams/download-engine-fetch-stream/errors/EmptyStreamTimeoutError.ts @@ -0,0 +1,3 @@ +import FetchStreamError from "./fetch-stream-error.js"; + +export class EmptyStreamTimeoutError extends FetchStreamError {} diff --git a/src/download/download-engine/streams/download-engine-fetch-stream/utils/smart-chunk-split.ts b/src/download/download-engine/streams/download-engine-fetch-stream/utils/smart-chunk-split.ts index 2f9a000..32e14b8 100644 --- a/src/download/download-engine/streams/download-engine-fetch-stream/utils/smart-chunk-split.ts +++ b/src/download/download-engine/streams/download-engine-fetch-stream/utils/smart-chunk-split.ts @@ -3,19 +3,29 @@ import {WriteCallback} from "../base-download-engine-fetch-stream.js"; export type SmartChunkSplitOptions = { chunkSize: number; startChunk: number; + endChunk: number; + lastChunkEndsFile: boolean; + totalSize: number }; export default class SmartChunkSplit { private readonly _callback: WriteCallback; private readonly _options: SmartChunkSplitOptions; + private readonly _lastChunkSize: number; private _bytesWriteLocation: number; - private _bytesLeftovers: number = 0; private _chunks: Uint8Array[] = []; + private _closed = false; public constructor(_callback: WriteCallback, _options: SmartChunkSplitOptions) { this._options = _options; this._callback = _callback; this._bytesWriteLocation = _options.startChunk * _options.chunkSize; + this._lastChunkSize = _options.lastChunkEndsFile ? + this.calcLastChunkSize() : this._options.chunkSize; + } + + public calcLastChunkSize() { + return this._options.totalSize - Math.max(this._options.endChunk - 1, 0) * this._options.chunkSize; } public addChunk(data: Uint8Array) { @@ -24,32 +34,45 @@ export default class SmartChunkSplit { } public get savedLength() { - return this._bytesLeftovers + this._chunks.reduce((acc, chunk) => acc + chunk.length, 0); + return this._chunks.reduce((acc, chunk) => acc + chunk.length, 0); } - public sendLeftovers() { - if (this.savedLength > 0) { + closeAndSendLeftoversIfLengthIsUnknown() { + if (this._chunks.length > 0 && this._options.endChunk === Infinity) { this._callback(this._chunks, this._bytesWriteLocation, this._options.startChunk++); } + this._closed = true; } private _sendChunk() { - while (this.savedLength >= this._options.chunkSize) { - if (this._chunks.length === 0) { - this._callback([], this._bytesWriteLocation, this._options.startChunk++); - this._bytesWriteLocation += this._options.chunkSize; - this._bytesLeftovers -= this._options.chunkSize; - } + if (this._closed) return; + + const checkThreshold = () => + (this._options.endChunk - this._options.startChunk === 1 ? + this._lastChunkSize : this._options.chunkSize); - let sendLength = this._bytesLeftovers; + let calcChunkThreshold = 0; + while (this.savedLength >= (calcChunkThreshold = checkThreshold())) { + let sendLength = 0; for (let i = 0; i < this._chunks.length; i++) { - sendLength += this._chunks[i].byteLength; - if (sendLength >= this._options.chunkSize) { + const currentChunk = this._chunks[i]; + sendLength += currentChunk.length; + if (sendLength >= calcChunkThreshold) { const sendChunks = this._chunks.splice(0, i + 1); + const diffLength = sendLength - calcChunkThreshold; + + if (diffLength > 0) { + const lastChunkEnd = currentChunk.length - diffLength; + const lastChunk = currentChunk.subarray(0, lastChunkEnd); + + sendChunks.pop(); + sendChunks.push(lastChunk); + + this._chunks.unshift(currentChunk.subarray(lastChunkEnd)); + } this._callback(sendChunks, this._bytesWriteLocation, this._options.startChunk++); - this._bytesWriteLocation += sendLength - this._bytesLeftovers; - this._bytesLeftovers = sendLength - this._options.chunkSize; + this._bytesWriteLocation += calcChunkThreshold; break; } } diff --git a/src/download/download-engine/streams/download-engine-fetch-stream/utils/stream-response.ts b/src/download/download-engine/streams/download-engine-fetch-stream/utils/stream-response.ts index b43f587..3ea036b 100644 --- a/src/download/download-engine/streams/download-engine-fetch-stream/utils/stream-response.ts +++ b/src/download/download-engine/streams/download-engine-fetch-stream/utils/stream-response.ts @@ -20,7 +20,7 @@ export default async function streamResponse(stream: IStreamResponse, downloadEn }); stream.on("close", () => { - smartSplit.sendLeftovers(); + smartSplit.closeAndSendLeftoversIfLengthIsUnknown(); resolve(); }); diff --git a/src/download/download-engine/streams/download-engine-write-stream/base-download-engine-write-stream.ts b/src/download/download-engine/streams/download-engine-write-stream/base-download-engine-write-stream.ts index cbacdcc..ddee247 100644 --- a/src/download/download-engine/streams/download-engine-write-stream/base-download-engine-write-stream.ts +++ b/src/download/download-engine/streams/download-engine-write-stream/base-download-engine-write-stream.ts @@ -1,5 +1,5 @@ export default abstract class BaseDownloadEngineWriteStream { - abstract write(cursor: number, buffer: Uint8Array): Promise | void; + abstract write(cursor: number, buffers: Uint8Array[]): Promise | void; close(): void | Promise { } diff --git a/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-browser.ts b/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-browser.ts index daa7f9e..8099ac0 100644 --- a/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-browser.ts +++ b/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-browser.ts @@ -4,12 +4,12 @@ import BaseDownloadEngineWriteStream from "./base-download-engine-write-stream.j import WriterIsClosedError from "./errors/writer-is-closed-error.js"; import WriterNotDefineError from "./errors/writer-not-define-error.js"; -type DownloadEngineWriteStreamOptionsBrowser = { +export type DownloadEngineWriteStreamOptionsBrowser = { retry?: retry.Options file?: DownloadFile }; -export type DownloadEngineWriteStreamBrowserWriter = (cursor: number, buffer: Uint8Array, options: DownloadEngineWriteStreamOptionsBrowser) => Promise | void; +export type DownloadEngineWriteStreamBrowserWriter = (cursor: number, buffers: Uint8Array[], options: DownloadEngineWriteStreamOptionsBrowser) => Promise | void; export default class DownloadEngineWriteStreamBrowser extends BaseDownloadEngineWriteStream { protected readonly _writer?: DownloadEngineWriteStreamBrowserWriter; @@ -44,18 +44,26 @@ export default class DownloadEngineWriteStreamBrowser extends BaseDownloadEngine return this._memory = newMemory; } - public write(cursor: number, buffer: Uint8Array) { + public write(cursor: number, buffers: Uint8Array[]) { if (this.writerClosed) { throw new WriterIsClosedError(); } if (!this._writer) { - this._ensureBuffer(cursor + buffer.byteLength) - .set(buffer, cursor); - this._bytesWritten += buffer.byteLength; + const totalLength = buffers.reduce((sum, buffer) => sum + buffer.length, 0); + const bigBuffer = this._ensureBuffer(cursor + totalLength); + let writeLocation = cursor; + + for (const buffer of buffers) { + bigBuffer.set(buffer, writeLocation); + writeLocation += buffer.length; + } + + this._bytesWritten += totalLength; return; } - return this._writer(cursor, buffer, this.options); + + return this._writer(cursor, buffers, this.options); } public get result() { diff --git a/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-nodejs.ts b/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-nodejs.ts index ebf6032..12bfa73 100644 --- a/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-nodejs.ts +++ b/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-nodejs.ts @@ -4,27 +4,71 @@ import retry from "async-retry"; import {withLock} from "lifecycle-utils"; import BaseDownloadEngineWriteStream from "./base-download-engine-write-stream.js"; import WriterIsClosedError from "./errors/writer-is-closed-error.js"; +import {BytesWriteDebounce} from "./utils/BytesWriteDebounce.js"; export type DownloadEngineWriteStreamOptionsNodeJS = { retry?: retry.Options mode: string; + debounceWrite?: { + maxTime?: number + maxSize?: number + } }; -const DEFAULT_OPTIONS: DownloadEngineWriteStreamOptionsNodeJS = { - mode: "r+" -}; +const DEFAULT_OPTIONS = { + mode: "r+", + debounceWrite: { + maxTime: 1000 * 5, // 5 seconds + maxSize: 1024 * 1024 * 2 // 2 MB + } +} satisfies DownloadEngineWriteStreamOptionsNodeJS; +const MAX_AUTO_DEBOUNCE_SIZE = 1024 * 1024 * 100; // 100 MB +const AUTO_DEBOUNCE_SIZE_PERCENT = 0.05; +const MAX_META_SIZE = 10485760; // 10 MB const NOT_ENOUGH_SPACE_ERROR_CODE = "ENOSPC"; export default class DownloadEngineWriteStreamNodejs extends BaseDownloadEngineWriteStream { private _fd: FileHandle | null = null; private _fileWriteFinished = false; + private _writeDebounce: BytesWriteDebounce; + private _fileSize = 0; + public readonly options: DownloadEngineWriteStreamOptionsNodeJS; - public fileSize = 0; + public autoDebounceMaxSize = false; constructor(public path: string, public finalPath: string, options: Partial = {}) { super(); - this.options = {...DEFAULT_OPTIONS, ...options}; + + this.autoDebounceMaxSize = !options.debounceWrite?.maxSize; + const optionsWithDefaults = this.options = { + ...DEFAULT_OPTIONS, + ...options, + debounceWrite: { + ...DEFAULT_OPTIONS.debounceWrite, + ...options.debounceWrite + } + }; + + this._writeDebounce = new BytesWriteDebounce({ + ...optionsWithDefaults.debounceWrite, + writev: (cursor, buffers) => this._writeWithoutDebounce(cursor, buffers) + }); + } + + public get fileSize() { + return this._fileSize; + } + + public set fileSize(value) { + this._fileSize = value; + + if (this.autoDebounceMaxSize) { + this.options.debounceWrite!.maxSize = Math.max( + Math.min(value * AUTO_DEBOUNCE_SIZE_PERCENT, MAX_AUTO_DEBOUNCE_SIZE), + DEFAULT_OPTIONS.debounceWrite.maxSize + ); + } } private async _ensureFileOpen() { @@ -40,12 +84,16 @@ export default class DownloadEngineWriteStreamNodejs extends BaseDownloadEngineW }); } - async write(cursor: number, buffer: Uint8Array) { + async write(cursor: number, buffers: Uint8Array[]) { + await this._writeDebounce.addChunk(cursor, buffers); + } + + async _writeWithoutDebounce(cursor: number, buffers: Uint8Array[]) { let throwError: Error | false = false; await retry(async () => { try { - return await this._writeWithoutRetry(cursor, buffer); + return await this._writeWithoutRetry(cursor, buffers); } catch (error: any) { if (error?.code === NOT_ENOUGH_SPACE_ERROR_CODE) { throwError = error; @@ -60,7 +108,12 @@ export default class DownloadEngineWriteStreamNodejs extends BaseDownloadEngineW } } - async ftruncate(size = this.fileSize) { + async ensureBytesSynced() { + await this._writeDebounce.writeAll(); + } + + async ftruncate(size = this._fileSize) { + await this.ensureBytesSynced(); this._fileWriteFinished = true; await retry(async () => { const fd = await this._ensureFileOpen(); @@ -68,7 +121,7 @@ export default class DownloadEngineWriteStreamNodejs extends BaseDownloadEngineW }, this.options.retry); } - async saveMedataAfterFile(data: any) { + async saveMetadataAfterFile(data: any) { if (this._fileWriteFinished) { throw new WriterIsClosedError(); } @@ -78,7 +131,7 @@ export default class DownloadEngineWriteStreamNodejs extends BaseDownloadEngineW const encoder = new TextEncoder(); const uint8Array = encoder.encode(jsonString); - await this.write(this.fileSize, uint8Array); + await this.write(this._fileSize, [uint8Array]); } async loadMetadataAfterFileWithoutRetry() { @@ -89,13 +142,16 @@ export default class DownloadEngineWriteStreamNodejs extends BaseDownloadEngineW const fd = await this._ensureFileOpen(); try { const state = await fd.stat(); - const metadataSize = state.size - this.fileSize; - if (metadataSize <= 0) { + const metadataSize = state.size - this._fileSize; + if (metadataSize <= 0 || metadataSize >= MAX_META_SIZE) { + if (this._fileSize > 0 && state.size > this._fileSize) { + await this.ftruncate(); + } return; } const metadataBuffer = Buffer.alloc(metadataSize); - await fd.read(metadataBuffer, 0, metadataSize, this.fileSize); + await fd.read(metadataBuffer, 0, metadataSize, this._fileSize); const decoder = new TextDecoder(); const metadataString = decoder.decode(metadataBuffer); @@ -108,10 +164,12 @@ export default class DownloadEngineWriteStreamNodejs extends BaseDownloadEngineW } } - private async _writeWithoutRetry(cursor: number, buffer: Uint8Array) { - const fd = await this._ensureFileOpen(); - const {bytesWritten} = await fd.write(buffer, 0, buffer.length, cursor); - return bytesWritten; + private async _writeWithoutRetry(cursor: number, buffers: Uint8Array[]) { + return await withLock(this, "lockWriteOperation", async () => { + const fd = await this._ensureFileOpen(); + const {bytesWritten} = await fd.writev(buffers, cursor); + return bytesWritten; + }); } override async close() { diff --git a/src/download/download-engine/streams/download-engine-write-stream/utils/BytesWriteDebounce.ts b/src/download/download-engine/streams/download-engine-write-stream/utils/BytesWriteDebounce.ts new file mode 100644 index 0000000..805dc36 --- /dev/null +++ b/src/download/download-engine/streams/download-engine-write-stream/utils/BytesWriteDebounce.ts @@ -0,0 +1,96 @@ +import sleep from "sleep-promise"; + +export type BytesWriteDebounceOptions = { + maxTime: number; + maxSize: number; + writev: (index: number, buffers: Uint8Array[]) => Promise; +}; + +export class BytesWriteDebounce { + private _writeChunks: { + index: number; + buffer: Uint8Array; + }[] = []; + private _lastWriteTime = Date.now(); + private _totalSizeOfChunks = 0; + private _checkWriteInterval = false; + + constructor(private _options: BytesWriteDebounceOptions) { + + } + + async addChunk(index: number, buffers: Uint8Array[]) { + let writeIndex = index; + for (const buffer of buffers) { + this._writeChunks.push({index: writeIndex, buffer}); + this._totalSizeOfChunks += buffer.length; + writeIndex += buffer.length; + } + + await this._writeIfNeeded(); + this.checkIfWriteNeededInterval(); + } + + private async _writeIfNeeded() { + if (this._totalSizeOfChunks >= this._options.maxSize || Date.now() - this._lastWriteTime >= this._options.maxTime) { + await this.writeAll(); + } + } + + private async checkIfWriteNeededInterval() { + if (this._checkWriteInterval) { + return; + } + this._checkWriteInterval = true; + + while (this._writeChunks.length > 0) { + await this._writeIfNeeded(); + const timeUntilMaxLimitAfterWrite = this._options.maxTime - (Date.now() - this._lastWriteTime); + await sleep(Math.max(timeUntilMaxLimitAfterWrite, 0)); + } + + this._checkWriteInterval = false; + } + + writeAll() { + if (this._writeChunks.length === 0) { + return; + } + + this._writeChunks = this._writeChunks.sort((a, b) => a.index - b.index); + const firstWrite = this._writeChunks[0]; + + let writeIndex = firstWrite.index; + let buffers: Uint8Array[] = [firstWrite.buffer]; + let buffersTotalLength = firstWrite.buffer.length; + + const writePromises: Promise[] = []; + + for (let i = 1; i < this._writeChunks.length; i++) { + const nextWriteLocation = writeIndex + buffersTotalLength; + const currentWrite = this._writeChunks[i]; + + if (currentWrite.index < nextWriteLocation) { // overlapping, prefer the last buffer (newer data) + const lastBuffer = buffers.pop()!; + buffers.push(currentWrite.buffer); + buffersTotalLength += currentWrite.buffer.length - lastBuffer.length; + } else if (nextWriteLocation === currentWrite.index) { + buffers.push(currentWrite.buffer); + buffersTotalLength += currentWrite.buffer.length; + } else { + writePromises.push(this._options.writev(writeIndex, buffers)); + + writeIndex = currentWrite.index; + buffers = [currentWrite.buffer]; + buffersTotalLength = currentWrite.buffer.length; + } + } + + writePromises.push(this._options.writev(writeIndex, buffers)); + + this._writeChunks = []; + this._totalSizeOfChunks = 0; + this._lastWriteTime = Date.now(); + return Promise.all(writePromises); + } +} diff --git a/src/download/download-engine/types.ts b/src/download/download-engine/types.ts index f1a96e0..c6ae82c 100644 --- a/src/download/download-engine/types.ts +++ b/src/download/download-engine/types.ts @@ -11,6 +11,7 @@ export enum ChunkStatus { } export type SaveProgressInfo = { + downloadId: string, part: number, chunks: ChunkStatus[], chunkSize: number, diff --git a/src/download/download-engine/utils/concurrency.ts b/src/download/download-engine/utils/concurrency.ts new file mode 100644 index 0000000..2da18d0 --- /dev/null +++ b/src/download/download-engine/utils/concurrency.ts @@ -0,0 +1,30 @@ +import {promiseWithResolvers} from "./promiseWithResolvers.js"; + +export function concurrency(array: Value[], concurrencyCount: number, callback: (value: Value) => Promise) { + const {resolve, reject, promise} = promiseWithResolvers(); + let index = 0; + let activeCount = 0; + + function reload() { + if (index === array.length && activeCount === 0) { + resolve(); + return; + } + + while (activeCount < concurrencyCount && index < array.length) { + activeCount++; + callback(array[index++]) + .then(() => { + activeCount--; + reload(); + }, reject); + } + } + + reload(); + + return { + promise, + reload + }; +} diff --git a/src/download/download-engine/utils/promiseWithResolvers.ts b/src/download/download-engine/utils/promiseWithResolvers.ts new file mode 100644 index 0000000..a1fb4a5 --- /dev/null +++ b/src/download/download-engine/utils/promiseWithResolvers.ts @@ -0,0 +1,9 @@ +export function promiseWithResolvers() { + let resolve: (value: T) => void; + let reject: (reason?: any) => void; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + return {promise, resolve: resolve!, reject: reject!}; +} diff --git a/src/download/node-download.ts b/src/download/node-download.ts index 69647fe..5b5a1f7 100644 --- a/src/download/node-download.ts +++ b/src/download/node-download.ts @@ -1,35 +1,35 @@ import DownloadEngineNodejs, {DownloadEngineOptionsNodejs} from "./download-engine/engine/download-engine-nodejs.js"; import BaseDownloadEngine from "./download-engine/engine/base-download-engine.js"; import DownloadEngineMultiDownload, {DownloadEngineMultiDownloadOptions} from "./download-engine/engine/download-engine-multi-download.js"; -import CliAnimationWrapper, {CliProgressDownloadEngineOptions} from "./transfer-visualize/transfer-cli/cli-animation-wrapper.js"; -import {CLI_LEVEL} from "./transfer-visualize/transfer-cli/transfer-cli.js"; -import {NoDownloadEngineProvidedError} from "./download-engine/engine/error/no-download-engine-provided-error.js"; +import {CliProgressDownloadEngineOptions, globalCLI} from "./transfer-visualize/transfer-cli/GlobalCLI.js"; +import {DownloadEngineRemote} from "./download-engine/engine/DownloadEngineRemote.js"; const DEFAULT_PARALLEL_STREAMS_FOR_NODEJS = 3; -export type DownloadFileOptions = DownloadEngineOptionsNodejs & CliProgressDownloadEngineOptions & { - /** @deprecated use partURLs instead */ - partsURL?: string[]; -}; +export type DownloadFileOptions = DownloadEngineOptionsNodejs & CliProgressDownloadEngineOptions; /** * Download one file with CLI progress */ export async function downloadFile(options: DownloadFileOptions) { - // TODO: Remove in the next major version - if (!("url" in options) && options.partsURL) { - options.partURLs ??= options.partsURL; - } - - options.parallelStreams ??= DEFAULT_PARALLEL_STREAMS_FOR_NODEJS; const downloader = DownloadEngineNodejs.createFromOptions(options); - const wrapper = new CliAnimationWrapper(downloader, options); + globalCLI.addDownload(downloader, options); - await wrapper.attachAnimation(); return await downloader; } +/** + * Stream events for a download from remote session, doing so by calling `emitRemoteProgress` with the progress info. + * - Supports CLI progress. + */ +export function downloadFileRemote(options?: CliProgressDownloadEngineOptions) { + const downloader = new DownloadEngineRemote(); + globalCLI.addDownload(downloader, options); + + return downloader; +} + export type DownloadSequenceOptions = CliProgressDownloadEngineOptions & DownloadEngineMultiDownloadOptions & { fetchStrategy?: "localFile" | "fetch"; }; @@ -46,14 +46,9 @@ export async function downloadSequence(options?: DownloadSequenceOptions | Downl downloadOptions = options; } - if (downloads.length === 0) { - throw new NoDownloadEngineProvidedError(); - } - - downloadOptions.cliLevel = CLI_LEVEL.HIGH; - const downloader = DownloadEngineMultiDownload.fromEngines(downloads, downloadOptions); - const wrapper = new CliAnimationWrapper(downloader, downloadOptions); + const downloader = new DownloadEngineMultiDownload(downloadOptions); + globalCLI.addDownload(downloader, downloadOptions); + await downloader.addDownload(...downloads); - await wrapper.attachAnimation(); - return await downloader; + return downloader; } diff --git a/src/download/transfer-visualize/progress-statistics-builder.ts b/src/download/transfer-visualize/progress-statistics-builder.ts index db10ada..978fb2b 100644 --- a/src/download/transfer-visualize/progress-statistics-builder.ts +++ b/src/download/transfer-visualize/progress-statistics-builder.ts @@ -5,6 +5,7 @@ import {createFormattedStatus, FormattedStatus} from "./format-transfer-status.j import DownloadEngineFile from "../download-engine/download-file/download-engine-file.js"; import ProgressStatusFile, {DownloadStatus, ProgressStatus} from "../download-engine/download-file/progress-status-file.js"; import DownloadEngineMultiDownload from "../download-engine/engine/download-engine-multi-download.js"; +import {DownloadEngineRemote} from "../download-engine/engine/DownloadEngineRemote.js"; export type ProgressStatusWithIndex = FormattedStatus & { index: number, @@ -14,12 +15,13 @@ interface CliProgressBuilderEvents { progress: (progress: ProgressStatusWithIndex) => void; } -export type AnyEngine = DownloadEngineFile | BaseDownloadEngine | DownloadEngineMultiDownload; +export type AnyEngine = DownloadEngineFile | BaseDownloadEngine | DownloadEngineMultiDownload | DownloadEngineRemote; export default class ProgressStatisticsBuilder extends EventEmitter { - private _engines: AnyEngine[] = []; + private _engines = new Set(); private _activeTransfers: { [index: number]: number } = {}; private _totalBytes = 0; private _transferredBytes = 0; + private _latestEngine: AnyEngine | null = null; /** * @internal */ @@ -27,8 +29,38 @@ export default class ProgressStatisticsBuilder extends EventEmitter { + const retrying = Number(data.retrying); + this._retrying += retrying - lastRetrying; + lastRetrying = retrying; + + this._retryingTotalAttempts += data.retryingTotalAttempts - lastRetryingTotalAttempts; + lastRetryingTotalAttempts = data.retryingTotalAttempts; + + this._streamsNotResponding += data.streamsNotResponding - lastStreamsNotResponding; + lastStreamsNotResponding = data.streamsNotResponding; + this._sendProgress(data, index, downloadPartStart); }); @@ -71,10 +114,20 @@ export default class ProgressStatisticsBuilder extends EventEmitter 0, + retryingTotalAttempts: this._retryingTotalAttempts, + streamsNotResponding: this._streamsNotResponding }), index }; - - this.emit("progress", this._lastStatus); } static oneStatistics(engine: DownloadEngineFile) { diff --git a/src/download/transfer-visualize/transfer-cli/GlobalCLI.ts b/src/download/transfer-visualize/transfer-cli/GlobalCLI.ts new file mode 100644 index 0000000..e14e928 --- /dev/null +++ b/src/download/transfer-visualize/transfer-cli/GlobalCLI.ts @@ -0,0 +1,162 @@ +import DownloadEngineMultiDownload, {DownloadEngineMultiAllowedEngines} from "../../download-engine/engine/download-engine-multi-download.js"; +import TransferCli, {TransferCliOptions} from "./transfer-cli.js"; +import {BaseMultiProgressBar} from "./multiProgressBars/BaseMultiProgressBar.js"; +import switchCliProgressStyle, {AvailableCLIProgressStyle} from "./progress-bars/switch-cli-progress-style.js"; +import {CliFormattedStatus} from "./progress-bars/base-transfer-cli-progress-bar.js"; +import cliSpinners from "cli-spinners"; +import {DownloadStatus} from "../../download-engine/download-file/progress-status-file.js"; +import BaseDownloadEngine from "../../download-engine/engine/base-download-engine.js"; +import {DownloadEngineRemote} from "../../download-engine/engine/DownloadEngineRemote.js"; + +type AllowedDownloadEngine = DownloadEngineMultiDownload | BaseDownloadEngine | DownloadEngineRemote; + +const DEFAULT_CLI_STYLE: AvailableCLIProgressStyle = "auto"; + +export type CliProgressDownloadEngineOptions = { + truncateName?: boolean | number; + cliProgress?: boolean; + maxViewDownloads?: number; + createMultiProgressBar?: typeof BaseMultiProgressBar, + cliStyle?: AvailableCLIProgressStyle | ((status: CliFormattedStatus) => string) + cliName?: string; + loadingAnimation?: cliSpinners.SpinnerName; +}; + +class GlobalCLI { + private _multiDownloadEngine = this._createMultiDownloadEngine(); + private _eventsRegistered = new Set(); + private _transferCLI = GlobalCLI._createOptions({}); + private _cliActive = false; + private _downloadOptions = new WeakMap(); + + constructor() { + this._registerCLIEvents(); + } + + async addDownload(engine: AllowedDownloadEngine | Promise, cliOptions: CliProgressDownloadEngineOptions = {}) { + if (!this._cliActive && cliOptions.cliProgress) { + this._transferCLI = GlobalCLI._createOptions(cliOptions); + } + + if (engine instanceof Promise) { + engine.then((engine) => this._downloadOptions.set(engine, cliOptions)); + } else { + this._downloadOptions.set(engine, cliOptions); + } + + await this._multiDownloadEngine.addDownload(engine); + this._multiDownloadEngine.download(); + } + + private _createMultiDownloadEngine() { + return new DownloadEngineMultiDownload({ + unpackInnerMultiDownloadsStatues: true, + finalizeDownloadAfterAllSettled: false, + naturalDownloadStart: true, + parallelDownloads: Number.MAX_VALUE + }); + } + + private _registerCLIEvents() { + const isDownloadActive = (parentEngine: DownloadEngineMultiDownload = this._multiDownloadEngine) => { + if (parentEngine.loadingDownloads > 0) { + return true; + } + + for (const engine of parentEngine.activeDownloads) { + if (engine instanceof DownloadEngineMultiDownload) { + if (isDownloadActive(engine)) { + return true; + } + } + + if (engine.status.downloadStatus === DownloadStatus.Active || parentEngine.status.downloadStatus === DownloadStatus.Active && [DownloadStatus.Loading, DownloadStatus.NotStarted].includes(engine.status.downloadStatus)) { + return true; + } + } + + return false; + }; + + const checkPauseCLI = () => { + if (this._cliActive && !isDownloadActive()) { + this._transferCLI.stop(); + this._cliActive = false; + } + }; + + const checkCloseCLI = (engine: DownloadEngineMultiAllowedEngines) => { + this._eventsRegistered.delete(engine); + checkPauseCLI(); + }; + + const checkResumeCLI = (engine: DownloadEngineMultiAllowedEngines) => { + if (engine.status.downloadStatus === DownloadStatus.Active) { + this._transferCLI.start(); + this._cliActive = true; + } + }; + + this._multiDownloadEngine.on("finished", () => { + this._multiDownloadEngine = this._createMultiDownloadEngine(); + this._eventsRegistered = new Set(); + }); + + const eventsRegistered = this._eventsRegistered; + this._multiDownloadEngine.on("childDownloadStarted", function registerEngineStatus(engine) { + if (eventsRegistered.has(engine)) return; + eventsRegistered.add(engine); + + checkResumeCLI(engine); + engine.on("paused", checkPauseCLI); + engine.on("closed", () => checkCloseCLI(engine)); + engine.on("resumed", () => checkResumeCLI(engine)); + engine.on("start", () => checkResumeCLI(engine)); + + if (engine instanceof DownloadEngineMultiDownload) { + engine.on("childDownloadStarted", registerEngineStatus); + } + }); + + const getCLIEngines = (multiEngine: DownloadEngineMultiDownload) => { + const enginesToShow: AllowedDownloadEngine[] = []; + for (const engine of multiEngine.activeDownloads) { + const isShowEngine = this._downloadOptions.get(engine)?.cliProgress; + if (engine instanceof DownloadEngineMultiDownload) { + if (isShowEngine) { + enginesToShow.push(...engine._flatEngines); + continue; + } + enginesToShow.push(...getCLIEngines(engine)); + } else if (isShowEngine) { + enginesToShow.push(engine); + } + } + + return enginesToShow.filter((engine, index, self) => self.indexOf(engine) === index); + }; + + this._multiDownloadEngine.on("progress", (progress) => { + if (!this._cliActive) return; + const statues = getCLIEngines(this._multiDownloadEngine) + .map(x => x.status); + this._transferCLI.updateStatues(statues, progress, this._multiDownloadEngine.loadingDownloads); + }); + } + + private static _createOptions(options: CliProgressDownloadEngineOptions) { + const cliOptions: Partial = {...options}; + cliOptions.createProgressBar ??= typeof options.cliStyle === "function" ? + { + createStatusLine: options.cliStyle, + multiProgressBar: options.createMultiProgressBar ?? BaseMultiProgressBar + } : + switchCliProgressStyle(options.cliStyle ?? DEFAULT_CLI_STYLE, { + truncateName: options.truncateName, + loadingSpinner: options.loadingAnimation + }); + return new TransferCli(cliOptions); + } +} + +export const globalCLI = new GlobalCLI(); diff --git a/src/download/transfer-visualize/transfer-cli/cli-animation-wrapper.ts b/src/download/transfer-visualize/transfer-cli/cli-animation-wrapper.ts deleted file mode 100644 index d1a2c48..0000000 --- a/src/download/transfer-visualize/transfer-cli/cli-animation-wrapper.ts +++ /dev/null @@ -1,89 +0,0 @@ -import DownloadEngineNodejs from "../../download-engine/engine/download-engine-nodejs.js"; -import DownloadEngineMultiDownload from "../../download-engine/engine/download-engine-multi-download.js"; -import switchCliProgressStyle, {AvailableCLIProgressStyle} from "./progress-bars/switch-cli-progress-style.js"; -import {CliFormattedStatus} from "./progress-bars/base-transfer-cli-progress-bar.js"; -import TransferCli, {CLI_LEVEL, TransferCliOptions} from "./transfer-cli.js"; -import {BaseMultiProgressBar} from "./multiProgressBars/BaseMultiProgressBar.js"; -import cliSpinners from "cli-spinners"; - -const DEFAULT_CLI_STYLE: AvailableCLIProgressStyle = "auto"; -type AllowedDownloadEngines = DownloadEngineNodejs | DownloadEngineMultiDownload; - -export type CliProgressDownloadEngineOptions = { - truncateName?: boolean | number; - cliProgress?: boolean; - maxViewDownloads?: number; - createMultiProgressBar?: typeof BaseMultiProgressBar, - cliStyle?: AvailableCLIProgressStyle | ((status: CliFormattedStatus) => string) - cliName?: string; - cliAction?: string; - fetchStrategy?: "localFile" | "fetch"; - loadingAnimation?: cliSpinners.SpinnerName; - /** @internal */ - cliLevel?: CLI_LEVEL; -}; - -export default class CliAnimationWrapper { - private readonly _downloadEngine: Promise; - private readonly _options: CliProgressDownloadEngineOptions; - private _activeCLI?: TransferCli; - - public constructor(downloadEngine: Promise, _options: CliProgressDownloadEngineOptions) { - this._options = _options; - this._downloadEngine = downloadEngine; - this._init(); - } - - private _init() { - if (!this._options.cliProgress) { - return; - } - this._options.cliAction ??= this._options.fetchStrategy === "localFile" ? "Copying" : "Downloading"; - - const cliOptions: Partial = {...this._options}; - if (this._options.cliAction) { - cliOptions.action = this._options.cliAction; - } - if (this._options.cliName) { - cliOptions.name = this._options.cliName; - } - - cliOptions.createProgressBar = typeof this._options.cliStyle === "function" ? - { - createStatusLine: this._options.cliStyle, - multiProgressBar: this._options.createMultiProgressBar ?? BaseMultiProgressBar - } : - switchCliProgressStyle(this._options.cliStyle ?? DEFAULT_CLI_STYLE, { - truncateName: this._options.truncateName, - loadingSpinner: this._options.loadingAnimation - }); - - this._activeCLI = new TransferCli(cliOptions, this._options.cliLevel); - } - - public async attachAnimation() { - if (!this._activeCLI) { - return; - } - this._activeCLI.loadingAnimation.start(); - - try { - const engine = await this._downloadEngine; - this._activeCLI.loadingAnimation.stop(); - - engine.once("start", () => { - this._activeCLI?.start(); - - engine.on("progress", (progress) => { - this._activeCLI?.updateStatues(engine.downloadStatues, progress); - }); - - engine.on("closed", () => { - this._activeCLI?.stop(); - }); - }); - } finally { - this._activeCLI.loadingAnimation.stop(); - } - } -} diff --git a/src/download/transfer-visualize/transfer-cli/loading-animation/base-loading-animation.ts b/src/download/transfer-visualize/transfer-cli/loading-animation/base-loading-animation.ts deleted file mode 100644 index 588bee8..0000000 --- a/src/download/transfer-visualize/transfer-cli/loading-animation/base-loading-animation.ts +++ /dev/null @@ -1,74 +0,0 @@ -import UpdateManager from "stdout-update"; -import sleep from "sleep-promise"; -import {CLIProgressPrintType} from "../multiProgressBars/BaseMultiProgressBar.js"; - -export type BaseLoadingAnimationOptions = { - updateIntervalMs?: number | null; - loadingText?: string; - logType: CLIProgressPrintType -}; - -export const DEFAULT_LOADING_ANIMATION_OPTIONS: BaseLoadingAnimationOptions = { - loadingText: "Gathering information", - logType: "update" -}; - -const DEFAULT_UPDATE_INTERVAL_MS = 300; - -export default abstract class BaseLoadingAnimation { - protected options: BaseLoadingAnimationOptions; - protected stdoutManager = UpdateManager.getInstance(); - protected _animationActive = false; - - - protected constructor(options: BaseLoadingAnimationOptions = DEFAULT_LOADING_ANIMATION_OPTIONS) { - this.options = options; - this._processExit = this._processExit.bind(this); - } - - protected _render(): void { - const frame = this.createFrame(); - - if (this.options.logType === "update") { - this.stdoutManager.update([frame]); - } else { - console.log(frame); - } - } - - protected abstract createFrame(): string; - - async start() { - process.on("SIGINT", this._processExit); - - if (this.options.logType === "update") { - this.stdoutManager.hook(); - } - - this._animationActive = true; - while (this._animationActive) { - this._render(); - await sleep(this.options.updateIntervalMs || DEFAULT_UPDATE_INTERVAL_MS); - } - } - - stop(): void { - if (!this._animationActive) { - return; - } - - this._animationActive = false; - - if (this.options.logType === "update") { - this.stdoutManager.erase(); - this.stdoutManager.unhook(false); - } - - process.off("SIGINT", this._processExit); - } - - private _processExit() { - this.stop(); - process.exit(0); - } -} diff --git a/src/download/transfer-visualize/transfer-cli/loading-animation/cli-spinners-loading-animation.ts b/src/download/transfer-visualize/transfer-cli/loading-animation/cli-spinners-loading-animation.ts deleted file mode 100644 index 54d1fda..0000000 --- a/src/download/transfer-visualize/transfer-cli/loading-animation/cli-spinners-loading-animation.ts +++ /dev/null @@ -1,23 +0,0 @@ -import BaseLoadingAnimation, {BaseLoadingAnimationOptions, DEFAULT_LOADING_ANIMATION_OPTIONS} from "./base-loading-animation.js"; -import {Spinner} from "cli-spinners"; - -export default class CliSpinnersLoadingAnimation extends BaseLoadingAnimation { - private _spinner: Spinner; - private _frameIndex = 0; - - public constructor(spinner: Spinner, options: BaseLoadingAnimationOptions) { - options = {...DEFAULT_LOADING_ANIMATION_OPTIONS, ...options}; - options.updateIntervalMs ??= spinner.interval; - super(options); - this._spinner = spinner; - } - - protected createFrame(): string { - const frame = this._spinner.frames[this._frameIndex]; - this._frameIndex++; - if (this._frameIndex >= this._spinner.frames.length) { - this._frameIndex = 0; - } - return `${frame} ${this.options.loadingText}`; - } -} diff --git a/src/download/transfer-visualize/transfer-cli/multiProgressBars/BaseMultiProgressBar.ts b/src/download/transfer-visualize/transfer-cli/multiProgressBars/BaseMultiProgressBar.ts index d393167..d4c5086 100644 --- a/src/download/transfer-visualize/transfer-cli/multiProgressBars/BaseMultiProgressBar.ts +++ b/src/download/transfer-visualize/transfer-cli/multiProgressBars/BaseMultiProgressBar.ts @@ -3,11 +3,12 @@ import {FormattedStatus} from "../../format-transfer-status.js"; import {DownloadStatus} from "../../../download-engine/download-file/progress-status-file.js"; import chalk from "chalk"; import prettyBytes from "pretty-bytes"; +import cliSpinners from "cli-spinners"; export type MultiProgressBarOptions = { maxViewDownloads: number; createProgressBar: TransferCliProgressBar - action?: string; + loadingAnimation: cliSpinners.SpinnerName, }; export type CLIProgressPrintType = "update" | "log"; @@ -16,14 +17,12 @@ export class BaseMultiProgressBar { public readonly updateIntervalMs: null | number = null; public readonly printType: CLIProgressPrintType = "update"; + public constructor(protected options: MultiProgressBarOptions) { } protected createProgresses(statuses: FormattedStatus[]): string { - return statuses.map((status) => { - status.transferAction = this.options.action ?? status.transferAction; - return this.options.createProgressBar.createStatusLine(status); - }) + return statuses.map((status) => this.options.createProgressBar.createStatusLine(status)) .join("\n"); } @@ -49,8 +48,8 @@ export class BaseMultiProgressBar { } // eslint-disable-next-line @typescript-eslint/no-unused-vars - createMultiProgressBar(statuses: FormattedStatus[], oneStatus: FormattedStatus) { - if (statuses.length < this.options.maxViewDownloads) { + createMultiProgressBar(statuses: FormattedStatus[], oneStatus: FormattedStatus, loadingDownloads = 0) { + if (statuses.length < this.options.maxViewDownloads - Math.min(loadingDownloads, 1)) { return this.createProgresses(statuses); } @@ -58,10 +57,10 @@ export class BaseMultiProgressBar { const tasksLogs = this.createProgresses(allStatusesSorted.slice(0, this.options.maxViewDownloads)); if (notFinished) { - return tasksLogs + `\nand ${chalk.gray(remaining)} more out of ${chalk.blueBright(statuses.length)} downloads.`; + return tasksLogs + `\nand ${chalk.gray((remaining + loadingDownloads).toLocaleString())} more out of ${chalk.blueBright(statuses.length.toLocaleString())} downloads.`; } const totalSize = allStatusesSorted.reduce((acc, status) => acc + status.totalBytes, 0); - return tasksLogs + `\n${chalk.green(`All ${statuses.length} downloads (${prettyBytes(totalSize)}) finished.`)}`; + return tasksLogs + `\n${chalk.green(`All ${statuses.length.toLocaleString()} downloads (${prettyBytes(totalSize)}) finished.`)}`; } } diff --git a/src/download/transfer-visualize/transfer-cli/multiProgressBars/SummaryMultiProgressBar.ts b/src/download/transfer-visualize/transfer-cli/multiProgressBars/SummaryMultiProgressBar.ts index 2bf59c0..b257edc 100644 --- a/src/download/transfer-visualize/transfer-cli/multiProgressBars/SummaryMultiProgressBar.ts +++ b/src/download/transfer-visualize/transfer-cli/multiProgressBars/SummaryMultiProgressBar.ts @@ -8,15 +8,15 @@ export class SummaryMultiProgressBar extends BaseMultiProgressBar { private _parallelDownloads = 0; private _lastStatuses: FormattedStatus[] = []; - override createMultiProgressBar(statuses: FormattedStatus[], oneStatus: FormattedStatus) { + override createMultiProgressBar(statuses: FormattedStatus[], oneStatus: FormattedStatus, loadingDownloads = 0) { oneStatus = structuredClone(oneStatus); oneStatus.downloadFlags.push(DownloadFlags.DownloadSequence); const linesToPrint: FormattedStatus[] = []; - let index = 0; for (const status of statuses) { - const isStatusChanged = this._lastStatuses[index++]?.downloadStatus !== status.downloadStatus; + const lastStatus = this._lastStatuses.find(x => x.downloadId === status.downloadId); + const isStatusChanged = lastStatus?.downloadStatus !== status.downloadStatus; const copyStatus = structuredClone(status); if (isStatusChanged) { @@ -38,7 +38,7 @@ export class SummaryMultiProgressBar extends BaseMultiProgressBar { const activeDownloads = statuses.filter((status) => status.downloadStatus === DownloadStatus.Active).length; this._parallelDownloads ||= activeDownloads; const finishedDownloads = statuses.filter((status) => status.downloadStatus === DownloadStatus.Finished).length; - oneStatus.comment = `${finishedDownloads}/${statuses.length} files done${this._parallelDownloads > 1 ? ` (${activeDownloads} active)` : ""}`; + oneStatus.comment = `${finishedDownloads.toLocaleString()}/${(statuses.length + loadingDownloads).toLocaleString()} files done${this._parallelDownloads > 1 ? ` (${activeDownloads.toLocaleString()} active)` : ""}`; return this.createProgresses(filterStatusesSliced); } diff --git a/src/download/transfer-visualize/transfer-cli/progress-bars/base-transfer-cli-progress-bar.ts b/src/download/transfer-visualize/transfer-cli/progress-bars/base-transfer-cli-progress-bar.ts index c8f356f..17b8284 100644 --- a/src/download/transfer-visualize/transfer-cli/progress-bars/base-transfer-cli-progress-bar.ts +++ b/src/download/transfer-visualize/transfer-cli/progress-bars/base-transfer-cli-progress-bar.ts @@ -64,6 +64,16 @@ export default class BaseTransferCliProgressBar implements TransferCliProgressBa return this.status.startTime < Date.now() - SKIP_ETA_START_TIME; } + protected get alertStatus() { + if (this.status.retrying) { + return `(retrying #${this.status.retryingTotalAttempts})`; + } else if (this.status.streamsNotResponding) { + return `(${this.status.streamsNotResponding} streams not responding)`; + } + + return ""; + } + protected getNameSize(fileName = this.status.fileName) { return this.options.truncateName === false ? fileName.length @@ -170,6 +180,7 @@ export default class BaseTransferCliProgressBar implements TransferCliProgressBa const {formattedPercentage, formattedSpeed, formatTransferredOfTotal, formatTotal} = this.status; const status = this.switchTransferToShortText(); + const alertStatus = this.alertStatus; return renderDataLine([ { type: "status", @@ -177,6 +188,12 @@ export default class BaseTransferCliProgressBar implements TransferCliProgressBa size: status.length, formatter: (text) => chalk.cyan(text) }, + { + type: "status", + fullText: alertStatus, + size: alertStatus.length, + formatter: (text) => chalk.ansi256(196)(text) + }, { type: "spacer", fullText: " ", diff --git a/src/download/transfer-visualize/transfer-cli/progress-bars/fancy-transfer-cli-progress-bar.ts b/src/download/transfer-visualize/transfer-cli/progress-bars/fancy-transfer-cli-progress-bar.ts index a9d0429..e54648e 100644 --- a/src/download/transfer-visualize/transfer-cli/progress-bars/fancy-transfer-cli-progress-bar.ts +++ b/src/download/transfer-visualize/transfer-cli/progress-bars/fancy-transfer-cli-progress-bar.ts @@ -19,47 +19,58 @@ export default class FancyTransferCliProgressBar extends BaseTransferCliProgress const progressBarText = ` ${formattedPercentageWithPadding} (${formatTransferred}/${formatTotal}) `; const dimEta: DataLine = this.getETA(" | ", text => chalk.dim(text)); + const alertStatus = this.alertStatus; - return renderDataLine([{ - type: "status", - fullText: "", - size: 1, - formatter: () => STATUS_ICONS.activeDownload - }, { - type: "spacer", - fullText: " ", - size: " ".length - }, ...this.getNameAndCommentDataParts(), { - type: "spacer", - fullText: " ", - size: " ".length - }, { - type: "progressBar", - fullText: progressBarText, - size: Math.max(progressBarText.length, `100.0% (1024.00MB/${formatTotal})`.length), - flex: 4, - addEndPadding: 4, - maxSize: 40, - formatter(_, size) { - const leftPad = " ".repeat(Math.floor((size - progressBarText.length) / 2)); - return renderProgressBar({ - barText: leftPad + ` ${chalk.black.bgWhiteBright(formattedPercentageWithPadding)} ${chalk.gray(`(${formatTransferred}/${formatTotal})`)} `, - backgroundText: leftPad + ` ${chalk.yellow.bgGray(formattedPercentageWithPadding)} ${chalk.white(`(${formatTransferred}/${formatTotal})`)} `, - length: size, - loadedPercentage: percentage / 100, - barStyle: chalk.black.bgWhiteBright, - backgroundStyle: chalk.bgGray - }); - } - }, { - type: "spacer", - fullText: " ", - size: " ".length - }, { - type: "speed", - fullText: formattedSpeed, - size: Math.max("00.00kB/s".length, formattedSpeed.length) - }, ...dimEta]); + return renderDataLine([ + { + type: "status", + fullText: "", + size: 1, + formatter: () => STATUS_ICONS.activeDownload + }, + { + type: "status", + fullText: alertStatus, + size: alertStatus.length, + formatter: (text) => chalk.ansi256(196)(text) + }, + { + type: "spacer", + fullText: " ", + size: " ".length + }, ...this.getNameAndCommentDataParts(), { + type: "spacer", + fullText: " ", + size: " ".length + }, { + type: "progressBar", + fullText: progressBarText, + size: Math.max(progressBarText.length, `100.0% (1024.00MB/${formatTotal})`.length), + flex: 4, + addEndPadding: 4, + maxSize: 40, + formatter(_, size) { + const leftPad = " ".repeat(Math.floor((size - progressBarText.length) / 2)); + return renderProgressBar({ + barText: leftPad + ` ${chalk.black.bgWhiteBright(formattedPercentageWithPadding)} ${chalk.gray(`(${formatTransferred}/${formatTotal})`)} `, + backgroundText: leftPad + ` ${chalk.yellow.bgGray(formattedPercentageWithPadding)} ${chalk.white(`(${formatTransferred}/${formatTotal})`)} `, + length: size, + loadedPercentage: percentage / 100, + barStyle: chalk.black.bgWhiteBright, + backgroundStyle: chalk.bgGray + }); + } + }, { + type: "spacer", + fullText: " ", + size: " ".length + }, { + type: "speed", + fullText: formattedSpeed, + size: Math.max("00.00kB/s".length, formattedSpeed.length) + }, + ...dimEta + ]); } protected override renderFinishedLine() { diff --git a/src/download/transfer-visualize/transfer-cli/transfer-cli.ts b/src/download/transfer-visualize/transfer-cli/transfer-cli.ts index 9e64af8..a4e8052 100644 --- a/src/download/transfer-visualize/transfer-cli/transfer-cli.ts +++ b/src/download/transfer-visualize/transfer-cli/transfer-cli.ts @@ -2,13 +2,11 @@ import UpdateManager from "stdout-update"; import debounce from "lodash.debounce"; import {TransferCliProgressBar} from "./progress-bars/base-transfer-cli-progress-bar.js"; import cliSpinners from "cli-spinners"; -import CliSpinnersLoadingAnimation from "./loading-animation/cli-spinners-loading-animation.js"; import {FormattedStatus} from "../format-transfer-status.js"; import switchCliProgressStyle from "./progress-bars/switch-cli-progress-style.js"; import {BaseMultiProgressBar} from "./multiProgressBars/BaseMultiProgressBar.js"; export type TransferCliOptions = { - action?: string, name?: string, maxViewDownloads: number; truncateName: boolean | number; @@ -17,7 +15,6 @@ export type TransferCliOptions = { createProgressBar: TransferCliProgressBar; createMultiProgressBar: typeof BaseMultiProgressBar, loadingAnimation: cliSpinners.SpinnerName, - loadingText?: string; }; export const DEFAULT_TRANSFER_CLI_OPTIONS: TransferCliOptions = { @@ -27,30 +24,20 @@ export const DEFAULT_TRANSFER_CLI_OPTIONS: TransferCliOptions = { maxDebounceWait: process.platform === "win32" ? 500 : 100, createProgressBar: switchCliProgressStyle("auto", {truncateName: true}), loadingAnimation: "dots", - loadingText: "Gathering information", createMultiProgressBar: BaseMultiProgressBar }; -export enum CLI_LEVEL { - LOW = 0, - HIGH = 2 -} - - export default class TransferCli { - public static activeCLILevel = CLI_LEVEL.LOW; - public readonly loadingAnimation: CliSpinnersLoadingAnimation; protected options: TransferCliOptions; protected stdoutManager = UpdateManager.getInstance(); - protected myCLILevel: number; - protected latestProgress: [FormattedStatus[], FormattedStatus] = null!; + protected latestProgress: [FormattedStatus[], FormattedStatus, number] = null!; private _cliStopped = true; private readonly _updateStatuesDebounce: () => void; private _multiProgressBar: BaseMultiProgressBar; private _isFirstPrint = true; + private _lastProgressLong = ""; - public constructor(options: Partial, myCLILevel = CLI_LEVEL.LOW) { - TransferCli.activeCLILevel = this.myCLILevel = myCLILevel; + public constructor(options: Partial) { this.options = {...DEFAULT_TRANSFER_CLI_OPTIONS, ...options}; this._multiProgressBar = new this.options.createProgressBar.multiProgressBar(this.options); @@ -59,18 +46,11 @@ export default class TransferCli { maxWait: maxDebounceWait }); - this.loadingAnimation = new CliSpinnersLoadingAnimation(cliSpinners[this.options.loadingAnimation], { - loadingText: this.options.loadingText, - updateIntervalMs: this._multiProgressBar.updateIntervalMs, - logType: this._multiProgressBar.printType - }); this._processExit = this._processExit.bind(this); - - } start() { - if (this.myCLILevel !== TransferCli.activeCLILevel) return; + if (!this._cliStopped) return; this._cliStopped = false; if (this._multiProgressBar.printType === "update") { this.stdoutManager.hook(); @@ -79,9 +59,9 @@ export default class TransferCli { } stop() { - if (this._cliStopped || this.myCLILevel !== TransferCli.activeCLILevel) return; - this._updateStatues(); + if (this._cliStopped) return; this._cliStopped = true; + this._updateStatues(); if (this._multiProgressBar.printType === "update") { this.stdoutManager.unhook(false); } @@ -93,8 +73,8 @@ export default class TransferCli { process.exit(0); } - updateStatues(statues: FormattedStatus[], oneStatus: FormattedStatus) { - this.latestProgress = [statues, oneStatus]; + updateStatues(statues: FormattedStatus[], oneStatus: FormattedStatus, loadingDownloads = 0) { + this.latestProgress = [statues, oneStatus, loadingDownloads]; if (this._isFirstPrint) { this._isFirstPrint = false; @@ -105,12 +85,10 @@ export default class TransferCli { } private _updateStatues() { - if (this._cliStopped || this.myCLILevel !== TransferCli.activeCLILevel) { - return; // Do not update if there is a higher level CLI, meaning that this CLI is sub-CLI - } - + if (!this.latestProgress) return; const printLog = this._multiProgressBar.createMultiProgressBar(...this.latestProgress); - if (printLog) { + if (printLog && this._lastProgressLong != printLog) { + this._lastProgressLong = printLog; this._logUpdate(printLog); } } diff --git a/src/index.ts b/src/index.ts index dbc4183..b98a0c4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ import DownloadEngineNodejs from "./download/download-engine/engine/download-engine-nodejs.js"; -import {downloadFile, DownloadFileOptions, downloadSequence, DownloadSequenceOptions} from "./download/node-download.js"; +import {downloadFile, DownloadFileOptions, downloadFileRemote, downloadSequence, DownloadSequenceOptions} from "./download/node-download.js"; import {SaveProgressInfo} from "./download/download-engine/types.js"; import PathNotAFileError from "./download/download-engine/streams/download-engine-fetch-stream/errors/path-not-a-file-error.js"; import EmptyResponseError from "./download/download-engine/streams/download-engine-fetch-stream/errors/empty-response-error.js"; @@ -10,17 +10,19 @@ import FetchStreamError from "./download/download-engine/streams/download-engine import IpullError from "./errors/ipull-error.js"; import EngineError from "./download/download-engine/engine/error/engine-error.js"; import {FormattedStatus} from "./download/transfer-visualize/format-transfer-status.js"; -import DownloadEngineMultiDownload from "./download/download-engine/engine/download-engine-multi-download.js"; +import DownloadEngineMultiDownload, {DownloadEngineMultiAllowedEngines} from "./download/download-engine/engine/download-engine-multi-download.js"; import HttpError from "./download/download-engine/streams/download-engine-fetch-stream/errors/http-error.js"; import BaseDownloadEngine from "./download/download-engine/engine/base-download-engine.js"; import {InvalidOptionError} from "./download/download-engine/engine/error/InvalidOptionError.js"; import {BaseMultiProgressBar, MultiProgressBarOptions} from "./download/transfer-visualize/transfer-cli/multiProgressBars/BaseMultiProgressBar.js"; import {DownloadFlags, DownloadStatus} from "./download/download-engine/download-file/progress-status-file.js"; -import {NoDownloadEngineProvidedError} from "./download/download-engine/engine/error/no-download-engine-provided-error.js"; +import {DownloadEngineRemote} from "./download/download-engine/engine/DownloadEngineRemote.js"; +import {CliProgressDownloadEngineOptions} from "./download/transfer-visualize/transfer-cli/GlobalCLI.js"; export { DownloadFlags, DownloadStatus, + downloadFileRemote, downloadFile, downloadSequence, BaseMultiProgressBar, @@ -33,20 +35,22 @@ export { FetchStreamError, IpullError, EngineError, - InvalidOptionError, - NoDownloadEngineProvidedError + InvalidOptionError }; export type { BaseDownloadEngine, + DownloadEngineRemote, DownloadFileOptions, DownloadSequenceOptions, DownloadEngineNodejs, DownloadEngineMultiDownload, + DownloadEngineMultiAllowedEngines, SaveProgressInfo, FormattedStatus, - MultiProgressBarOptions + MultiProgressBarOptions, + CliProgressDownloadEngineOptions }; diff --git a/test/browser.test.ts b/test/browser.test.ts index 58208d7..251e4b5 100644 --- a/test/browser.test.ts +++ b/test/browser.test.ts @@ -11,7 +11,9 @@ globalThis.XMLHttpRequest = await import("xmlhttprequest-ssl").then(m => m.XMLHt describe("Browser Fetch API", () => { test.concurrent("Download file browser - memory", async (context) => { const downloader = await downloadFileBrowser({ - url: BIG_FILE + url: BIG_FILE, + parallelStreams: 2, + autoIncreaseParallelStreams: false }); await downloader.download(); @@ -21,26 +23,40 @@ describe("Browser Fetch API", () => { }); test.concurrent("Download file browser", async (context) => { - let buffer = Buffer.alloc(0); + const response = await ensureLocalFile(BIG_FILE, BIG_FILE_EXAMPLE); + const bufferIsCorrect = Buffer.from(await fs.readFile(response)); + + let bigBuffer = Buffer.alloc(0); let lastWrite = 0; const downloader = await downloadFileBrowser({ url: BIG_FILE, + parallelStreams: 2, + autoIncreaseParallelStreams: false, onWrite(cursor, data) { - buffer.set(data, cursor); - if (cursor + data.length > lastWrite) { - lastWrite = cursor + data.length; + let writeLocation = cursor; + for (const buffer of data) { + bigBuffer.set(buffer, writeLocation); + writeLocation += buffer.length; + } + + if (writeLocation > lastWrite) { + lastWrite = writeLocation; } } }); - buffer = Buffer.alloc(downloader.file.totalSize); - + bigBuffer = Buffer.alloc(downloader.file.totalSize); await downloader.download(); - context.expect(hashBuffer(buffer)) - .toMatchInlineSnapshot("\"9ae3ff19ee04fc02e9c60ce34e42858d16b46eeb88634d2035693c1ae9dbcbc9\""); + + const diff = bigBuffer.findIndex((value, index) => value !== bufferIsCorrect[index]); + context.expect(diff) + .toBe(-1); + context.expect(lastWrite) .toBe(downloader.file.totalSize); - }); + context.expect(hashBuffer(bigBuffer)) + .toMatchInlineSnapshot("\"9ae3ff19ee04fc02e9c60ce34e42858d16b46eeb88634d2035693c1ae9dbcbc9\""); + }, {repeats: 4, concurrent: true}); }, {timeout: 1000 * 60 * 3}); describe("Browser Fetch memory", () => { diff --git a/test/copy-file.test.ts b/test/copy-file.test.ts index 0115dda..b867404 100644 --- a/test/copy-file.test.ts +++ b/test/copy-file.test.ts @@ -17,7 +17,7 @@ describe("File Copy", async () => { fileName: copyFileToName, chunkSize: 4, parallelStreams: 1, - fetchStrategy: "localFile", + fetchStrategy: "local", cliProgress: false }); await engine.download(); @@ -37,7 +37,7 @@ describe("File Copy", async () => { url: fileToCopy, directory: ".", fileName: copyFileToName, - fetchStrategy: "localFile", + fetchStrategy: "local", cliProgress: false, parallelStreams: 1 }); diff --git a/test/daynamic-content-length.test.ts b/test/daynamic-content-length.test.ts index 3625c69..e821d38 100644 --- a/test/daynamic-content-length.test.ts +++ b/test/daynamic-content-length.test.ts @@ -15,10 +15,12 @@ describe("Dynamic content download", async () => { beforeAll(async () => { regularDownload = await ensureLocalFile(DYNAMIC_DOWNLOAD_FILE, ORIGINAL_FILE); originalFileHash = await fileHash(regularDownload); - }); + }, 1000 * 30); afterAll(async () => { - await fs.remove(regularDownload); + if (regularDownload) { + await fs.remove(regularDownload); + } }); @@ -26,7 +28,11 @@ describe("Dynamic content download", async () => { const downloader = await downloadFile({ url: DYNAMIC_DOWNLOAD_FILE, directory: ".", - fileName: IPUll_FILE + fileName: IPUll_FILE, + defaultFetchDownloadInfo: { + acceptRange: false, + length: 0 + } }); await downloader.download(); @@ -39,7 +45,11 @@ describe("Dynamic content download", async () => { test.concurrent("Browser Download", async (context) => { const downloader = await downloadFileBrowser({ - url: DYNAMIC_DOWNLOAD_FILE + url: DYNAMIC_DOWNLOAD_FILE, + defaultFetchDownloadInfo: { + acceptRange: false, + length: 0 + } }); await downloader.download(); diff --git a/test/download.test.ts b/test/download.test.ts index 2bd3b86..53099c3 100644 --- a/test/download.test.ts +++ b/test/download.test.ts @@ -23,6 +23,7 @@ describe("File Download", () => { const downloader = new DownloadEngineFile(file, { parallelStreams: randomNumber, chunkSize: 1024 ** 2, + autoIncreaseParallelStreams: false, fetchStream, writeStream }); @@ -46,7 +47,7 @@ describe("File Download", () => { let totalBytesWritten = 0; const fetchStream = new DownloadEngineFetchStreamFetch(); const writeStream = new DownloadEngineWriteStreamBrowser((cursor, data) => { - totalBytesWritten += data.byteLength; + totalBytesWritten += data.reduce((sum, buffer) => sum + buffer.length, 0); }); const file = await createDownloadFile(BIG_FILE); diff --git a/tsconfig.json b/tsconfig.json index 82085a0..d8117e1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,11 @@ { "compilerOptions": { "lib": [ - "esNext", + "es2022", "DOM" ], - "module": "nodeNext", - "target": "esNext", + "module": "NodeNext", + "target": "es2022", "esModuleInterop": true, "noImplicitAny": true, "noImplicitReturns": true, From 7296216331b71670326b7596d88915b559c0e02f Mon Sep 17 00:00:00 2001 From: ido Date: Fri, 23 May 2025 01:25:06 +0300 Subject: [PATCH 02/33] fix(multiDownload): update status when skipping download --- src/download/transfer-visualize/progress-statistics-builder.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/download/transfer-visualize/progress-statistics-builder.ts b/src/download/transfer-visualize/progress-statistics-builder.ts index 978fb2b..ea29770 100644 --- a/src/download/transfer-visualize/progress-statistics-builder.ts +++ b/src/download/transfer-visualize/progress-statistics-builder.ts @@ -55,6 +55,7 @@ export default class ProgressStatisticsBuilder extends EventEmitter Date: Sat, 24 May 2025 15:41:57 +0300 Subject: [PATCH 03/33] fix(multiDownload): events & cli progress --- .../download-file/download-engine-file.ts | 16 +++++----- .../engine/download-engine-multi-download.ts | 4 ++- .../transfer-cli/GlobalCLI.ts | 29 +++++++++++++------ .../transfer-cli/transfer-cli.ts | 6 ++-- 4 files changed, 35 insertions(+), 20 deletions(-) diff --git a/src/download/download-engine/download-file/download-engine-file.ts b/src/download/download-engine/download-file/download-engine-file.ts index 8963f71..e6ce7f4 100644 --- a/src/download/download-engine/download-file/download-engine-file.ts +++ b/src/download/download-engine/download-file/download-engine-file.ts @@ -129,7 +129,7 @@ export default class DownloadEngineFile extends EventEmitter c.isRetrying); - thisStatus.retryingTotalAttempts = Math.max(...streamContexts.map(x => x.retryingAttempts)); + thisStatus.retryingTotalAttempts = Math.max(0, ...streamContexts.map(x => x.retryingAttempts)); thisStatus.streamsNotResponding = streamContexts.reduce((acc, cur) => acc + (cur.isStreamNotResponding ? 1 : 0), 0); return thisStatus; @@ -170,14 +170,14 @@ export default class DownloadEngineFile extends EventEmitter this._progress.part || !this._activePart.acceptRange) { this._progress.part = i; this._progress.chunkSize = this.options.chunkSize; this._progress.parallelStreams = this.options.parallelStreams; - this._progress.chunks = this._emptyChunksForPart(i); + this._progress.chunks = this._chunksForPart(i); } // Reset in progress chunks diff --git a/src/download/download-engine/engine/download-engine-multi-download.ts b/src/download/download-engine/engine/download-engine-multi-download.ts index 110f667..0de48d2 100644 --- a/src/download/download-engine/engine/download-engine-multi-download.ts +++ b/src/download/download-engine/engine/download-engine-multi-download.ts @@ -231,8 +231,8 @@ export default class DownloadEngineMultiDownload { - this._multiDownloadEngine = this._createMultiDownloadEngine(); - this._eventsRegistered = new Set(); - }); - const eventsRegistered = this._eventsRegistered; this._multiDownloadEngine.on("childDownloadStarted", function registerEngineStatus(engine) { if (eventsRegistered.has(engine)) return; @@ -120,7 +117,7 @@ class GlobalCLI { const getCLIEngines = (multiEngine: DownloadEngineMultiDownload) => { const enginesToShow: AllowedDownloadEngine[] = []; - for (const engine of multiEngine.activeDownloads) { + for (const engine of multiEngine.downloads) { const isShowEngine = this._downloadOptions.get(engine)?.cliProgress; if (engine instanceof DownloadEngineMultiDownload) { if (isShowEngine) { @@ -136,11 +133,25 @@ class GlobalCLI { return enginesToShow.filter((engine, index, self) => self.indexOf(engine) === index); }; - this._multiDownloadEngine.on("progress", (progress) => { - if (!this._cliActive) return; + const printProgress = (progress: FormattedStatus) => { const statues = getCLIEngines(this._multiDownloadEngine) .map(x => x.status); this._transferCLI.updateStatues(statues, progress, this._multiDownloadEngine.loadingDownloads); + }; + + this._multiDownloadEngine.on("progress", (progress) => { + if (!this._cliActive) return; + printProgress(progress); + }); + + this._multiDownloadEngine.on("finished", () => { + if (this._transferCLI.isFirstPrint) { + printProgress(this._multiDownloadEngine.status); + } + this._transferCLI.isFirstPrint = true; + this._multiDownloadEngine = this._createMultiDownloadEngine(); + this._eventsRegistered = new Set(); + this._registerCLIEvents(); }); } diff --git a/src/download/transfer-visualize/transfer-cli/transfer-cli.ts b/src/download/transfer-visualize/transfer-cli/transfer-cli.ts index a4e8052..fffd430 100644 --- a/src/download/transfer-visualize/transfer-cli/transfer-cli.ts +++ b/src/download/transfer-visualize/transfer-cli/transfer-cli.ts @@ -34,7 +34,7 @@ export default class TransferCli { private _cliStopped = true; private readonly _updateStatuesDebounce: () => void; private _multiProgressBar: BaseMultiProgressBar; - private _isFirstPrint = true; + public isFirstPrint = true; private _lastProgressLong = ""; public constructor(options: Partial) { @@ -76,8 +76,8 @@ export default class TransferCli { updateStatues(statues: FormattedStatus[], oneStatus: FormattedStatus, loadingDownloads = 0) { this.latestProgress = [statues, oneStatus, loadingDownloads]; - if (this._isFirstPrint) { - this._isFirstPrint = false; + if (this.isFirstPrint) { + this.isFirstPrint = false; this._updateStatues(); } else { this._updateStatuesDebounce(); From 250467606be08c3903b056392e4eaaa1eef609df Mon Sep 17 00:00:00 2001 From: ido Date: Sat, 24 May 2025 16:45:58 +0300 Subject: [PATCH 04/33] pref(microtask): optimize timers --- .../download-engine-write-stream-nodejs.ts | 2 +- .../utils/BytesWriteDebounce.ts | 20 ++++++-- .../utils/abortableSleep.ts | 14 ++++++ .../transfer-cli/transfer-cli.ts | 21 +++++--- .../utils/abortableDebounce.ts | 50 +++++++++++++++++++ 5 files changed, 97 insertions(+), 10 deletions(-) create mode 100644 src/download/download-engine/streams/download-engine-write-stream/utils/abortableSleep.ts create mode 100644 src/download/transfer-visualize/utils/abortableDebounce.ts diff --git a/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-nodejs.ts b/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-nodejs.ts index 12bfa73..2f9536b 100644 --- a/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-nodejs.ts +++ b/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-nodejs.ts @@ -109,7 +109,7 @@ export default class DownloadEngineWriteStreamNodejs extends BaseDownloadEngineW } async ensureBytesSynced() { - await this._writeDebounce.writeAll(); + await this._writeDebounce.writeAllAndFinish(); } async ftruncate(size = this._fileSize) { diff --git a/src/download/download-engine/streams/download-engine-write-stream/utils/BytesWriteDebounce.ts b/src/download/download-engine/streams/download-engine-write-stream/utils/BytesWriteDebounce.ts index 805dc36..23ad5f0 100644 --- a/src/download/download-engine/streams/download-engine-write-stream/utils/BytesWriteDebounce.ts +++ b/src/download/download-engine/streams/download-engine-write-stream/utils/BytesWriteDebounce.ts @@ -1,4 +1,5 @@ -import sleep from "sleep-promise"; +import {abortableSleep} from "./abortableSleep.js"; +import WriterIsClosedError from "../errors/writer-is-closed-error.js"; export type BytesWriteDebounceOptions = { maxTime: number; @@ -14,12 +15,18 @@ export class BytesWriteDebounce { private _lastWriteTime = Date.now(); private _totalSizeOfChunks = 0; private _checkWriteInterval = false; + private _abortSleep = new AbortController(); + private _finished = false; constructor(private _options: BytesWriteDebounceOptions) { } async addChunk(index: number, buffers: Uint8Array[]) { + if (this._finished) { + throw new WriterIsClosedError("Cannot write to a finished stream"); + } + let writeIndex = index; for (const buffer of buffers) { this._writeChunks.push({index: writeIndex, buffer}); @@ -43,10 +50,10 @@ export class BytesWriteDebounce { } this._checkWriteInterval = true; - while (this._writeChunks.length > 0) { + while (this._writeChunks.length > 0 && !this._finished) { await this._writeIfNeeded(); const timeUntilMaxLimitAfterWrite = this._options.maxTime - (Date.now() - this._lastWriteTime); - await sleep(Math.max(timeUntilMaxLimitAfterWrite, 0)); + await abortableSleep(Math.max(timeUntilMaxLimitAfterWrite, 0), this._abortSleep.signal); } this._checkWriteInterval = false; @@ -93,4 +100,11 @@ export class BytesWriteDebounce { this._lastWriteTime = Date.now(); return Promise.all(writePromises); } + + + writeAllAndFinish() { + this._finished = true; + this._abortSleep.abort(); + return this.writeAll(); + } } diff --git a/src/download/download-engine/streams/download-engine-write-stream/utils/abortableSleep.ts b/src/download/download-engine/streams/download-engine-write-stream/utils/abortableSleep.ts new file mode 100644 index 0000000..504a3c9 --- /dev/null +++ b/src/download/download-engine/streams/download-engine-write-stream/utils/abortableSleep.ts @@ -0,0 +1,14 @@ +export function abortableSleep(timeMS: number, signal?: AbortSignal): Promise { + return new Promise((resolve) => { + const timeoutId = setTimeout(() => { + resolve(); + }, timeMS); + + if (signal) { + signal.addEventListener("abort", () => { + clearTimeout(timeoutId); + resolve(); + }, {once: true}); + } + }); +} diff --git a/src/download/transfer-visualize/transfer-cli/transfer-cli.ts b/src/download/transfer-visualize/transfer-cli/transfer-cli.ts index fffd430..3379d20 100644 --- a/src/download/transfer-visualize/transfer-cli/transfer-cli.ts +++ b/src/download/transfer-visualize/transfer-cli/transfer-cli.ts @@ -1,10 +1,10 @@ import UpdateManager from "stdout-update"; -import debounce from "lodash.debounce"; import {TransferCliProgressBar} from "./progress-bars/base-transfer-cli-progress-bar.js"; import cliSpinners from "cli-spinners"; import {FormattedStatus} from "../format-transfer-status.js"; import switchCliProgressStyle from "./progress-bars/switch-cli-progress-style.js"; import {BaseMultiProgressBar} from "./multiProgressBars/BaseMultiProgressBar.js"; +import {abortableDebounce} from "../utils/abortableDebounce.js"; export type TransferCliOptions = { name?: string, @@ -32,7 +32,8 @@ export default class TransferCli { protected stdoutManager = UpdateManager.getInstance(); protected latestProgress: [FormattedStatus[], FormattedStatus, number] = null!; private _cliStopped = true; - private readonly _updateStatuesDebounce: () => void; + private _updateStatuesDebounce: () => void = this._updateStatues; + private _abortDebounce = new AbortController(); private _multiProgressBar: BaseMultiProgressBar; public isFirstPrint = true; private _lastProgressLong = ""; @@ -41,12 +42,18 @@ export default class TransferCli { this.options = {...DEFAULT_TRANSFER_CLI_OPTIONS, ...options}; this._multiProgressBar = new this.options.createProgressBar.multiProgressBar(this.options); + this._updateStatues = this._updateStatues.bind(this); + this._processExit = this._processExit.bind(this); + this._resetDebounce(); + } + + private _resetDebounce() { const maxDebounceWait = this._multiProgressBar.updateIntervalMs || this.options.maxDebounceWait; - this._updateStatuesDebounce = debounce(this._updateStatues.bind(this), maxDebounceWait, { - maxWait: maxDebounceWait + this._abortDebounce = new AbortController(); + this._updateStatuesDebounce = abortableDebounce(this._updateStatues.bind(this), { + wait: maxDebounceWait, + signal: this._abortDebounce.signal }); - - this._processExit = this._processExit.bind(this); } start() { @@ -66,6 +73,8 @@ export default class TransferCli { this.stdoutManager.unhook(false); } process.off("SIGINT", this._processExit); + this._abortDebounce.abort(); + this._resetDebounce(); } private _processExit() { diff --git a/src/download/transfer-visualize/utils/abortableDebounce.ts b/src/download/transfer-visualize/utils/abortableDebounce.ts new file mode 100644 index 0000000..4e0d209 --- /dev/null +++ b/src/download/transfer-visualize/utils/abortableDebounce.ts @@ -0,0 +1,50 @@ +/** + * Creates a debounced function that can be aborted using an AbortSignal. + * The function will execute after a specified wait time, but can also execute immediately + * if the maximum wait time is reached since the last call. + * + * @param func - The function to debounce. + * @param options - Options for the debounce behavior. + * @returns A debounced version of the provided function. + */ +type AbortableDebounceOptions = { + maxWait?: number; // Maximum wait time in milliseconds + wait?: number; // Wait time in milliseconds + signal?: AbortSignal; // Abort signal to cancel the debounce +}; + +export function abortableDebounce void>(func: T, {wait = 0, maxWait = wait, signal}: AbortableDebounceOptions): (...args: Parameters) => void { + let timeoutId: NodeJS.Timeout | null = null; + let lastCallTime = 0; + + signal?.addEventListener("abort", () => { + if (timeoutId) { + clearTimeout(timeoutId); + timeoutId = null; + } + }); + + return (...args: Parameters) => { + const now = Date.now(); + + if (timeoutId) { + clearTimeout(timeoutId); + } + + if (signal?.aborted) { + return; // If the signal is aborted, do nothing + } + + const timeSinceLastCall = now - lastCallTime; + + if (timeSinceLastCall >= maxWait) { + func(...args); + lastCallTime = now; + } else { + timeoutId = setTimeout(() => { + func(...args); + lastCallTime = Date.now(); + }, Math.max(wait - timeSinceLastCall, 0)); + } + }; +} From 982639619bd3edd674302daeb9616d77592bfd78 Mon Sep 17 00:00:00 2001 From: ido Date: Sun, 25 May 2025 11:52:11 +0300 Subject: [PATCH 05/33] fix(transferStatistics): 0 when download size is unknown --- src/download/transfer-visualize/transfer-statistics.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/download/transfer-visualize/transfer-statistics.ts b/src/download/transfer-visualize/transfer-statistics.ts index bbe517c..75980c6 100644 --- a/src/download/transfer-visualize/transfer-statistics.ts +++ b/src/download/transfer-visualize/transfer-statistics.ts @@ -56,7 +56,7 @@ export default class TransferStatistics { const speed = clamp(this._calculateSpeed(transferred)); const timeLeft = (total - transferred) / speed; const timeLeftFinalNumber = clamp((timeLeft || 0) * 1000, 0, MAX_TIME_LEFT); - const percentage = clamp(((transferred / total) * 100), 0, 100); + const percentage = total === 0 ? 0 : clamp(((transferred / total) * 100), 0, 100); return this._latestProgress = { transferredBytes: clamp(transferred), From fc48e3be262fcb347d5b8341fa6ec16d17102567 Mon Sep 17 00:00:00 2001 From: ido Date: Sun, 25 May 2025 12:19:51 +0300 Subject: [PATCH 06/33] pref(downloadInfo): optimize requests --- README.md | 6 +++++- .../download-engine-fetch-stream-fetch.ts | 10 ++++++++-- .../download-engine-fetch-stream-xhr.ts | 14 +++++++++++--- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 665f1d7..3b3cc94 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,11 @@ import {downloadFileBrowser} from "ipull/dist/browser.js"; const downloader = await downloadFileBrowser({ url: 'https://example.com/file.large', - acceptRangeIsKnown: true // cors origin request will not return the range header, but we can force it to be true (multi-connection download) + acceptRangeIsKnown: true, // cors origin request will not return the range header, but we can force it to be true (multi-connection download) + // defaultFetchDownloadInfo: { // if we know the file size and other info, we can set it manually to overcome CORS issues && prevent multiple requests + // acceptRanges: true, + // length: 40789822, + // } }); await downloader.download(); diff --git a/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-fetch.ts b/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-fetch.ts index 8888480..1f65ed2 100644 --- a/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-fetch.ts +++ b/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-fetch.ts @@ -101,8 +101,14 @@ export default class DownloadEngineFetchStreamFetch extends BaseDownloadEngineFe const fileName = parseContentDisposition(response.headers.get("content-disposition")); let length = parseInt(response.headers.get("content-length")!) || 0; - if (response.headers.get("content-encoding") || browserCheck() && MIN_LENGTH_FOR_MORE_INFO_REQUEST < length) { - length = acceptRange ? await this.fetchDownloadInfoWithoutRetryContentRange(url, method === "GET" ? response : undefined) : 0; + const contentEncoding = response.headers.get("content-encoding"); + + if (contentEncoding && contentEncoding !== "identity") { + length = 0; // If content is encoded, we cannot determine the length reliably + } + + if (acceptRange && length === 0 && browserCheck() && MIN_LENGTH_FOR_MORE_INFO_REQUEST < length) { + length = await this.fetchDownloadInfoWithoutRetryContentRange(url, method === "GET" ? response : undefined); } return { diff --git a/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-xhr.ts b/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-xhr.ts index 322006e..78cb570 100644 --- a/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-xhr.ts +++ b/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-xhr.ts @@ -204,14 +204,22 @@ export default class DownloadEngineFetchStreamXhr extends BaseDownloadEngineFetc xhr.onload = async () => { if (xhr.status >= 200 && xhr.status < 300) { - const contentLength = parseInt(xhr.getResponseHeader("content-length")!); - const length = MIN_LENGTH_FOR_MORE_INFO_REQUEST < contentLength ? await this.fetchDownloadInfoWithoutRetryContentRange(url, method === "GET" ? xhr : undefined) : 0; const fileName = parseContentDisposition(xhr.getResponseHeader("content-disposition")); const acceptRange = this.options.acceptRangeIsKnown ?? xhr.getResponseHeader("Accept-Ranges") === "bytes"; + const contentEncoding = xhr.getResponseHeader("content-encoding"); + + let length = parseInt(xhr.getResponseHeader("content-length")!) || 0; + if (contentEncoding && contentEncoding !== "identity") { + length = 0; // If content is encoded, we cannot determine the length reliably + } + + if (acceptRange && length === 0 && MIN_LENGTH_FOR_MORE_INFO_REQUEST < length) { + length = await this.fetchDownloadInfoWithoutRetryContentRange(url, method === "GET" ? xhr : undefined); + } resolve({ - length, acceptRange, + length, newURL: xhr.responseURL, fileName }); From a6b98f53ee7b06fb645b2ba3c19fdfd1ff78e907 Mon Sep 17 00:00:00 2001 From: ido Date: Sun, 25 May 2025 12:30:04 +0300 Subject: [PATCH 07/33] docs(README): typo --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3b3cc94..6bff656 100644 --- a/README.md +++ b/README.md @@ -56,9 +56,9 @@ import {downloadFileBrowser} from "ipull/dist/browser.js"; const downloader = await downloadFileBrowser({ url: 'https://example.com/file.large', - acceptRangeIsKnown: true, // cors origin request will not return the range header, but we can force it to be true (multi-connection download) - // defaultFetchDownloadInfo: { // if we know the file size and other info, we can set it manually to overcome CORS issues && prevent multiple requests - // acceptRanges: true, + acceptRangeIsKnown: true, // overcome CORS, force multi-connection download (use only if you know the server supports range requests) + // defaultFetchDownloadInfo: { // set download info manually to overcome CORS issues && prevent multiple requests + // acceptRange: true, // length: 40789822, // } }); From 695478127e204d22acd7fb1b96d023cb94a95ac2 Mon Sep 17 00:00:00 2001 From: ido Date: Wed, 28 May 2025 21:15:07 +0300 Subject: [PATCH 08/33] fix(build): no source map in prod --- .github/workflows/build.yml | 2 +- .github/workflows/test.yml | 2 +- package.json | 1 + tsconfig.prod.json | 37 +++++++++++++++++++++++++++++++++++++ 4 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 tsconfig.prod.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 445310e..7a3d70a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,7 +16,7 @@ jobs: - name: Install modules run: npm ci --ignore-scripts - name: Build - run: npm run build + run: npm run build:prod - name: Test run: npm run test - name: Generate docs diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 652f105..cf25a9a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,6 +13,6 @@ jobs: - name: ESLint run: npm run lint - name: TypeScript validity - run: npm run build + run: npm run build:prod - name: Run Tests run: npm run test diff --git a/package.json b/package.json index 5655c5e..eb50ebb 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "scripts": { "generate-docs": "typedoc", "build": "tsc --build tsconfig.json", + "build:prod": "tsc --build tsconfig.prod.json", "cli": "npm run build && node -r dotenv/config ./dist/cli/cli.js", "format": "npm run lint:eslint -- --fix", "prepack": "npm run build", diff --git a/tsconfig.prod.json b/tsconfig.prod.json new file mode 100644 index 0000000..0b634c8 --- /dev/null +++ b/tsconfig.prod.json @@ -0,0 +1,37 @@ +{ + "compilerOptions": { + "lib": [ + "es2022", + "DOM" + ], + "module": "NodeNext", + "target": "es2022", + "esModuleInterop": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noImplicitOverride": true, + "removeComments": false, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "moduleResolution": "NodeNext", + "resolveJsonModule": true, + "strictNullChecks": true, + "isolatedModules": true, + "noEmit": false, + "outDir": "./dist", + "strict": true, + "sourceMap": false, + "composite": false, + "declaration": true, + "stripInternal": true + }, + "files": [ + "./src/index.ts" + ], + "include": [ + "./src" + ] +} From dee3ad5ef8a27738e2a395bb88b98b57df68602d Mon Sep 17 00:00:00 2001 From: ido Date: Wed, 11 Jun 2025 21:45:37 +0300 Subject: [PATCH 09/33] feat(downloadURL): regenerate download url when token expires --- package-lock.json | 42 ++++++++++++ package.json | 2 + .../download-file/download-engine-file.ts | 4 +- .../engine/base-download-engine.ts | 8 +++ .../engine/download-engine-browser.ts | 4 +- .../engine/download-engine-nodejs.ts | 6 +- .../base-download-engine-fetch-stream.ts | 44 +++++++++++-- .../download-engine-fetch-stream-fetch.ts | 8 +-- ...download-engine-fetch-stream-local-file.ts | 2 +- .../download-engine-fetch-stream-xhr.ts | 10 +-- .../utils/smart-chunk-split.ts | 6 +- src/download/download-engine/types.ts | 6 +- test/fetchDownloadInfo.test.ts | 64 ++++++++++++++++++- 13 files changed, 178 insertions(+), 28 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4d76fa1..286627c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,6 +42,7 @@ "@types/fs-extra": "^11.0.1", "@types/lodash.debounce": "^4.0.9", "@types/node": "^20.4.9", + "@types/stream-throttle": "^0.1.4", "@typescript-eslint/eslint-plugin": "^6.3.0", "@typescript-eslint/parser": "^6.3.0", "@vitest/ui": "^1.6.0", @@ -55,6 +56,7 @@ "hash.js": "^1.1.7", "husky": "^8.0.3", "semantic-release": "^24.0.0", + "stream-throttle": "^0.1.3", "tslib": "^2.6.1", "typedoc": "^0.26.3", "typedoc-material-theme": "^1.1.0", @@ -2512,6 +2514,16 @@ "@types/send": "*" } }, + "node_modules/@types/stream-throttle": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@types/stream-throttle/-/stream-throttle-0.1.4.tgz", + "integrity": "sha512-VxXIHGjVuK8tYsVm60rIQMmF/0xguCeen5OmK5S4Y6K64A+z+y4/GI6anRnVzaUZaJB9Ah9IfbDcO0o1gZCc/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/unist": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", @@ -6659,6 +6671,12 @@ "resolved": "https://registry.npmjs.org/lifecycle-utils/-/lifecycle-utils-1.3.1.tgz", "integrity": "sha512-Vo3cw99hNk95lG+dxmaA9kfpsYXW/0g9MwvnO0zpvfstiC0ds8fAIzCa9M+hVPnFF5dvx9oy4nlEbIFChdL7RA==" }, + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==", + "dev": true + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -11737,6 +11755,30 @@ "readable-stream": "^2.0.2" } }, + "node_modules/stream-throttle": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/stream-throttle/-/stream-throttle-0.1.3.tgz", + "integrity": "sha512-889+B9vN9dq7/vLbGyuHeZ6/ctf5sNuGWsDy89uNxkFTAgzy0eK7+w5fL3KLNRTkLle7EgZGvHUphZW0Q26MnQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "commander": "^2.2.0", + "limiter": "^1.0.5" + }, + "bin": { + "throttleproxy": "bin/throttleproxy.js" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/stream-throttle/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", diff --git a/package.json b/package.json index eb50ebb..b216ff0 100644 --- a/package.json +++ b/package.json @@ -105,6 +105,7 @@ "@types/fs-extra": "^11.0.1", "@types/lodash.debounce": "^4.0.9", "@types/node": "^20.4.9", + "@types/stream-throttle": "^0.1.4", "@typescript-eslint/eslint-plugin": "^6.3.0", "@typescript-eslint/parser": "^6.3.0", "@vitest/ui": "^1.6.0", @@ -118,6 +119,7 @@ "hash.js": "^1.1.7", "husky": "^8.0.3", "semantic-release": "^24.0.0", + "stream-throttle": "^0.1.3", "tslib": "^2.6.1", "typedoc": "^0.26.3", "typedoc-material-theme": "^1.1.0", diff --git a/src/download/download-engine/download-file/download-engine-file.ts b/src/download/download-engine/download-file/download-engine-file.ts index e6ce7f4..ee9af27 100644 --- a/src/download/download-engine/download-file/download-engine-file.ts +++ b/src/download/download-engine/download-file/download-engine-file.ts @@ -280,9 +280,7 @@ export default class DownloadEngineFile extends EventEmitter { getContext().streamBytes = length; this._sendProgressDownloadPart(); diff --git a/src/download/download-engine/engine/base-download-engine.ts b/src/download/download-engine/engine/base-download-engine.ts index 4b9dc90..47672d1 100644 --- a/src/download/download-engine/engine/base-download-engine.ts +++ b/src/download/download-engine/engine/base-download-engine.ts @@ -38,6 +38,10 @@ export type BaseDownloadEngineEvents = { [key: string]: any }; +export const DEFAULT_BASE_DOWNLOAD_ENGINE_OPTIONS: Partial = { + reuseRedirectURL: true +}; + export default class BaseDownloadEngine extends EventEmitter { public readonly options: DownloadEngineFileOptions; protected readonly _engine: DownloadEngineFile; @@ -165,6 +169,8 @@ export default class BaseDownloadEngine extends EventEmitter 0 && acceptRange }; @@ -173,6 +179,8 @@ export default class BaseDownloadEngine extends EventEmitter void, + activePart: { + size: number, + acceptRange?: boolean, + downloadURL: string, + originalURL: string, + downloadURLUpdateDate: number + } }; export type BaseDownloadEngineFetchStreamEvents = { @@ -102,6 +108,7 @@ export default abstract class BaseDownloadEngineFetchStream extends EventEmitter public aborted = false; protected _pausedResolve?: () => void; public errorCount = {value: 0}; + public lastFetchTime = 0; constructor(options: Partial = {}) { super(); @@ -114,7 +121,7 @@ export default abstract class BaseDownloadEngineFetchStream extends EventEmitter } protected get _endSize() { - return Math.min(this.state.endChunk * this.state.chunkSize, this.state.totalSize); + return Math.min(this.state.endChunk * this.state.chunkSize, this.state.activePart.size); } protected initEvents() { @@ -210,6 +217,7 @@ export default abstract class BaseDownloadEngineFetchStream extends EventEmitter // eslint-disable-next-line no-constant-condition while (true) { try { + this.lastFetchTime = Date.now(); return await this.fetchWithoutRetryChunks((...args) => { if (retryingOn) { retryingOn = false; @@ -219,10 +227,12 @@ export default abstract class BaseDownloadEngineFetchStream extends EventEmitter }); } catch (error: any) { if (error?.name === "AbortError") return; + this.errorCount.value++; this.emit("errorCountIncreased", this.errorCount.value, error); - if (error instanceof HttpError && !this.retryOnServerError(error)) { + const needToRecreateURL = this.shouldRecreateURL(error); + if (!needToRecreateURL && error instanceof HttpError && !this.retryOnServerError(error)) { throw error; } @@ -238,11 +248,31 @@ export default abstract class BaseDownloadEngineFetchStream extends EventEmitter retryResolvers = retryAsyncStatementSimple(this.options.retry); } - await retryResolvers(error); + await Promise.all([ + retryResolvers(error), + needToRecreateURL && this.recreateDownloadURL() + ]); } } } + shouldRecreateURL(error: Error): boolean { + return error instanceof StatusCodeError && TOKEN_EXPIRED_ERROR_CODES.includes(error.statusCode) && + this.state.activePart.downloadURL !== this.state.activePart.originalURL; + } + + recreateDownloadURL() { + return withLock(this.state.activePart, "_recreateURLLock", async () => { + if (this.state.activePart.downloadURLUpdateDate > this.lastFetchTime) { + return; // The URL was updated while we were waiting for the lock + } + + const downloadInfo = await this.fetchDownloadInfo(this.state.activePart.originalURL); + this.state.activePart.downloadURL = downloadInfo.newURL || this.state.activePart.originalURL; + this.state.activePart.downloadURLUpdateDate = Date.now(); + }); + } + protected abstract fetchWithoutRetryChunks(callback: WriteCallback): Promise | void; public close(): void | Promise { diff --git a/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-fetch.ts b/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-fetch.ts index 1f65ed2..d3a3ae2 100644 --- a/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-fetch.ts +++ b/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-fetch.ts @@ -32,7 +32,7 @@ export default class DownloadEngineFetchStreamFetch extends BaseDownloadEngineFe ...this.options.headers }; - if (this.state.rangeSupport) { + if (this.state.activePart.acceptRange) { headers.range = `bytes=${this._startSize}-${this._endSize - 1}`; } @@ -49,18 +49,18 @@ export default class DownloadEngineFetchStreamFetch extends BaseDownloadEngineFe }); - response = await fetch(this.appendToURL(this.state.url), { + response = await fetch(this.appendToURL(this.state.activePart.downloadURL), { headers, signal: this._activeController.signal }); if (response.status < 200 || response.status >= 300) { - throw new StatusCodeError(this.state.url, response.status, response.statusText, headers); + throw new StatusCodeError(this.state.activePart.downloadURL, response.status, response.statusText, headers); } const contentLength = parseHttpContentRange(response.headers.get("content-range"))?.length ?? parseInt(response.headers.get("content-length")!); const expectedContentLength = this._endSize - this._startSize; - if (this.state.rangeSupport && contentLength !== expectedContentLength) { + if (this.state.activePart.acceptRange && contentLength !== expectedContentLength) { throw new InvalidContentLengthError(expectedContentLength, contentLength); } diff --git a/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-local-file.ts b/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-local-file.ts index e593c60..b75612b 100644 --- a/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-local-file.ts +++ b/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-local-file.ts @@ -33,7 +33,7 @@ export default class DownloadEngineFetchStreamLocalFile extends BaseDownloadEngi } protected override async fetchWithoutRetryChunks(callback: WriteCallback): Promise { - const file = await this._ensureFileOpen(this.state.url); + const file = await this._ensureFileOpen(this.state.activePart.downloadURL); const stream = file.createReadStream({ start: this._startSize, diff --git a/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-xhr.ts b/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-xhr.ts index 78cb570..344a937 100644 --- a/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-xhr.ts +++ b/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-xhr.ts @@ -42,7 +42,7 @@ export default class DownloadEngineFetchStreamXhr extends BaseDownloadEngineFetc ...this.options.headers }; - if (this.state.rangeSupport) { + if (this.state.activePart.acceptRange) { headers.range = `bytes=${start}-${end - 1}`; } @@ -90,7 +90,7 @@ export default class DownloadEngineFetchStreamXhr extends BaseDownloadEngineFetc clearStreamTimeout(); const contentLength = parseInt(xhr.getResponseHeader("content-length")!); - if (this.state.rangeSupport && contentLength !== end - start) { + if (this.state.activePart.acceptRange && contentLength !== end - start) { throw new InvalidContentLengthError(end - start, contentLength); } @@ -133,7 +133,7 @@ export default class DownloadEngineFetchStreamXhr extends BaseDownloadEngineFetc } public override async fetchChunks(callback: WriteCallback) { - if (this.state.rangeSupport) { + if (this.state.activePart.acceptRange) { return await this._fetchChunksRangeSupport(callback); } @@ -149,14 +149,14 @@ export default class DownloadEngineFetchStreamXhr extends BaseDownloadEngineFetc await this.paused; if (this.aborted) return; - const chunk = await this.fetchBytes(this.state.url, this._startSize, this._endSize, this.state.onProgress); + const chunk = await this.fetchBytes(this.state.activePart.downloadURL, this._startSize, this._endSize, this.state.onProgress); callback([chunk], this._startSize, this.state.startChunk++); } } protected async _fetchChunksWithoutRange(callback: WriteCallback) { const relevantContent = await (async (): Promise => { - const result = await this.fetchBytes(this.state.url, 0, this._endSize, this.state.onProgress); + const result = await this.fetchBytes(this.state.activePart.downloadURL, 0, this._endSize, this.state.onProgress); return result.slice(this._startSize, this._endSize || result.length); })(); diff --git a/src/download/download-engine/streams/download-engine-fetch-stream/utils/smart-chunk-split.ts b/src/download/download-engine/streams/download-engine-fetch-stream/utils/smart-chunk-split.ts index 32e14b8..e60409c 100644 --- a/src/download/download-engine/streams/download-engine-fetch-stream/utils/smart-chunk-split.ts +++ b/src/download/download-engine/streams/download-engine-fetch-stream/utils/smart-chunk-split.ts @@ -5,7 +5,9 @@ export type SmartChunkSplitOptions = { startChunk: number; endChunk: number; lastChunkEndsFile: boolean; - totalSize: number + activePart: { + size: number; + } }; export default class SmartChunkSplit { @@ -25,7 +27,7 @@ export default class SmartChunkSplit { } public calcLastChunkSize() { - return this._options.totalSize - Math.max(this._options.endChunk - 1, 0) * this._options.chunkSize; + return this._options.activePart.size - Math.max(this._options.endChunk - 1, 0) * this._options.chunkSize; } public addChunk(data: Uint8Array) { diff --git a/src/download/download-engine/types.ts b/src/download/download-engine/types.ts index c6ae82c..bdade31 100644 --- a/src/download/download-engine/types.ts +++ b/src/download/download-engine/types.ts @@ -1,7 +1,9 @@ export type DownloadFilePart = { - downloadURL?: string - acceptRange?: boolean + downloadURL: string + originalURL: string + acceptRange: boolean size: number + downloadURLUpdateDate: number }; export enum ChunkStatus { diff --git a/test/fetchDownloadInfo.test.ts b/test/fetchDownloadInfo.test.ts index 752be31..ba8a5b2 100644 --- a/test/fetchDownloadInfo.test.ts +++ b/test/fetchDownloadInfo.test.ts @@ -2,6 +2,12 @@ import {beforeAll, describe, test} from "vitest"; import express from "express"; import {downloadFile} from "../src/index.js"; import {packageJson} from "../src/const.js"; +import fsPromise from "fs/promises"; +import {Throttle} from "stream-throttle"; +import {Readable} from "node:stream"; + +const TEMP_MODEL_TO_DOWNLOAD = "https://huggingface.co/mradermacher/arwkv-qwen-r1-1b5-i1-GGUF/resolve/main/arwkv-qwen-r1-1b5.i1-IQ1_M.gguf?download=true"; +const MAX_SPEED_BYTES = 5 * 1024 * 1024; // 5 MB/s describe("Fetch download info", () => { beforeAll(async () => { @@ -10,11 +16,53 @@ describe("Fetch download info", () => { res.json(packageJson); }); + server.get("/fileCreateToken.gguf", (req, res) => { + res.redirect(`/file.gguf?token=${Date.now() + 1000 * 3}`); + }); + + server.get("/file.gguf", async (req, res) => { + const token = req.query.token as string; + if (!token || Date.now() > parseInt(token)) { + res.status(403) + .send("Token expired"); + return; + } + + // proxy to huggingface model with throttling + const throttledStream = new Throttle({ + rate: MAX_SPEED_BYTES // 5 MB/s + }); + + const response = await fetch(TEMP_MODEL_TO_DOWNLOAD, { + headers: { + range: req.header("range")! + } + }); + + if (!response.ok) { + res.status(response.status) + .send("Error fetching file"); + return; + } + res.setHeader("Content-Type", "application/octet-stream"); + res.setHeader("Content-Disposition", `attachment; filename="file.gguf"`); + res.setHeader("Accept-Ranges", "bytes"); + res.setHeader("Content-Length", response.headers.get("Content-Length") || "0"); + Readable.fromWeb(response.body as any) + .pipe(throttledStream) + .pipe(res) + .on("error", (err) => { + console.error("Error piping response:", err); + res.status(500) + .send("Internal Server Error"); + }); + }); + await new Promise(resolve => { console.log("File Server Listening on 3000"); server.listen(3000, resolve); }); - }); + }, 0); test("Fetch download info GET", async (context) => { const downloader = await downloadFile({ @@ -25,4 +73,18 @@ describe("Fetch download info", () => { context.expect(downloader.file.totalSize > 0) .toBeTruthy(); }); + + test("Refetch download info when token is expired", async (context) => { + const downloader = await downloadFile({ + url: "http://localhost:3000/fileCreateToken.gguf", + directory: ".", + programType: "chunks", + parallelStreams: 3 + }); + + await downloader.download(); + const fileSize = (await fsPromise.stat(downloader.finalFileAbsolutePath)).size; + context.expect(fileSize) + .toMatchInlineSnapshot(`599171040`); + }); }, {timeout: 0}); From 872c30941db10418adcc0a6557029d21ee196609 Mon Sep 17 00:00:00 2001 From: ido Date: Fri, 20 Feb 2026 15:24:59 +0200 Subject: [PATCH 10/33] fix(cli): save flag does not save to directory --- src/cli/cli.ts | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/cli/cli.ts b/src/cli/cli.ts index 71c359e..c2cb7fd 100644 --- a/src/cli/cli.ts +++ b/src/cli/cli.ts @@ -4,7 +4,7 @@ import {Command, Option} from "commander"; import {packageJson} from "../const.js"; import {downloadFile, downloadSequence} from "../download/node-download.js"; import {setCommand} from "./commands/set.js"; -import findDownloadDir, {downloadToDirectory, findFileName} from "./utils/find-download-dir.js"; +import findDownloadDir, {findFileName} from "./utils/find-download-dir.js"; import {AvailableCLIProgressStyle} from "../download/transfer-visualize/transfer-cli/progress-bars/switch-cli-progress-style.js"; @@ -29,11 +29,25 @@ pullCommand process.exit(0); } + const saveLocationIsDirectory = saveLocation && path.extname(saveLocation) === ""; + const fileDownloads = await Promise.all( - files.map(async (file) => { - const isDirectory = saveLocation && await downloadToDirectory(saveLocation); - const directory = isDirectory ? saveLocation : await findDownloadDir(findFileName(file)); - const fileName = isDirectory || !saveLocation ? "" : path.basename(saveLocation); + files.map(async (file, index) => { + + let fileName: string | undefined; + let directory = await findDownloadDir(findFileName(file)); + + if (saveLocation) { + if (saveLocationIsDirectory) { + directory = saveLocation; + } else { + directory = path.dirname(saveLocation); + + const basename = path.basename(saveLocation); + const fileIndex = file.length > 1 ? ((index + 1) + "-") : ""; + fileName = fileIndex + basename; + } + } return await downloadFile({ url: file, From 6228d28eadca9b85bdcce89c2de4fdf392f3001d Mon Sep 17 00:00:00 2001 From: ido Date: Fri, 20 Feb 2026 16:24:52 +0200 Subject: [PATCH 11/33] fix: tests --- package-lock.json | 1550 +++++++++++++++++--------------- package.json | 5 +- test/browser.test.ts | 16 +- test/copy-file.test.ts | 2 +- test/download.test.ts | 4 +- test/fetchDownloadInfo.test.ts | 2 +- test/utils/download.ts | 6 +- test/utils/files.ts | 2 +- vitest.config.ts | 9 +- 9 files changed, 834 insertions(+), 762 deletions(-) diff --git a/package-lock.json b/package-lock.json index 286627c..010a9a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,7 +45,6 @@ "@types/stream-throttle": "^0.1.4", "@typescript-eslint/eslint-plugin": "^6.3.0", "@typescript-eslint/parser": "^6.3.0", - "@vitest/ui": "^1.6.0", "dotenv": "^16.3.1", "eslint": "^8.46.0", "eslint-plugin-import": "^2.28.0", @@ -62,8 +61,8 @@ "typedoc-material-theme": "^1.1.0", "typedoc-plugin-missing-exports": "^3.0.0", "typescript": "^5.5.3", - "vitest": "^1.6.0", - "xmlhttprequest-ssl": "^2.1.1" + "vitest": "^4.0.18", + "xmlhttprequest-ssl": "^4.0.0" }, "engines": { "node": ">=18.0.0" @@ -757,371 +756,445 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", "cpu": [ "loong64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", "cpu": [ "mips64el" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@eslint-community/eslint-utils": { @@ -1213,18 +1286,6 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", @@ -1235,10 +1296,11 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.9", @@ -1485,12 +1547,6 @@ "node": ">=12" } }, - "node_modules/@polka/url": { - "version": "1.0.0-next.24", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.24.tgz", - "integrity": "sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==", - "dev": true - }, "node_modules/@reflink/reflink": { "version": "0.1.16", "resolved": "https://registry.npmjs.org/@reflink/reflink/-/reflink-0.1.16.tgz", @@ -1631,208 +1687,350 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.1.tgz", - "integrity": "sha512-lncuC4aHicncmbORnx+dUaAgzee9cm/PbIqgWz1PpXuwc+sa1Ct83tnqUDy/GFKleLiN7ZIeytM6KJ4cAn1SxA==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.58.0.tgz", + "integrity": "sha512-mr0tmS/4FoVk1cnaeN244A/wjvGDNItZKR8hRhnmCzygyRXYtKF5jVDSIILR1U97CTzAYmbgIj/Dukg62ggG5w==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.1.tgz", - "integrity": "sha512-F/tkdw0WSs4ojqz5Ovrw5r9odqzFjb5LIgHdHZG65dFI1lWTWRVy32KDJLKRISHgJvqUeUhdIvy43fX41znyDg==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.58.0.tgz", + "integrity": "sha512-+s++dbp+/RTte62mQD9wLSbiMTV+xr/PeRJEc/sFZFSBRlHPNPVaf5FXlzAL77Mr8FtSfQqCN+I598M8U41ccQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.1.tgz", - "integrity": "sha512-vk+ma8iC1ebje/ahpxpnrfVQJibTMyHdWpOGZ3JpQ7Mgn/3QNHmPq7YwjZbIE7km73dH5M1e6MRRsnEBW7v5CQ==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.58.0.tgz", + "integrity": "sha512-MFWBwTcYs0jZbINQBXHfSrpSQJq3IUOakcKPzfeSznONop14Pxuqa0Kg19GD0rNBMPQI2tFtu3UzapZpH0Uc1Q==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.1.tgz", - "integrity": "sha512-IgpzXKauRe1Tafcej9STjSSuG0Ghu/xGYH+qG6JwsAUxXrnkvNHcq/NL6nz1+jzvWAnQkuAJ4uIwGB48K9OCGA==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.58.0.tgz", + "integrity": "sha512-yiKJY7pj9c9JwzuKYLFaDZw5gma3fI9bkPEIyofvVfsPqjCWPglSHdpdwXpKGvDeYDms3Qal8qGMEHZ1M/4Udg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.58.0.tgz", + "integrity": "sha512-x97kCoBh5MOevpn/CNK9W1x8BEzO238541BGWBc315uOlN0AD/ifZ1msg+ZQB05Ux+VF6EcYqpiagfLJ8U3LvQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.58.0.tgz", + "integrity": "sha512-Aa8jPoZ6IQAG2eIrcXPpjRcMjROMFxCt1UYPZZtCxRV68WkuSigYtQ/7Zwrcr2IvtNJo7T2JfDXyMLxq5L4Jlg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.1.tgz", - "integrity": "sha512-P9bSiAUnSSM7EmyRK+e5wgpqai86QOSv8BwvkGjLwYuOpaeomiZWifEos517CwbG+aZl1T4clSE1YqqH2JRs+g==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.58.0.tgz", + "integrity": "sha512-Ob8YgT5kD/lSIYW2Rcngs5kNB/44Q2RzBSPz9brf2WEtcGR7/f/E9HeHn1wYaAwKBni+bdXEwgHvUd0x12lQSA==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.1.tgz", - "integrity": "sha512-5RnjpACoxtS+aWOI1dURKno11d7krfpGDEn19jI8BuWmSBbUC4ytIADfROM1FZrFhQPSoP+KEa3NlEScznBTyQ==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.58.0.tgz", + "integrity": "sha512-K+RI5oP1ceqoadvNt1FecL17Qtw/n9BgRSzxif3rTL2QlIu88ccvY+Y9nnHe/cmT5zbH9+bpiJuG1mGHRVwF4Q==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.1.tgz", - "integrity": "sha512-8mwmGD668m8WaGbthrEYZ9CBmPug2QPGWxhJxh/vCgBjro5o96gL04WLlg5BA233OCWLqERy4YUzX3bJGXaJgQ==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.58.0.tgz", + "integrity": "sha512-T+17JAsCKUjmbopcKepJjHWHXSjeW7O5PL7lEFaeQmiVyw4kkc5/lyYKzrv6ElWRX/MrEWfPiJWqbTvfIvjM1Q==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.1.tgz", - "integrity": "sha512-dJX9u4r4bqInMGOAQoGYdwDP8lQiisWb9et+T84l2WXk41yEej8v2iGKodmdKimT8cTAYt0jFb+UEBxnPkbXEQ==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.58.0.tgz", + "integrity": "sha512-cCePktb9+6R9itIJdeCFF9txPU7pQeEHB5AbHu/MKsfH/k70ZtOeq1k4YAtBv9Z7mmKI5/wOLYjQ+B9QdxR6LA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.58.0.tgz", + "integrity": "sha512-iekUaLkfliAsDl4/xSdoCJ1gnnIXvoNz85C8U8+ZxknM5pBStfZjeXgB8lXobDQvvPRCN8FPmmuTtH+z95HTmg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.58.0.tgz", + "integrity": "sha512-68ofRgJNl/jYJbxFjCKE7IwhbfxOl1muPN4KbIqAIe32lm22KmU7E8OPvyy68HTNkI2iV/c8y2kSPSm2mW/Q9Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.1.tgz", - "integrity": "sha512-V72cXdTl4EI0x6FNmho4D502sy7ed+LuVW6Ym8aI6DRQ9hQZdp5sj0a2usYOlqvFBNKQnLQGwmYnujo2HvjCxQ==", + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.58.0.tgz", + "integrity": "sha512-dpz8vT0i+JqUKuSNPCP5SYyIV2Lh0sNL1+FhM7eLC457d5B9/BC3kDPp5BBftMmTNsBarcPcoz5UGSsnCiw4XQ==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.58.0.tgz", + "integrity": "sha512-4gdkkf9UJ7tafnweBCR/mk4jf3Jfl0cKX9Np80t5i78kjIH0ZdezUv/JDI2VtruE5lunfACqftJ8dIMGN4oHew==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.1.tgz", - "integrity": "sha512-f+pJih7sxoKmbjghrM2RkWo2WHUW8UbfxIQiWo5yeCaCM0TveMEuAzKJte4QskBp1TIinpnRcxkquY+4WuY/tg==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.58.0.tgz", + "integrity": "sha512-YFS4vPnOkDTD/JriUeeZurFYoJhPf9GQQEF/v4lltp3mVcBmnsAdjEWhr2cjUCZzZNzxCG0HZOvJU44UGHSdzw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.58.0.tgz", + "integrity": "sha512-x2xgZlFne+QVNKV8b4wwaCS8pwq3y14zedZ5DqLzjdRITvreBk//4Knbcvm7+lWmms9V9qFp60MtUd0/t/PXPw==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.1.tgz", - "integrity": "sha512-qb1hMMT3Fr/Qz1OKovCuUM11MUNLUuHeBC2DPPAWUYYUAOFWaxInaTwTQmc7Fl5La7DShTEpmYwgdt2hG+4TEg==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.58.0.tgz", + "integrity": "sha512-jIhrujyn4UnWF8S+DHSkAkDEO3hLX0cjzxJZPLF80xFyzyUIYgSMRcYQ3+uqEoyDD2beGq7Dj7edi8OnJcS/hg==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.1.tgz", - "integrity": "sha512-7O5u/p6oKUFYjRbZkL2FLbwsyoJAjyeXHCU3O4ndvzg2OFO2GinFPSJFGbiwFDaCFc+k7gs9CF243PwdPQFh5g==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.58.0.tgz", + "integrity": "sha512-+410Srdoh78MKSJxTQ+hZ/Mx+ajd6RjjPwBPNd0R3J9FtL6ZA0GqiiyNjCO9In0IzZkCNrpGymSfn+kgyPQocg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.1.tgz", - "integrity": "sha512-pDLkYITdYrH/9Cv/Vlj8HppDuLMDUBmgsM0+N+xLtFd18aXgM9Nyqupb/Uw+HeidhfYg2lD6CXvz6CjoVOaKjQ==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.58.0.tgz", + "integrity": "sha512-ZjMyby5SICi227y1MTR3VYBpFTdZs823Rs/hpakufleBoufoOIB6jtm9FEoxn/cgO7l6PM2rCEl5Kre5vX0QrQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.58.0.tgz", + "integrity": "sha512-ds4iwfYkSQ0k1nb8LTcyXw//ToHOnNTJtceySpL3fa7tc/AsE+UpUFphW126A6fKBGJD5dhRvg8zw1rvoGFxmw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.58.0.tgz", + "integrity": "sha512-fd/zpJniln4ICdPkjWFhZYeY/bpnaN9pGa6ko+5WD38I0tTqk9lXMgXZg09MNdhpARngmxiCg0B0XUamNw/5BQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.1.tgz", - "integrity": "sha512-W2ZNI323O/8pJdBGil1oCauuCzmVd9lDmWBBqxYZcOqWD6aWqJtVBQ1dFrF4dYpZPks6F+xCZHfzG5hYlSHZ6g==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.58.0.tgz", + "integrity": "sha512-YpG8dUOip7DCz3nr/JUfPbIUo+2d/dy++5bFzgi4ugOGBIox+qMbbqt/JoORwvI/C9Kn2tz6+Bieoqd5+B1CjA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.1.tgz", - "integrity": "sha512-ELfEX1/+eGZYMaCIbK4jqLxO1gyTSOIlZr6pbC4SRYFaSIDVKOnZNMdoZ+ON0mrFDp4+H5MhwNC1H/AhE3zQLg==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.58.0.tgz", + "integrity": "sha512-b9DI8jpFQVh4hIXFr0/+N/TzLdpBIoPzjt0Rt4xJbW3mzguV3mduR9cNgiuFcuL/TeORejJhCWiAXe3E/6PxWA==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.58.0.tgz", + "integrity": "sha512-CSrVpmoRJFN06LL9xhkitkwUcTZtIotYAF5p6XOR2zW0Zz5mzb3IPpcoPhB02frzMHFNo1reQ9xSF5fFm3hUsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.1.tgz", - "integrity": "sha512-yjk2MAkQmoaPYCSu35RLJ62+dz358nE83VfTePJRp8CG7aMg25mEJYpXFiD+NcevhX8LxD5OP5tktPXnXN7GDw==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.58.0.tgz", + "integrity": "sha512-QFsBgQNTnh5K0t/sBsjJLq24YVqEIVkGpfN2VHsnN90soZyhaiA9UUHufcctVNL4ypJY0wrwad0wslx2KJQ1/w==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -2260,12 +2458,6 @@ "@types/hast": "^3.0.4" } }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true - }, "node_modules/@sindresorhus/is": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", @@ -2290,6 +2482,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, "node_modules/@tinyhttp/content-disposition": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@tinyhttp/content-disposition/-/content-disposition-2.2.0.tgz", @@ -2345,6 +2544,17 @@ "@types/node": "*" } }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", @@ -2354,11 +2564,19 @@ "@types/node": "*" } }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" }, "node_modules/@types/express": { "version": "5.0.0", @@ -2720,117 +2938,84 @@ } }, "node_modules/@vitest/expect": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.0.tgz", - "integrity": "sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", + "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/spy": "1.6.0", - "@vitest/utils": "1.6.0", - "chai": "^4.3.10" + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/runner": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.0.tgz", - "integrity": "sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==", + "node_modules/@vitest/pretty-format": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", + "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/utils": "1.6.0", - "p-limit": "^5.0.0", - "pathe": "^1.1.1" + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/runner/node_modules/p-limit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", - "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "node_modules/@vitest/runner": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz", + "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==", "dev": true, + "license": "MIT", "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@vitest/runner/node_modules/yocto-queue": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", - "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", - "dev": true, - "engines": { - "node": ">=12.20" + "@vitest/utils": "4.0.18", + "pathe": "^2.0.3" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.0.tgz", - "integrity": "sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz", + "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==", "dev": true, + "license": "MIT", "dependencies": { - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "pretty-format": "^29.7.0" + "@vitest/pretty-format": "4.0.18", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/spy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.0.tgz", - "integrity": "sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz", + "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==", "dev": true, - "dependencies": { - "tinyspy": "^2.2.0" - }, + "license": "MIT", "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/ui": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-1.6.0.tgz", - "integrity": "sha512-k3Lyo+ONLOgylctiGovRKy7V4+dIN2yxstX3eY5cWFXH6WP+ooVX79YSyi0GagdTQzLmT43BF27T0s6dOIPBXA==", - "dev": true, - "dependencies": { - "@vitest/utils": "1.6.0", - "fast-glob": "^3.3.2", - "fflate": "^0.8.1", - "flatted": "^3.2.9", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "sirv": "^2.0.4" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "vitest": "1.6.0" - } - }, "node_modules/@vitest/utils": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.0.tgz", - "integrity": "sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz", + "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", "dev": true, + "license": "MIT", "dependencies": { - "diff-sequences": "^29.6.3", - "estree-walker": "^3.0.3", - "loupe": "^2.3.7", - "pretty-format": "^29.7.0" + "@vitest/pretty-format": "4.0.18", + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" @@ -3145,12 +3330,13 @@ } }, "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, + "license": "MIT", "engines": { - "node": "*" + "node": ">=12" } }, "node_modules/async-retry": { @@ -3282,15 +3468,6 @@ "node": ">= 0.8" } }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", @@ -3346,21 +3523,13 @@ } }, "node_modules/chai": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", - "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "dev": true, - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.0.8" - }, + "license": "MIT", "engines": { - "node": ">=4" + "node": ">=18" } }, "node_modules/chalk": { @@ -3383,18 +3552,6 @@ "node": ">=10" } }, - "node_modules/check-error": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", - "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", - "dev": true, - "dependencies": { - "get-func-name": "^2.0.2" - }, - "engines": { - "node": "*" - } - }, "node_modules/ci-info": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.0.0.tgz", @@ -4022,18 +4179,6 @@ "node": ">=0.10.0" } }, - "node_modules/deep-eql": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", - "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", - "dev": true, - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -4110,15 +4255,6 @@ "node": ">=0.3.1" } }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -4387,6 +4523,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, "node_modules/es-set-tostringtag": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", @@ -4428,41 +4571,45 @@ } }, "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", "dev": true, "hasInstallScript": true, + "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" } }, "node_modules/escalade": { @@ -4965,6 +5112,7 @@ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, + "license": "MIT", "dependencies": { "@types/estree": "^1.0.0" } @@ -5109,6 +5257,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/express": { "version": "4.21.1", "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", @@ -5241,12 +5399,6 @@ "reusify": "^1.0.4" } }, - "node_modules/fflate": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", - "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", - "dev": true - }, "node_modules/figures": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", @@ -5469,6 +5621,7 @@ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -5545,15 +5698,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -6602,12 +6746,6 @@ "json5": "lib/cli.js" } }, - "node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", - "dev": true - }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -6720,22 +6858,6 @@ "node": ">=4" } }, - "node_modules/local-pkg": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", - "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", - "dev": true, - "dependencies": { - "mlly": "^1.4.2", - "pkg-types": "^1.0.3" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -6852,15 +6974,6 @@ "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==", "dev": true }, - "node_modules/loupe": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", - "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", - "dev": true, - "dependencies": { - "get-func-name": "^2.0.1" - } - }, "node_modules/lowdb": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-7.0.1.tgz", @@ -6882,12 +6995,13 @@ "dev": true }, "node_modules/magic-string": { - "version": "0.30.10", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", - "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, "node_modules/make-error": { @@ -7297,27 +7411,6 @@ "node": ">= 6" } }, - "node_modules/mlly": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.6.0.tgz", - "integrity": "sha512-YOvg9hfYQmnaB56Yb+KrJE2u0Yzz5zR+sLejEvF4fzwzV1Al6hkf2vyHTwqCRyv0hCi9rVCqVoXpyYevQIRwLQ==", - "dev": true, - "dependencies": { - "acorn": "^8.11.3", - "pathe": "^1.1.2", - "pkg-types": "^1.0.3", - "ufo": "^1.3.2" - } - }, - "node_modules/mrmime": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", - "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -7335,6 +7428,25 @@ "thenify-all": "^1.0.0" } }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -10108,6 +10220,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -10380,25 +10503,18 @@ } }, "node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", - "dev": true - }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, - "engines": { - "node": "*" - } + "license": "MIT" }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", - "dev": true + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", @@ -10501,21 +10617,10 @@ "node": ">=4" } }, - "node_modules/pkg-types": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", - "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", - "dev": true, - "dependencies": { - "jsonc-parser": "^3.2.0", - "mlly": "^1.2.0", - "pathe": "^1.1.0" - } - }, "node_modules/postcss": { - "version": "8.4.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz", - "integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, "funding": [ { @@ -10531,34 +10636,16 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.1", - "source-map-js": "^1.2.0" + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" } }, - "node_modules/postcss/node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -10568,41 +10655,15 @@ "node": ">= 0.8.0" } }, - "node_modules/pretty-bytes": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz", - "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==", - "engines": { - "node": "^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, + "node_modules/pretty-bytes": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz", + "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==", "engines": { - "node": ">=10" + "node": "^14.13.1 || >=16.0.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/pretty-ms": { @@ -10754,12 +10815,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true - }, "node_modules/read-package-up": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz", @@ -11034,12 +11089,13 @@ } }, "node_modules/rollup": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.1.tgz", - "integrity": "sha512-Elx2UT8lzxxOXMpy5HWQGZqkrQOtrVDDa/bm9l10+U4rQnVzbL/LgZ4NOM1MPIDyHk69W4InuYDF5dzRh4Kw1A==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.58.0.tgz", + "integrity": "sha512-wbT0mBmWbIvvq8NeEYWWvevvxnOyhKChir47S66WCxw1SXqhw7ssIYejnQEVt7XYQpsj2y8F9PM+Cr3SNEa0gw==", "dev": true, + "license": "MIT", "dependencies": { - "@types/estree": "1.0.5" + "@types/estree": "1.0.8" }, "bin": { "rollup": "dist/bin/rollup" @@ -11049,22 +11105,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.18.1", - "@rollup/rollup-android-arm64": "4.18.1", - "@rollup/rollup-darwin-arm64": "4.18.1", - "@rollup/rollup-darwin-x64": "4.18.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.18.1", - "@rollup/rollup-linux-arm-musleabihf": "4.18.1", - "@rollup/rollup-linux-arm64-gnu": "4.18.1", - "@rollup/rollup-linux-arm64-musl": "4.18.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.18.1", - "@rollup/rollup-linux-riscv64-gnu": "4.18.1", - "@rollup/rollup-linux-s390x-gnu": "4.18.1", - "@rollup/rollup-linux-x64-gnu": "4.18.1", - "@rollup/rollup-linux-x64-musl": "4.18.1", - "@rollup/rollup-win32-arm64-msvc": "4.18.1", - "@rollup/rollup-win32-ia32-msvc": "4.18.1", - "@rollup/rollup-win32-x64-msvc": "4.18.1", + "@rollup/rollup-android-arm-eabi": "4.58.0", + "@rollup/rollup-android-arm64": "4.58.0", + "@rollup/rollup-darwin-arm64": "4.58.0", + "@rollup/rollup-darwin-x64": "4.58.0", + "@rollup/rollup-freebsd-arm64": "4.58.0", + "@rollup/rollup-freebsd-x64": "4.58.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.58.0", + "@rollup/rollup-linux-arm-musleabihf": "4.58.0", + "@rollup/rollup-linux-arm64-gnu": "4.58.0", + "@rollup/rollup-linux-arm64-musl": "4.58.0", + "@rollup/rollup-linux-loong64-gnu": "4.58.0", + "@rollup/rollup-linux-loong64-musl": "4.58.0", + "@rollup/rollup-linux-ppc64-gnu": "4.58.0", + "@rollup/rollup-linux-ppc64-musl": "4.58.0", + "@rollup/rollup-linux-riscv64-gnu": "4.58.0", + "@rollup/rollup-linux-riscv64-musl": "4.58.0", + "@rollup/rollup-linux-s390x-gnu": "4.58.0", + "@rollup/rollup-linux-x64-gnu": "4.58.0", + "@rollup/rollup-linux-x64-musl": "4.58.0", + "@rollup/rollup-openbsd-x64": "4.58.0", + "@rollup/rollup-openharmony-arm64": "4.58.0", + "@rollup/rollup-win32-arm64-msvc": "4.58.0", + "@rollup/rollup-win32-ia32-msvc": "4.58.0", + "@rollup/rollup-win32-x64-gnu": "4.58.0", + "@rollup/rollup-win32-x64-msvc": "4.58.0", "fsevents": "~2.3.2" } }, @@ -11424,7 +11489,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/signal-exit": { "version": "3.0.7", @@ -11529,20 +11595,6 @@ "node": ">=4" } }, - "node_modules/sirv": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", - "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", - "dev": true, - "dependencies": { - "@polka/url": "^1.0.0-next.24", - "mrmime": "^2.0.0", - "totalist": "^3.0.0" - }, - "engines": { - "node": ">= 10" - } - }, "node_modules/skin-tone": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", @@ -11619,10 +11671,11 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -11692,7 +11745,8 @@ "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/statuses": { "version": "2.0.1", @@ -11704,10 +11758,11 @@ } }, "node_modules/std-env": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", - "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", - "dev": true + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" }, "node_modules/stdout-update": { "version": "4.0.1", @@ -11908,24 +11963,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strip-literal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.0.0.tgz", - "integrity": "sha512-f9vHgsCWBq2ugHAkGMiiYY+AYG0D/cbloKKg0nhaaaSNsujdGIpVXCNsrJpCKr5M0f4aI31mr13UjY6GAuXCKA==", - "dev": true, - "dependencies": { - "js-tokens": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/strip-literal/node_modules/js-tokens": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-8.0.3.tgz", - "integrity": "sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==", - "dev": true - }, "node_modules/super-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-1.0.0.tgz", @@ -12086,25 +12123,76 @@ } }, "node_modules/tinybench": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.6.0.tgz", - "integrity": "sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==", - "dev": true + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } }, - "node_modules/tinypool": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", - "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, "engines": { - "node": ">=14.0.0" + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinyspy": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", - "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.0.0" } @@ -12130,15 +12218,6 @@ "node": ">=0.6" } }, - "node_modules/totalist": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", - "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/traverse": { "version": "0.6.7", "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.7.tgz", @@ -12242,15 +12321,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -12442,12 +12512,6 @@ "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", "dev": true }, - "node_modules/ufo": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.4.0.tgz", - "integrity": "sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==", - "dev": true - }, "node_modules/uglify-js": { "version": "3.19.0", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.0.tgz", @@ -12605,204 +12669,217 @@ "node": ">= 0.8" } }, - "node_modules/vite": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.4.tgz", - "integrity": "sha512-Cw+7zL3ZG9/NZBB8C+8QbQZmR54GwqIz+WMI4b3JgdYJvX+ny9AjJXqkGQlDXSXRP9rP0B4tbciRMOVEKulVOA==", + "node_modules/vitest": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", + "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", "dev": true, + "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.39", - "rollup": "^4.13.0" + "@vitest/expect": "4.0.18", + "@vitest/mocker": "4.0.18", + "@vitest/pretty-format": "4.0.18", + "@vitest/runner": "4.0.18", + "@vitest/snapshot": "4.0.18", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" }, "bin": { - "vite": "bin/vite.js" + "vitest": "vitest.mjs" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" }, "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" + "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.18", + "@vitest/browser-preview": "4.0.18", + "@vitest/browser-webdriverio": "4.0.18", + "@vitest/ui": "4.0.18", + "happy-dom": "*", + "jsdom": "*" }, "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, "@types/node": { "optional": true }, - "less": { + "@vitest/browser-playwright": { "optional": true }, - "lightningcss": { + "@vitest/browser-preview": { "optional": true }, - "sass": { + "@vitest/browser-webdriverio": { "optional": true }, - "stylus": { + "@vitest/ui": { "optional": true }, - "sugarss": { + "happy-dom": { "optional": true }, - "terser": { + "jsdom": { "optional": true } } }, - "node_modules/vite-node": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.0.tgz", - "integrity": "sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==", + "node_modules/vitest/node_modules/@vitest/mocker": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz", + "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==", "dev": true, + "license": "MIT", "dependencies": { - "cac": "^6.7.14", - "debug": "^4.3.4", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "vite": "^5.0.0" + "@vitest/spy": "4.0.18", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" }, - "bin": { - "vite-node": "vite-node.mjs" + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": ">=12" }, "funding": { - "url": "https://opencollective.com/vitest" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/vitest": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.0.tgz", - "integrity": "sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==", + "node_modules/vitest/node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/expect": "1.6.0", - "@vitest/runner": "1.6.0", - "@vitest/snapshot": "1.6.0", - "@vitest/spy": "1.6.0", - "@vitest/utils": "1.6.0", - "acorn-walk": "^8.3.2", - "chai": "^4.3.10", - "debug": "^4.3.4", - "execa": "^8.0.1", - "local-pkg": "^0.5.0", - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "std-env": "^3.5.0", - "strip-literal": "^2.0.0", - "tinybench": "^2.5.1", - "tinypool": "^0.8.3", - "vite": "^5.0.0", - "vite-node": "1.6.0", - "why-is-node-running": "^2.2.2" + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" }, "bin": { - "vitest": "vitest.mjs" + "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { - "url": "https://opencollective.com/vitest" + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" }, "peerDependencies": { - "@edge-runtime/vm": "*", - "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "1.6.0", - "@vitest/ui": "1.6.0", - "happy-dom": "*", - "jsdom": "*" + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { - "@edge-runtime/vm": { + "@types/node": { "optional": true }, - "@types/node": { + "jiti": { "optional": true }, - "@vitest/browser": { + "less": { "optional": true }, - "@vitest/ui": { + "lightningcss": { "optional": true }, - "happy-dom": { + "sass": { "optional": true }, - "jsdom": { + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { "optional": true } } }, - "node_modules/vitest/node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/vitest/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "engines": { - "node": ">=16.17.0" - } - }, - "node_modules/vitest/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -12854,10 +12931,11 @@ } }, "node_modules/why-is-node-running": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", - "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, + "license": "MIT", "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" @@ -12940,12 +13018,12 @@ "dev": true }, "node_modules/xmlhttprequest-ssl": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.1.tgz", - "integrity": "sha512-ptjR8YSJIXoA3Mbv5po7RtSYHO6mZr8s7i5VGmEk7QY2pQWyT1o0N+W1gKbOyJPUCGXGnuw0wqe8f0L6Y0ny7g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-4.0.0.tgz", + "integrity": "sha512-b7DXzbCm8VWmII2mQiQHy5VG1L6YBUnNxuCooldWpMUXTwa08uXEz1q5Nv6wlULnSI5GiHuwBIq6pNJrqRh/8Q==", "dev": true, "engines": { - "node": ">=0.4.0" + "node": ">=13.0.0" } }, "node_modules/xtend": { diff --git a/package.json b/package.json index b216ff0..4e939a8 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,6 @@ "@types/stream-throttle": "^0.1.4", "@typescript-eslint/eslint-plugin": "^6.3.0", "@typescript-eslint/parser": "^6.3.0", - "@vitest/ui": "^1.6.0", "dotenv": "^16.3.1", "eslint": "^8.46.0", "eslint-plugin-import": "^2.28.0", @@ -125,8 +124,8 @@ "typedoc-material-theme": "^1.1.0", "typedoc-plugin-missing-exports": "^3.0.0", "typescript": "^5.5.3", - "vitest": "^1.6.0", - "xmlhttprequest-ssl": "^2.1.1" + "vitest": "^4.0.18", + "xmlhttprequest-ssl": "^4.0.0" }, "optionalDependencies": { "@reflink/reflink": "^0.1.16" diff --git a/test/browser.test.ts b/test/browser.test.ts index 251e4b5..157f483 100644 --- a/test/browser.test.ts +++ b/test/browser.test.ts @@ -19,10 +19,10 @@ describe("Browser Fetch API", () => { await downloader.download(); const hash = hashBuffer(downloader.writeStream.result); context.expect(hash) - .toMatchInlineSnapshot("\"9ae3ff19ee04fc02e9c60ce34e42858d16b46eeb88634d2035693c1ae9dbcbc9\""); + .toMatchInlineSnapshot(`"0e1a20347e130a168a5c555826915a1302e3fae467b85db6aae53c243c2b0a26"`); }); - test.concurrent("Download file browser", async (context) => { + test.concurrent("Download file browser", {repeats: 4, concurrent: true}, async (context) => { const response = await ensureLocalFile(BIG_FILE, BIG_FILE_EXAMPLE); const bufferIsCorrect = Buffer.from(await fs.readFile(response)); @@ -55,9 +55,9 @@ describe("Browser Fetch API", () => { context.expect(lastWrite) .toBe(downloader.file.totalSize); context.expect(hashBuffer(bigBuffer)) - .toMatchInlineSnapshot("\"9ae3ff19ee04fc02e9c60ce34e42858d16b46eeb88634d2035693c1ae9dbcbc9\""); - }, {repeats: 4, concurrent: true}); -}, {timeout: 1000 * 60 * 3}); + .toMatchInlineSnapshot(`"0e1a20347e130a168a5c555826915a1302e3fae467b85db6aae53c243c2b0a26"`); + }); +}); describe("Browser Fetch memory", () => { test.sequential("Download file for tests", async (context) => { @@ -66,10 +66,10 @@ describe("Browser Fetch memory", () => { const buffer = Buffer.from(await fs.readFile(response)); const hash = hashBuffer(buffer); context.expect(hash) - .toMatchInlineSnapshot("\"9ae3ff19ee04fc02e9c60ce34e42858d16b46eeb88634d2035693c1ae9dbcbc9\""); + .toMatchInlineSnapshot(`"0e1a20347e130a168a5c555826915a1302e3fae467b85db6aae53c243c2b0a26"`); }); - test.sequential("Download file browser - memory (xhr)", async (context) => { + test.skip("Download file browser - memory (xhr)", async (context) => { const originalFile = await ensureLocalFile(BIG_FILE, BIG_FILE_EXAMPLE); const originalFileBuffer = new Uint8Array(await fs.readFile(originalFile)); @@ -107,4 +107,4 @@ describe("Browser Fetch memory", () => { context.expect(diff) .toBe(-1); }); -}, {timeout: 1000 * 60 * 3}); +}); diff --git a/test/copy-file.test.ts b/test/copy-file.test.ts index b867404..5a92102 100644 --- a/test/copy-file.test.ts +++ b/test/copy-file.test.ts @@ -49,4 +49,4 @@ describe("File Copy", async () => { context.expect(copiedFileHash) .toBe(originalFileHash); }); -}, {timeout: 1000 * 60 * 3}); +}); diff --git a/test/download.test.ts b/test/download.test.ts index 53099c3..13b0744 100644 --- a/test/download.test.ts +++ b/test/download.test.ts @@ -22,7 +22,7 @@ describe("File Download", () => { let maxInParallelConnections = 0; const downloader = new DownloadEngineFile(file, { parallelStreams: randomNumber, - chunkSize: 1024 ** 2, + chunkSize: 1024 ** 1.5, autoIncreaseParallelStreams: false, fetchStream, writeStream @@ -61,4 +61,4 @@ describe("File Download", () => { context.expect(totalBytesWritten) .toBe(file.totalSize); }); -}, {timeout: 1000 * 60 * 3}); +}); diff --git a/test/fetchDownloadInfo.test.ts b/test/fetchDownloadInfo.test.ts index ba8a5b2..0cde6ec 100644 --- a/test/fetchDownloadInfo.test.ts +++ b/test/fetchDownloadInfo.test.ts @@ -87,4 +87,4 @@ describe("Fetch download info", () => { context.expect(fileSize) .toMatchInlineSnapshot(`599171040`); }); -}, {timeout: 0}); +}); diff --git a/test/utils/download.ts b/test/utils/download.ts index 4ebc447..4796264 100644 --- a/test/utils/download.ts +++ b/test/utils/download.ts @@ -9,7 +9,7 @@ import DownloadEngineFetchStreamFetch from "../../src/download/download-engine/s import {withLock} from "lifecycle-utils"; const __dirname = fileURLToPath(new URL(".", import.meta.url)); -export const BIG_FILE_EXAMPLE = path.join(__dirname, "files", "big-file.jpg"); +export const BIG_FILE_EXAMPLE = path.join(__dirname, "files", "big-file.bin"); export const TEXT_FILE_EXAMPLE = path.join(__dirname, "files", "example.txt"); const lockScope = {}; @@ -38,7 +38,9 @@ export async function createDownloadFile(file = BIG_FILE, fetchStream: BaseDownl { downloadURL: file, acceptRange: fileInfo.acceptRange, - size: fileInfo.length + size: fileInfo.length, + originalURL: file, + downloadURLUpdateDate: Date.now() } ] }; diff --git a/test/utils/files.ts b/test/utils/files.ts index 125fb95..1f5aeca 100644 --- a/test/utils/files.ts +++ b/test/utils/files.ts @@ -1 +1 @@ -export const BIG_FILE = "https://upload.wikimedia.org/wikipedia/commons/9/9e/1_dubrovnik_pano_-_edit1.jpg"; // 40mb +export const BIG_FILE = "https://huggingface.co/datasets/ChaoticNeutrals/Creative_Writing-ShareGPT/resolve/main/Creative_Writing-ShareGPT.jsonl?download=true"; // 27.5mb diff --git a/vitest.config.ts b/vitest.config.ts index a2a07e9..1738914 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -3,13 +3,6 @@ import {defineConfig} from "vitest/config"; export default defineConfig({ test: { pool: "threads", - maxWorkers: 1, - minWorkers: 1, - poolOptions: { - threads: { - minThreads: 1, - maxThreads: 1 - } - } + testTimeout: 1000 * 60 * 3 } }); From bcd187084f323d8eb3389ce4aaa4ffbfe3ea9731 Mon Sep 17 00:00:00 2001 From: ido Date: Fri, 20 Feb 2026 16:50:04 +0200 Subject: [PATCH 12/33] fix: release --- .github/workflows/build.yml | 1 - package-lock.json | 3030 ++++++++++++++++------------------- package.json | 2 +- 3 files changed, 1396 insertions(+), 1637 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7a3d70a..caa787d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -66,7 +66,6 @@ jobs: - name: Release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} run: npx semantic-release - name: Set npm package url to GITHUB_OUTPUT diff --git a/package-lock.json b/package-lock.json index 010a9a3..7f74616 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,7 +54,7 @@ "express": "^4.21.1", "hash.js": "^1.1.7", "husky": "^8.0.3", - "semantic-release": "^24.0.0", + "semantic-release": "^25.0.3", "stream-throttle": "^0.1.3", "tslib": "^2.6.1", "typedoc": "^0.26.3", @@ -84,112 +84,78 @@ "node": ">=0.10.0" } }, - "node_modules/@babel/code-frame": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", - "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "node_modules/@actions/core": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-3.0.0.tgz", + "integrity": "sha512-zYt6cz+ivnTmiT/ksRVriMBOiuoUpDCJJlZ5KPl2/FRdvwU3f7MPh9qftvbkXJThragzUZieit2nyHUyw53Seg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.24.7", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", - "dev": true, - "engines": { - "node": ">=6.9.0" + "@actions/exec": "^3.0.0", + "@actions/http-client": "^4.0.0" } }, - "node_modules/@babel/highlight": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", - "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "node_modules/@actions/exec": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-3.0.0.tgz", + "integrity": "sha512-6xH/puSoNBXb72VPlZVm7vQ+svQpFyA96qdDBvhB8eNZOE8LtPf9L4oAsfzK/crCL8YZ+19fKYVnM63Sl+Xzlw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.24.7", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" + "@actions/io": "^3.0.2" } }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/@actions/http-client": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-4.0.0.tgz", + "integrity": "sha512-QuwPsgVMsD6qaPD57GLZi9sqzAZCtiJT8kVBCDpLtxhL5MydQ4gS+DrejtZZPdIYyB1e95uCK9Luyds7ybHI3g==", "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" + "tunnel": "^0.0.6", + "undici": "^6.23.0" } }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/@actions/http-client/node_modules/undici": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz", + "integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==", "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, + "license": "MIT", "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" + "node": ">=18.17" } }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "node_modules/@actions/io": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@actions/io/-/io-3.0.2.tgz", + "integrity": "sha512-nRBchcMM+QK1pdjO7/idu86rbJI5YHUKCvKs0KxnSYbVe3F51UfGxuZX4Qy/fWlp6l7gWFwIkrOzN+oUK03kfw==", "dev": true, - "engines": { - "node": ">=0.8.0" - } + "license": "MIT" }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, "engines": { - "node": ">=4" + "node": ">=6.9.0" } }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, + "license": "MIT", "engines": { - "node": ">=4" + "node": ">=6.9.0" } }, "node_modules/@colors/colors": { @@ -197,6 +163,7 @@ "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "dev": true, + "license": "MIT", "optional": true, "engines": { "node": ">=0.1.90" @@ -1363,147 +1330,159 @@ } }, "node_modules/@octokit/auth-token": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.1.tgz", - "integrity": "sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", + "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@octokit/core": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.2.tgz", - "integrity": "sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.6.tgz", + "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", "dev": true, + "license": "MIT", "dependencies": { - "@octokit/auth-token": "^5.0.0", - "@octokit/graphql": "^8.0.0", - "@octokit/request": "^9.0.0", - "@octokit/request-error": "^6.0.1", - "@octokit/types": "^13.0.0", - "before-after-hook": "^3.0.2", + "@octokit/auth-token": "^6.0.0", + "@octokit/graphql": "^9.0.3", + "@octokit/request": "^10.0.6", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "before-after-hook": "^4.0.0", "universal-user-agent": "^7.0.0" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@octokit/endpoint": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz", - "integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.3.tgz", + "integrity": "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==", "dev": true, + "license": "MIT", "dependencies": { - "@octokit/types": "^13.0.0", + "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@octokit/graphql": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.1.1.tgz", - "integrity": "sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.3.tgz", + "integrity": "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==", "dev": true, + "license": "MIT", "dependencies": { - "@octokit/request": "^9.0.0", - "@octokit/types": "^13.0.0", + "@octokit/request": "^10.0.6", + "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.0" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@octokit/openapi-types": { - "version": "22.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", - "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==", - "dev": true + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", + "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", + "dev": true, + "license": "MIT" }, "node_modules/@octokit/plugin-paginate-rest": { - "version": "11.3.3", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.3.3.tgz", - "integrity": "sha512-o4WRoOJZlKqEEgj+i9CpcmnByvtzoUYC6I8PD2SA95M+BJ2x8h7oLcVOg9qcowWXBOdcTRsMZiwvM3EyLm9AfA==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-14.0.0.tgz", + "integrity": "sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==", "dev": true, + "license": "MIT", "dependencies": { - "@octokit/types": "^13.5.0" + "@octokit/types": "^16.0.0" }, "engines": { - "node": ">= 18" + "node": ">= 20" }, "peerDependencies": { "@octokit/core": ">=6" } }, "node_modules/@octokit/plugin-retry": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-7.1.1.tgz", - "integrity": "sha512-G9Ue+x2odcb8E1XIPhaFBnTTIrrUDfXN05iFXiqhR+SeeeDMMILcAnysOsxUpEWcQp2e5Ft397FCXTcPkiPkLw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-8.1.0.tgz", + "integrity": "sha512-O1FZgXeiGb2sowEr/hYTr6YunGdSAFWnr2fyW39Ah85H8O33ELASQxcvOFF5LE6Tjekcyu2ms4qAzJVhSaJxTw==", "dev": true, + "license": "MIT", "dependencies": { - "@octokit/request-error": "^6.0.0", - "@octokit/types": "^13.0.0", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", "bottleneck": "^2.15.3" }, "engines": { - "node": ">= 18" + "node": ">= 20" }, "peerDependencies": { - "@octokit/core": ">=6" + "@octokit/core": ">=7" } }, "node_modules/@octokit/plugin-throttling": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-9.3.1.tgz", - "integrity": "sha512-Qd91H4liUBhwLB2h6jZ99bsxoQdhgPk6TdwnClPyTBSDAdviGPceViEgUwj+pcQDmB/rfAXAXK7MTochpHM3yQ==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-11.0.3.tgz", + "integrity": "sha512-34eE0RkFCKycLl2D2kq7W+LovheM/ex3AwZCYN8udpi6bxsyjZidb2McXs69hZhLmJlDqTSP8cH+jSRpiaijBg==", "dev": true, + "license": "MIT", "dependencies": { - "@octokit/types": "^13.0.0", + "@octokit/types": "^16.0.0", "bottleneck": "^2.15.3" }, "engines": { - "node": ">= 18" + "node": ">= 20" }, "peerDependencies": { - "@octokit/core": "^6.0.0" + "@octokit/core": "^7.0.0" } }, "node_modules/@octokit/request": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.3.tgz", - "integrity": "sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA==", + "version": "10.0.7", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.7.tgz", + "integrity": "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA==", "dev": true, + "license": "MIT", "dependencies": { - "@octokit/endpoint": "^10.0.0", - "@octokit/request-error": "^6.0.1", - "@octokit/types": "^13.1.0", + "@octokit/endpoint": "^11.0.2", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@octokit/request-error": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.4.tgz", - "integrity": "sha512-VpAhIUxwhWZQImo/dWAN/NpPqqojR6PSLgLYAituLM6U+ddx9hCioFGwBr5Mi+oi5CLeJkcAs3gJ0PYYzU6wUg==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz", + "integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==", "dev": true, + "license": "MIT", "dependencies": { - "@octokit/types": "^13.0.0" + "@octokit/types": "^16.0.0" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@octokit/types": { - "version": "13.5.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.5.0.tgz", - "integrity": "sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==", + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", + "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", "dev": true, + "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^22.2.0" + "@octokit/openapi-types": "^27.0.0" } }, "node_modules/@pnpm/config.env-replace": { @@ -1511,6 +1490,7 @@ "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", "dev": true, + "license": "MIT", "engines": { "node": ">=12.22.0" } @@ -1520,6 +1500,7 @@ "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", "dev": true, + "license": "MIT", "dependencies": { "graceful-fs": "4.2.10" }, @@ -1531,13 +1512,15 @@ "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/@pnpm/npm-conf": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-2.2.2.tgz", - "integrity": "sha512-UA91GwWPhFExt3IizW6bOeY/pQ0BkuNwKjk9iQW9KqxluGCrg4VenZ0/L+2Y0+ZOtme72EVvg6v0zo3AMQRCeA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-3.0.2.tgz", + "integrity": "sha512-h104Kh26rR8tm+a3Qkc5S4VLYint3FE48as7+/5oCEcKR2idC/pF1G6AhIXKI+eHPJa/3J9i5z0Al47IeGHPkA==", "dev": true, + "license": "MIT", "dependencies": { "@pnpm/config.env-replace": "^1.1.0", "@pnpm/network.ca-file": "^1.0.1", @@ -2040,20 +2023,22 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@semantic-release/commit-analyzer": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-13.0.0.tgz", - "integrity": "sha512-KtXWczvTAB1ZFZ6B4O+w8HkfYm/OgQb1dUGNFZtDgQ0csggrmkq8sTxhd+lwGF8kMb59/RnG9o4Tn7M/I8dQ9Q==", + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-13.0.1.tgz", + "integrity": "sha512-wdnBPHKkr9HhNhXOhZD5a2LNl91+hs8CC2vsAVYxtZH3y0dV3wKn+uZSN61rdJQZ8EGxzWB3inWocBHV9+u/CQ==", "dev": true, + "license": "MIT", "dependencies": { "conventional-changelog-angular": "^8.0.0", "conventional-changelog-writer": "^8.0.0", "conventional-commits-filter": "^5.0.0", "conventional-commits-parser": "^6.0.0", "debug": "^4.0.0", - "import-from-esm": "^1.0.3", + "import-from-esm": "^2.0.0", "lodash-es": "^4.17.21", "micromatch": "^4.0.2" }, @@ -2065,10 +2050,11 @@ } }, "node_modules/@semantic-release/commit-analyzer/node_modules/conventional-changelog-angular": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.0.0.tgz", - "integrity": "sha512-CLf+zr6St0wIxos4bmaKHRXWAcsCXrJU6F4VdNDrGRK3B8LDLKoX3zuMV5GhtbGkVR/LohZ6MT6im43vZLSjmA==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.1.0.tgz", + "integrity": "sha512-GGf2Nipn1RUCAktxuVauVr1e3r8QrLP/B0lEUsFktmGqc3ddbQkhoJZHJctVU829U1c6mTSWftrVOCHaL85Q3w==", "dev": true, + "license": "ISC", "dependencies": { "compare-func": "^2.0.0" }, @@ -2077,10 +2063,11 @@ } }, "node_modules/@semantic-release/commit-analyzer/node_modules/conventional-commits-parser": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.0.0.tgz", - "integrity": "sha512-TbsINLp48XeMXR8EvGjTnKGsZqBemisPoyWESlpRyR8lif0lcwzqz+NMtYSj1ooF/WYjSuu7wX0CtdeeMEQAmA==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.2.1.tgz", + "integrity": "sha512-20pyHgnO40rvfI0NGF/xiEoFMkXDtkF8FwHvk5BokoFoCuTQRI8vrNCNFWUOfuolKJMm1tPCHc8GgYEtr1XRNA==", "dev": true, + "license": "MIT", "dependencies": { "meow": "^13.0.0" }, @@ -2096,6 +2083,7 @@ "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -2108,6 +2096,7 @@ "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" } @@ -2262,123 +2251,73 @@ } }, "node_modules/@semantic-release/github": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-10.1.0.tgz", - "integrity": "sha512-g4RHBaCWJjGcEy95TeTdajlmUoP5jAaF5trGkFXHKsT/VpCwawhZbNW66+sUr0c2CIAdfpCxxmK+E7GyWBWJDw==", + "version": "12.0.6", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-12.0.6.tgz", + "integrity": "sha512-aYYFkwHW3c6YtHwQF0t0+lAjlU+87NFOZuH2CvWFD0Ylivc7MwhZMiHOJ0FMpIgPpCVib/VUAcOwvrW0KnxQtA==", "dev": true, + "license": "MIT", "dependencies": { - "@octokit/core": "^6.0.0", - "@octokit/plugin-paginate-rest": "^11.0.0", - "@octokit/plugin-retry": "^7.0.0", - "@octokit/plugin-throttling": "^9.0.0", + "@octokit/core": "^7.0.0", + "@octokit/plugin-paginate-rest": "^14.0.0", + "@octokit/plugin-retry": "^8.0.0", + "@octokit/plugin-throttling": "^11.0.0", "@semantic-release/error": "^4.0.0", "aggregate-error": "^5.0.0", "debug": "^4.3.4", "dir-glob": "^3.0.1", - "globby": "^14.0.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "issue-parser": "^7.0.0", "lodash-es": "^4.17.21", "mime": "^4.0.0", "p-filter": "^4.0.0", + "tinyglobby": "^0.2.14", + "undici": "^7.0.0", "url-join": "^5.0.0" }, "engines": { - "node": ">=20.8.1" + "node": "^22.14.0 || >= 24.10.0" }, "peerDependencies": { - "semantic-release": ">=20.1.0" - } - }, - "node_modules/@semantic-release/github/node_modules/@sindresorhus/merge-streams": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", - "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@semantic-release/github/node_modules/globby": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", - "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", - "dev": true, - "dependencies": { - "@sindresorhus/merge-streams": "^2.1.0", - "fast-glob": "^3.3.2", - "ignore": "^5.2.4", - "path-type": "^5.0.0", - "slash": "^5.1.0", - "unicorn-magic": "^0.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@semantic-release/github/node_modules/path-type": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", - "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@semantic-release/github/node_modules/slash": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", - "dev": true, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "semantic-release": ">=24.1.0" } }, "node_modules/@semantic-release/npm": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-12.0.1.tgz", - "integrity": "sha512-/6nntGSUGK2aTOI0rHPwY3ZjgY9FkXmEHbW9Kr+62NVOsyqpKKeP0lrCH+tphv+EsNdJNmqqwijTEnVWUMQ2Nw==", + "version": "13.1.4", + "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-13.1.4.tgz", + "integrity": "sha512-z5Fn9ftK1QQgFxMSuOd3DtYbTl4hWI2trCEvZcEJMQJy1/OBR0WHcxqzfVun455FSkHML8KgvPxJEa9MtZIBsg==", "dev": true, + "license": "MIT", "dependencies": { + "@actions/core": "^3.0.0", "@semantic-release/error": "^4.0.0", "aggregate-error": "^5.0.0", + "env-ci": "^11.2.0", "execa": "^9.0.0", "fs-extra": "^11.0.0", "lodash-es": "^4.17.21", "nerf-dart": "^1.0.0", "normalize-url": "^8.0.0", - "npm": "^10.5.0", + "npm": "^11.6.2", "rc": "^1.2.8", - "read-pkg": "^9.0.0", + "read-pkg": "^10.0.0", "registry-auth-token": "^5.0.0", "semver": "^7.1.2", "tempy": "^3.0.0" }, "engines": { - "node": ">=20.8.1" + "node": "^22.14.0 || >= 24.10.0" }, "peerDependencies": { "semantic-release": ">=20.1.0" } }, "node_modules/@semantic-release/release-notes-generator": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-14.0.1.tgz", - "integrity": "sha512-K0w+5220TM4HZTthE5dDpIuFrnkN1NfTGPidJFm04ULT1DEZ9WG89VNXN7F0c+6nMEpWgqmPvb7vY7JkB2jyyA==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-14.1.0.tgz", + "integrity": "sha512-CcyDRk7xq+ON/20YNR+1I/jP7BYKICr1uKd1HHpROSnnTdGqOTburi4jcRiTYz0cpfhxSloQO3cGhnoot7IEkA==", "dev": true, + "license": "MIT", "dependencies": { "conventional-changelog-angular": "^8.0.0", "conventional-changelog-writer": "^8.0.0", @@ -2386,7 +2325,7 @@ "conventional-commits-parser": "^6.0.0", "debug": "^4.0.0", "get-stream": "^7.0.0", - "import-from-esm": "^1.0.3", + "import-from-esm": "^2.0.0", "into-stream": "^7.0.0", "lodash-es": "^4.17.21", "read-package-up": "^11.0.0" @@ -2399,10 +2338,11 @@ } }, "node_modules/@semantic-release/release-notes-generator/node_modules/conventional-changelog-angular": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.0.0.tgz", - "integrity": "sha512-CLf+zr6St0wIxos4bmaKHRXWAcsCXrJU6F4VdNDrGRK3B8LDLKoX3zuMV5GhtbGkVR/LohZ6MT6im43vZLSjmA==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.1.0.tgz", + "integrity": "sha512-GGf2Nipn1RUCAktxuVauVr1e3r8QrLP/B0lEUsFktmGqc3ddbQkhoJZHJctVU829U1c6mTSWftrVOCHaL85Q3w==", "dev": true, + "license": "ISC", "dependencies": { "compare-func": "^2.0.0" }, @@ -2411,10 +2351,11 @@ } }, "node_modules/@semantic-release/release-notes-generator/node_modules/conventional-commits-parser": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.0.0.tgz", - "integrity": "sha512-TbsINLp48XeMXR8EvGjTnKGsZqBemisPoyWESlpRyR8lif0lcwzqz+NMtYSj1ooF/WYjSuu7wX0CtdeeMEQAmA==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.2.1.tgz", + "integrity": "sha512-20pyHgnO40rvfI0NGF/xiEoFMkXDtkF8FwHvk5BokoFoCuTQRI8vrNCNFWUOfuolKJMm1tPCHc8GgYEtr1XRNA==", "dev": true, + "license": "MIT", "dependencies": { "meow": "^13.0.0" }, @@ -2430,6 +2371,7 @@ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-7.0.1.tgz", "integrity": "sha512-3M8C1EOFN6r8AMUhwUAACIoXZJEOufDU5+0gFFN5uNs6XYOralD2Pqkl7m046va6x77FwposWXbAhPPIOus7mQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=16" }, @@ -2437,11 +2379,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@semantic-release/release-notes-generator/node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@semantic-release/release-notes-generator/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/@semantic-release/release-notes-generator/node_modules/meow": { "version": "13.2.0", "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -2449,32 +2412,50 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@shikijs/core": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.10.3.tgz", - "integrity": "sha512-D45PMaBaeDHxww+EkcDQtDAtzv00Gcsp72ukBtaLSmqRvh0WgGMq3Al0rl1QQBZfuneO75NXMIzEZGFitThWbg==", + "node_modules/@semantic-release/release-notes-generator/node_modules/normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "@types/hast": "^3.0.4" + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "node_modules/@semantic-release/release-notes-generator/node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@sindresorhus/merge-streams": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", - "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "node_modules/@semantic-release/release-notes-generator/node_modules/read-package-up": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz", + "integrity": "sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==", "dev": true, + "license": "MIT", + "dependencies": { + "find-up-simple": "^1.0.0", + "read-pkg": "^9.0.0", + "type-fest": "^4.6.0" + }, "engines": { "node": ">=18" }, @@ -2482,16 +2463,97 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "node_modules/@semantic-release/release-notes-generator/node_modules/read-pkg": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", + "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", "dev": true, - "license": "MIT" - }, - "node_modules/@tinyhttp/content-disposition": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@tinyhttp/content-disposition/-/content-disposition-2.2.0.tgz", + "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.3", + "normalize-package-data": "^6.0.0", + "parse-json": "^8.0.0", + "type-fest": "^4.6.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/release-notes-generator/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/release-notes-generator/node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@shikijs/core": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.10.3.tgz", + "integrity": "sha512-D45PMaBaeDHxww+EkcDQtDAtzv00Gcsp72ukBtaLSmqRvh0WgGMq3Al0rl1QQBZfuneO75NXMIzEZGFitThWbg==", + "dev": true, + "dependencies": { + "@types/hast": "^3.0.4" + } + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tinyhttp/content-disposition": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@tinyhttp/content-disposition/-/content-disposition-2.2.0.tgz", "integrity": "sha512-w1dJaSAtcCinOlT/YQg35RnFCOBbCHBGDVhH4yLoiJVtecRAJ2cYMf5HP+UhfbXURa38GC8fkRXO0vODDTjmeg==", "engines": { "node": ">=12.20.0" @@ -3065,13 +3127,11 @@ } }, "node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "dev": true, - "dependencies": { - "debug": "^4.3.4" - }, + "license": "MIT", "engines": { "node": ">= 14" } @@ -3081,6 +3141,7 @@ "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", "dev": true, + "license": "MIT", "dependencies": { "clean-stack": "^5.2.0", "indent-string": "^5.0.0" @@ -3134,9 +3195,10 @@ } }, "node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", "engines": { "node": ">=12" }, @@ -3163,7 +3225,8 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/are-docs-informative": { "version": "0.0.2", @@ -3366,10 +3429,11 @@ "dev": true }, "node_modules/before-after-hook": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", - "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==", - "dev": true + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", + "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", + "dev": true, + "license": "Apache-2.0" }, "node_modules/body-parser": { "version": "1.20.3", @@ -3414,7 +3478,8 @@ "version": "2.19.5", "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/brace-expansion": { "version": "1.1.11", @@ -3533,9 +3598,10 @@ } }, "node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" }, @@ -3548,6 +3614,7 @@ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" } @@ -3567,10 +3634,11 @@ } }, "node_modules/clean-stack": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.2.0.tgz", - "integrity": "sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.3.0.tgz", + "integrity": "sha512-9ngPTOhYGQqNVSfeJkYXHmF7AGWp4/nN5D/QqNQs3Dvxd1Kk/WpjHfNujKHYUQ/5CoGyOyFNoWSPk5afzP0QVg==", "dev": true, + "license": "MIT", "dependencies": { "escape-string-regexp": "5.0.0" }, @@ -3586,6 +3654,7 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -3598,6 +3667,7 @@ "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", "dev": true, + "license": "ISC", "dependencies": { "chalk": "^4.0.0", "highlight.js": "^10.7.1", @@ -3619,6 +3689,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -3628,6 +3699,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -3644,6 +3716,7 @@ "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, + "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -3654,13 +3727,15 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/cli-highlight/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -3675,6 +3750,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -3687,6 +3763,7 @@ "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, + "license": "MIT", "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -3716,6 +3793,7 @@ "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", "dev": true, + "license": "MIT", "dependencies": { "string-width": "^4.2.0" }, @@ -3731,6 +3809,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -3739,13 +3818,15 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/cli-table3/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -3760,6 +3841,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -3878,6 +3960,7 @@ "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", "dev": true, + "license": "MIT", "dependencies": { "ini": "^1.3.4", "proto-list": "~1.2.1" @@ -3949,12 +4032,12 @@ } }, "node_modules/conventional-changelog-writer": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-8.0.0.tgz", - "integrity": "sha512-TQcoYGRatlAnT2qEWDON/XSfnVG38JzA7E0wcGScu7RElQBkg9WWgZd1peCWFcWDh1xfb2CfsrcvOn1bbSzztA==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-8.2.0.tgz", + "integrity": "sha512-Y2aW4596l9AEvFJRwFGJGiQjt2sBYTjPD18DdvxX9Vpz0Z7HQ+g1Z+6iYDAm1vR3QOJrDBkRHixHK/+FhkR6Pw==", "dev": true, + "license": "MIT", "dependencies": { - "@types/semver": "^7.5.5", "conventional-commits-filter": "^5.0.0", "handlebars": "^4.7.7", "meow": "^13.0.0", @@ -3972,6 +4055,7 @@ "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -3984,6 +4068,7 @@ "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-5.0.0.tgz", "integrity": "sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" } @@ -4079,10 +4164,11 @@ "dev": true }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -4097,6 +4183,7 @@ "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", "dev": true, + "license": "MIT", "dependencies": { "type-fest": "^1.0.1" }, @@ -4112,6 +4199,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -4184,6 +4272,7 @@ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true, + "license": "MIT", "engines": { "node": ">=4.0.0" } @@ -4327,7 +4416,8 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/encodeurl": { "version": "2.0.0", @@ -4351,10 +4441,11 @@ } }, "node_modules/env-ci": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-11.0.0.tgz", - "integrity": "sha512-apikxMgkipkgTvMdRT9MNqWx5VLOci79F4VBd7Op/7OPjjoanjdAvn6fglMCCEf/1bAh8eOiuEVCUs4V3qP3nQ==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-11.2.0.tgz", + "integrity": "sha512-D5kWfzkmaOQDioPmiviWAVtKmpPT4/iJmMVQxWxMPJTFyTkdc5JQUfc5iXEeWxcOdsYTKSAiA/Age4NUOqKsRA==", "dev": true, + "license": "MIT", "dependencies": { "execa": "^8.0.0", "java-properties": "^1.0.2" @@ -4433,6 +4524,7 @@ "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -5141,23 +5233,24 @@ "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" }, "node_modules/execa": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-9.3.0.tgz", - "integrity": "sha512-l6JFbqnHEadBoVAVpN5dl2yCyfX28WoBAGaoQcNmLLSedOxTxcn2Qa83s8I/PA5i56vWru2OHOtrwF7Om2vqlg==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", "dev": true, + "license": "MIT", "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", - "cross-spawn": "^7.0.3", + "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", - "human-signals": "^7.0.0", + "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", - "npm-run-path": "^5.2.0", - "pretty-ms": "^9.0.0", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", - "yoctocolors": "^2.0.0" + "yoctocolors": "^2.1.1" }, "engines": { "node": "^18.19.0 || >=20.5.0" @@ -5171,6 +5264,7 @@ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", "dev": true, + "license": "MIT", "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" @@ -5187,6 +5281,7 @@ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -5199,6 +5294,24 @@ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, "engines": { "node": ">=18" }, @@ -5211,6 +5324,7 @@ "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -5218,11 +5332,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/execa/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/execa/node_modules/pretty-ms": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.0.0.tgz", - "integrity": "sha512-E9e9HJ9R9NasGOgPaPE8VMeiPKAyWR5jcFpNnwIejslIhWqdqOrb2wShBsncMPUb+BcCd2OPYfh7p2W6oemTng==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", + "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", "dev": true, + "license": "MIT", "dependencies": { "parse-ms": "^4.0.0" }, @@ -5238,6 +5366,7 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, + "license": "ISC", "engines": { "node": ">=14" }, @@ -5250,6 +5379,20 @@ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -5344,6 +5487,23 @@ } ] }, + "node_modules/fast-content-type-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", + "integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -5404,6 +5564,7 @@ "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", "dev": true, + "license": "MIT", "dependencies": { "is-unicode-supported": "^2.0.0" }, @@ -5513,10 +5674,11 @@ } }, "node_modules/find-up-simple": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.0.tgz", - "integrity": "sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz", + "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -5591,6 +5753,7 @@ "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", "dev": true, + "license": "MIT", "dependencies": { "inherits": "^2.0.1", "readable-stream": "^2.0.0" @@ -5944,6 +6107,7 @@ "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "dev": true, + "license": "MIT", "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", @@ -6077,40 +6241,37 @@ "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": "*" } }, "node_modules/hook-std": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-3.0.0.tgz", - "integrity": "sha512-jHRQzjSDzMtFy34AGj1DN+vq54WVuhSvKgrHf0OMiFQTwDD4L/qqofVEWjLOBMTn5+lCD3fPg32W9yOfnEJTTw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-4.0.0.tgz", + "integrity": "sha512-IHI4bEVOt3vRUDJ+bFA9VUJlo7SzvFARPNLw75pqSmAOP2HmTWfFJtPvLBrDrlgjEYXY9zs7SFdHPQaJShkSCQ==", "dev": true, + "license": "MIT", "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/hosted-git-info": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", - "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", + "integrity": "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==", "dev": true, + "license": "ISC", "dependencies": { - "lru-cache": "^10.0.1" + "lru-cache": "^11.1.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true - }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -6132,6 +6293,7 @@ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, + "license": "MIT", "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" @@ -6141,12 +6303,13 @@ } }, "node_modules/https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, + "license": "MIT", "dependencies": { - "agent-base": "^7.0.2", + "agent-base": "^7.1.2", "debug": "4" }, "engines": { @@ -6154,10 +6317,11 @@ } }, "node_modules/human-signals": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-7.0.0.tgz", - "integrity": "sha512-74kytxOUSvNbjrT9KisAbaTZ/eJwD/LrbM/kh5j0IhPuJzwuA19dWvniFGwBzN9rVjg+O/e+F310PjObDXS+9Q==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=18.18.0" } @@ -6215,23 +6379,25 @@ } }, "node_modules/import-from-esm": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/import-from-esm/-/import-from-esm-1.3.4.tgz", - "integrity": "sha512-7EyUlPFC0HOlBDpUFGfYstsU7XHxZJKAAMzCT8wZ0hMW7b+hG51LIKTDcsgtz8Pu6YC0HqRVbX+rVUtsGMUKvg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-from-esm/-/import-from-esm-2.0.0.tgz", + "integrity": "sha512-YVt14UZCgsX1vZQ3gKjkWVdBdHQ6eu3MPU1TBgL1H5orXe2+jWD006WCPPtOuwlQm10NuzOW5WawiF1Q9veW8g==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^4.3.4", "import-meta-resolve": "^4.0.0" }, "engines": { - "node": ">=16.20" + "node": ">=18.20" } }, "node_modules/import-meta-resolve": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", - "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", "dev": true, + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -6251,6 +6417,7 @@ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -6259,10 +6426,11 @@ } }, "node_modules/index-to-position": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-0.1.2.tgz", - "integrity": "sha512-MWDKS3AS1bGCHLBA2VLImJz42f7bJh8wQsTGCzI3j519/CASStoDONUBVz2I/VID0MpiX3SGSnbOD2xUalbE5g==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz", + "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -6311,6 +6479,7 @@ "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-7.0.0.tgz", "integrity": "sha512-2dYz766i9HprMBasCMvHMuazJ7u4WzhJwo5kb3iPSiW/iRYV6uPari3zHoqZlnuaR7V1bEiNMxikhp37rdBXbw==", "dev": true, + "license": "MIT", "dependencies": { "from2": "^2.3.0", "p-is-promise": "^3.0.0" @@ -6663,6 +6832,7 @@ "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-7.0.1.tgz", "integrity": "sha512-3YZcUUR2Wt1WsapF+S/WiA2WmlW0cWAoPccMqne7AxEBhCdFeTPjfv/Axb8V2gyCgY3nRw+ksZ3xSUX+R47iAg==", "dev": true, + "license": "MIT", "dependencies": { "lodash.capitalize": "^4.2.1", "lodash.escaperegexp": "^4.1.2", @@ -6687,7 +6857,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/js-yaml": { "version": "4.1.0", @@ -6880,10 +7051,11 @@ "dev": true }, "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "dev": true + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", + "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", + "dev": true, + "license": "MIT" }, "node_modules/lodash.camelcase": { "version": "4.3.0", @@ -6895,7 +7067,8 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz", "integrity": "sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.debounce": { "version": "4.0.8", @@ -6906,7 +7079,8 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.isfunction": { "version": "3.0.9", @@ -6924,7 +7098,8 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.kebabcase": { "version": "4.1.1", @@ -6966,7 +7141,8 @@ "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.upperfirst": { "version": "4.3.1", @@ -6988,6 +7164,16 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/lru-cache": { + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/lunr": { "version": "2.3.9", "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", @@ -7040,10 +7226,11 @@ } }, "node_modules/marked": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz", - "integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==", + "version": "15.0.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", + "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", "dev": true, + "license": "MIT", "bin": { "marked": "bin/marked.js" }, @@ -7052,30 +7239,33 @@ } }, "node_modules/marked-terminal": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.1.0.tgz", - "integrity": "sha512-+pvwa14KZL74MVXjYdPR3nSInhGhNvPce/3mqLVZT2oUvt654sL1XImFuLZ1pkA866IYZ3ikDTOFUIC7XzpZZg==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.3.0.tgz", + "integrity": "sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==", "dev": true, + "license": "MIT", "dependencies": { "ansi-escapes": "^7.0.0", - "chalk": "^5.3.0", + "ansi-regex": "^6.1.0", + "chalk": "^5.4.1", "cli-highlight": "^2.1.11", "cli-table3": "^0.6.5", - "node-emoji": "^2.1.3", - "supports-hyperlinks": "^3.0.0" + "node-emoji": "^2.2.0", + "supports-hyperlinks": "^3.1.0" }, "engines": { "node": ">=16.0.0" }, "peerDependencies": { - "marked": ">=1 <14" + "marked": ">=1 <16" } }, "node_modules/marked-terminal/node_modules/ansi-escapes": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", - "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", "dev": true, + "license": "MIT", "dependencies": { "environment": "^1.0.0" }, @@ -7314,13 +7504,14 @@ } }, "node_modules/mime": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.4.tgz", - "integrity": "sha512-v8yqInVjhXyqP6+Kw4fV3ZzeMRqEW6FotRsKXjRS5VMTNIuXsdRoAvklpoRgSqXm6o9VNH4/C0mgedko9DdLsQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.1.0.tgz", + "integrity": "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==", "dev": true, "funding": [ "https://github.com/sponsors/broofa" ], + "license": "MIT", "bin": { "mime": "bin/cli.js" }, @@ -7422,6 +7613,7 @@ "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", "dev": true, + "license": "MIT", "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", @@ -7466,19 +7658,22 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/nerf-dart": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/nerf-dart/-/nerf-dart-1.0.0.tgz", "integrity": "sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/node-emoji": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.1.3.tgz", - "integrity": "sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", + "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", "dev": true, + "license": "MIT", "dependencies": { "@sindresorhus/is": "^4.6.0", "char-regex": "^1.0.2", @@ -7529,10 +7724,11 @@ } }, "node_modules/normalize-url": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", - "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.1.tgz", + "integrity": "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.16" }, @@ -7541,15 +7737,16 @@ } }, "node_modules/npm": { - "version": "10.8.2", - "resolved": "https://registry.npmjs.org/npm/-/npm-10.8.2.tgz", - "integrity": "sha512-x/AIjFIKRllrhcb48dqUNAAZl0ig9+qMuN91RpZo3Cb2+zuibfh+KISl6+kVVyktDz230JKc208UkQwwMqyB+w==", + "version": "11.10.1", + "resolved": "https://registry.npmjs.org/npm/-/npm-11.10.1.tgz", + "integrity": "sha512-woavuY2OgDFQ1K/tB9QHsUuW989nKfvsKTN/h5qGyS+3+BhvXN/DA2TNzx569JaFfTqrET5bEQNHwVhFk+U1gg==", "bundleDependencies": [ "@isaacs/string-locale-compare", "@npmcli/arborist", "@npmcli/config", "@npmcli/fs", "@npmcli/map-workspaces", + "@npmcli/metavuln-calculator", "@npmcli/package-json", "@npmcli/promise-spawn", "@npmcli/redact", @@ -7560,7 +7757,6 @@ "cacache", "chalk", "ci-info", - "cli-columns", "fastest-levenshtein", "fs-minipass", "glob", @@ -7574,7 +7770,6 @@ "libnpmdiff", "libnpmexec", "libnpmfund", - "libnpmhook", "libnpmorg", "libnpmpack", "libnpmpublish", @@ -7588,7 +7783,6 @@ "ms", "node-gyp", "nopt", - "normalize-package-data", "npm-audit-report", "npm-install-checks", "npm-package-arg", @@ -7611,10 +7805,10 @@ "tiny-relative-date", "treeverse", "validate-npm-package-name", - "which", - "write-file-atomic" + "which" ], "dev": true, + "license": "Artistic-2.0", "workspaces": [ "docs", "smoke-tests", @@ -7624,80 +7818,77 @@ ], "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^7.5.4", - "@npmcli/config": "^8.3.4", - "@npmcli/fs": "^3.1.1", - "@npmcli/map-workspaces": "^3.0.6", - "@npmcli/package-json": "^5.2.0", - "@npmcli/promise-spawn": "^7.0.2", - "@npmcli/redact": "^2.0.1", - "@npmcli/run-script": "^8.1.0", - "@sigstore/tuf": "^2.3.4", - "abbrev": "^2.0.0", + "@npmcli/arborist": "^9.3.1", + "@npmcli/config": "^10.7.1", + "@npmcli/fs": "^5.0.0", + "@npmcli/map-workspaces": "^5.0.3", + "@npmcli/metavuln-calculator": "^9.0.3", + "@npmcli/package-json": "^7.0.5", + "@npmcli/promise-spawn": "^9.0.1", + "@npmcli/redact": "^4.0.0", + "@npmcli/run-script": "^10.0.3", + "@sigstore/tuf": "^4.0.1", + "abbrev": "^4.0.0", "archy": "~1.0.0", - "cacache": "^18.0.3", - "chalk": "^5.3.0", - "ci-info": "^4.0.0", - "cli-columns": "^4.0.0", + "cacache": "^20.0.3", + "chalk": "^5.6.2", + "ci-info": "^4.4.0", "fastest-levenshtein": "^1.0.16", "fs-minipass": "^3.0.3", - "glob": "^10.4.2", + "glob": "^13.0.6", "graceful-fs": "^4.2.11", - "hosted-git-info": "^7.0.2", - "ini": "^4.1.3", - "init-package-json": "^6.0.3", - "is-cidr": "^5.1.0", - "json-parse-even-better-errors": "^3.0.2", - "libnpmaccess": "^8.0.6", - "libnpmdiff": "^6.1.4", - "libnpmexec": "^8.1.3", - "libnpmfund": "^5.0.12", - "libnpmhook": "^10.0.5", - "libnpmorg": "^6.0.6", - "libnpmpack": "^7.0.4", - "libnpmpublish": "^9.0.9", - "libnpmsearch": "^7.0.6", - "libnpmteam": "^6.0.5", - "libnpmversion": "^6.0.3", - "make-fetch-happen": "^13.0.1", - "minimatch": "^9.0.5", - "minipass": "^7.1.1", + "hosted-git-info": "^9.0.2", + "ini": "^6.0.0", + "init-package-json": "^8.2.5", + "is-cidr": "^6.0.3", + "json-parse-even-better-errors": "^5.0.0", + "libnpmaccess": "^10.0.3", + "libnpmdiff": "^8.1.2", + "libnpmexec": "^10.2.2", + "libnpmfund": "^7.0.16", + "libnpmorg": "^8.0.1", + "libnpmpack": "^9.1.2", + "libnpmpublish": "^11.1.3", + "libnpmsearch": "^9.0.1", + "libnpmteam": "^8.0.2", + "libnpmversion": "^8.0.3", + "make-fetch-happen": "^15.0.3", + "minimatch": "^10.2.2", + "minipass": "^7.1.3", "minipass-pipeline": "^1.2.4", "ms": "^2.1.2", - "node-gyp": "^10.1.0", - "nopt": "^7.2.1", - "normalize-package-data": "^6.0.2", - "npm-audit-report": "^5.0.0", - "npm-install-checks": "^6.3.0", - "npm-package-arg": "^11.0.2", - "npm-pick-manifest": "^9.1.0", - "npm-profile": "^10.0.0", - "npm-registry-fetch": "^17.1.0", - "npm-user-validate": "^2.0.1", - "p-map": "^4.0.0", - "pacote": "^18.0.6", - "parse-conflict-json": "^3.0.1", - "proc-log": "^4.2.0", + "node-gyp": "^12.2.0", + "nopt": "^9.0.0", + "npm-audit-report": "^7.0.0", + "npm-install-checks": "^8.0.0", + "npm-package-arg": "^13.0.2", + "npm-pick-manifest": "^11.0.3", + "npm-profile": "^12.0.1", + "npm-registry-fetch": "^19.1.1", + "npm-user-validate": "^4.0.0", + "p-map": "^7.0.4", + "pacote": "^21.3.1", + "parse-conflict-json": "^5.0.1", + "proc-log": "^6.1.0", "qrcode-terminal": "^0.12.0", - "read": "^3.0.1", - "semver": "^7.6.2", + "read": "^5.0.1", + "semver": "^7.7.4", "spdx-expression-parse": "^4.0.0", - "ssri": "^10.0.6", - "supports-color": "^9.4.0", - "tar": "^6.2.1", + "ssri": "^13.0.1", + "supports-color": "^10.2.2", + "tar": "^7.5.9", "text-table": "~0.2.0", - "tiny-relative-date": "^1.3.0", + "tiny-relative-date": "^2.0.2", "treeverse": "^3.0.0", - "validate-npm-package-name": "^5.0.1", - "which": "^4.0.0", - "write-file-atomic": "^5.0.1" + "validate-npm-package-name": "^7.0.2", + "which": "^6.0.1" }, "bin": { "npm": "bin/npm-cli.js", "npx": "bin/npx-cli.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm-run-path": { @@ -7727,71 +7918,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/@isaacs/cliui": { - "version": "8.0.2", + "node_modules/npm/node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/npm/node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/npm/node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm/node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" + "minipass": "^7.0.4" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": ">=18.0.0" } }, "node_modules/npm/node_modules/@isaacs/string-locale-compare": { @@ -7801,7 +7937,7 @@ "license": "ISC" }, "node_modules/npm/node_modules/@npmcli/agent": { - "version": "2.2.2", + "version": "4.0.0", "dev": true, "inBundle": true, "license": "ISC", @@ -7809,83 +7945,81 @@ "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", - "lru-cache": "^10.0.1", + "lru-cache": "^11.2.1", "socks-proxy-agent": "^8.0.3" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/arborist": { - "version": "7.5.4", + "version": "9.3.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/fs": "^3.1.1", - "@npmcli/installed-package-contents": "^2.1.0", - "@npmcli/map-workspaces": "^3.0.2", - "@npmcli/metavuln-calculator": "^7.1.1", - "@npmcli/name-from-folder": "^2.0.0", - "@npmcli/node-gyp": "^3.0.0", - "@npmcli/package-json": "^5.1.0", - "@npmcli/query": "^3.1.0", - "@npmcli/redact": "^2.0.0", - "@npmcli/run-script": "^8.1.0", - "bin-links": "^4.0.4", - "cacache": "^18.0.3", - "common-ancestor-path": "^1.0.1", - "hosted-git-info": "^7.0.2", - "json-parse-even-better-errors": "^3.0.2", + "@npmcli/fs": "^5.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/map-workspaces": "^5.0.0", + "@npmcli/metavuln-calculator": "^9.0.2", + "@npmcli/name-from-folder": "^4.0.0", + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/query": "^5.0.0", + "@npmcli/redact": "^4.0.0", + "@npmcli/run-script": "^10.0.0", + "bin-links": "^6.0.0", + "cacache": "^20.0.1", + "common-ancestor-path": "^2.0.0", + "hosted-git-info": "^9.0.0", "json-stringify-nice": "^1.1.4", - "lru-cache": "^10.2.2", - "minimatch": "^9.0.4", - "nopt": "^7.2.1", - "npm-install-checks": "^6.2.0", - "npm-package-arg": "^11.0.2", - "npm-pick-manifest": "^9.0.1", - "npm-registry-fetch": "^17.0.1", - "pacote": "^18.0.6", - "parse-conflict-json": "^3.0.0", - "proc-log": "^4.2.0", - "proggy": "^2.0.0", + "lru-cache": "^11.2.1", + "minimatch": "^10.0.3", + "nopt": "^9.0.0", + "npm-install-checks": "^8.0.0", + "npm-package-arg": "^13.0.0", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "pacote": "^21.0.2", + "parse-conflict-json": "^5.0.1", + "proc-log": "^6.0.0", + "proggy": "^4.0.0", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^3.0.1", - "read-package-json-fast": "^3.0.2", "semver": "^7.3.7", - "ssri": "^10.0.6", + "ssri": "^13.0.0", "treeverse": "^3.0.0", - "walk-up-path": "^3.0.1" + "walk-up-path": "^4.0.0" }, "bin": { "arborist": "bin/index.js" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/config": { - "version": "8.3.4", + "version": "10.7.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/map-workspaces": "^3.0.2", - "@npmcli/package-json": "^5.1.1", + "@npmcli/map-workspaces": "^5.0.0", + "@npmcli/package-json": "^7.0.0", "ci-info": "^4.0.0", - "ini": "^4.1.2", - "nopt": "^7.2.1", - "proc-log": "^4.2.0", + "ini": "^6.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", "semver": "^7.3.5", - "walk-up-path": "^3.0.1" + "walk-up-path": "^4.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/fs": { - "version": "3.1.1", + "version": "5.0.0", "dev": true, "inBundle": true, "license": "ISC", @@ -7893,244 +8027,233 @@ "semver": "^7.3.5" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/git": { - "version": "5.0.8", + "version": "7.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/promise-spawn": "^7.0.0", - "ini": "^4.1.3", - "lru-cache": "^10.0.1", - "npm-pick-manifest": "^9.0.0", - "proc-log": "^4.0.0", - "promise-inflight": "^1.0.1", + "@npmcli/promise-spawn": "^9.0.0", + "ini": "^6.0.0", + "lru-cache": "^11.2.1", + "npm-pick-manifest": "^11.0.1", + "proc-log": "^6.0.0", "promise-retry": "^2.0.1", "semver": "^7.3.5", - "which": "^4.0.0" + "which": "^6.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/installed-package-contents": { - "version": "2.1.0", + "version": "4.0.0", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "npm-bundled": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" + "npm-bundled": "^5.0.0", + "npm-normalize-package-bin": "^5.0.0" }, "bin": { "installed-package-contents": "bin/index.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/map-workspaces": { - "version": "3.0.6", + "version": "5.0.3", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/name-from-folder": "^2.0.0", - "glob": "^10.2.2", - "minimatch": "^9.0.0", - "read-package-json-fast": "^3.0.0" + "@npmcli/name-from-folder": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "glob": "^13.0.0", + "minimatch": "^10.0.3" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { - "version": "7.1.1", + "version": "9.0.3", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "cacache": "^18.0.0", - "json-parse-even-better-errors": "^3.0.0", - "pacote": "^18.0.0", - "proc-log": "^4.1.0", + "cacache": "^20.0.0", + "json-parse-even-better-errors": "^5.0.0", + "pacote": "^21.0.0", + "proc-log": "^6.0.0", "semver": "^7.3.5" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/name-from-folder": { - "version": "2.0.0", + "version": "4.0.0", "dev": true, "inBundle": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/node-gyp": { - "version": "3.0.0", + "version": "5.0.0", "dev": true, "inBundle": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/package-json": { - "version": "5.2.0", + "version": "7.0.5", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/git": "^5.0.0", - "glob": "^10.2.2", - "hosted-git-info": "^7.0.0", - "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^6.0.0", - "proc-log": "^4.0.0", - "semver": "^7.5.3" + "@npmcli/git": "^7.0.0", + "glob": "^13.0.0", + "hosted-git-info": "^9.0.0", + "json-parse-even-better-errors": "^5.0.0", + "proc-log": "^6.0.0", + "semver": "^7.5.3", + "spdx-expression-parse": "^4.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/promise-spawn": { - "version": "7.0.2", + "version": "9.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "which": "^4.0.0" + "which": "^6.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/query": { - "version": "3.1.0", + "version": "5.0.0", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "postcss-selector-parser": "^6.0.10" + "postcss-selector-parser": "^7.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/redact": { - "version": "2.0.1", + "version": "4.0.0", "dev": true, "inBundle": true, "license": "ISC", "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/run-script": { - "version": "8.1.0", + "version": "10.0.3", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/node-gyp": "^3.0.0", - "@npmcli/package-json": "^5.0.0", - "@npmcli/promise-spawn": "^7.0.0", - "node-gyp": "^10.0.0", - "proc-log": "^4.0.0", - "which": "^4.0.0" + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "node-gyp": "^12.1.0", + "proc-log": "^6.0.0", + "which": "^6.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@sigstore/bundle": { - "version": "2.3.2", + "version": "4.0.0", "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/protobuf-specs": "^0.3.2" + "@sigstore/protobuf-specs": "^0.5.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@sigstore/core": { - "version": "1.1.0", + "version": "3.1.0", "dev": true, "inBundle": true, "license": "Apache-2.0", "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@sigstore/protobuf-specs": { - "version": "0.3.2", + "version": "0.5.0", "dev": true, "inBundle": true, "license": "Apache-2.0", "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm/node_modules/@sigstore/sign": { - "version": "2.3.2", + "version": "4.1.0", "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^2.3.2", - "@sigstore/core": "^1.0.0", - "@sigstore/protobuf-specs": "^0.3.2", - "make-fetch-happen": "^13.0.1", - "proc-log": "^4.2.0", + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.1.0", + "@sigstore/protobuf-specs": "^0.5.0", + "make-fetch-happen": "^15.0.3", + "proc-log": "^6.1.0", "promise-retry": "^2.0.1" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@sigstore/tuf": { - "version": "2.3.4", + "version": "4.0.1", "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/protobuf-specs": "^0.3.2", - "tuf-js": "^2.2.1" + "@sigstore/protobuf-specs": "^0.5.0", + "tuf-js": "^4.1.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@sigstore/verify": { - "version": "1.2.1", + "version": "3.1.0", "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^2.3.2", - "@sigstore/core": "^1.1.0", - "@sigstore/protobuf-specs": "^0.3.2" + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.1.0", + "@sigstore/protobuf-specs": "^0.5.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@tufjs/canonical-json": { @@ -8143,75 +8266,38 @@ } }, "node_modules/npm/node_modules/@tufjs/models": { - "version": "2.0.1", + "version": "4.1.0", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { "@tufjs/canonical-json": "2.0.0", - "minimatch": "^9.0.4" + "minimatch": "^10.1.1" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/abbrev": { - "version": "2.0.0", + "version": "4.0.0", "dev": true, "inBundle": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/agent-base": { - "version": "7.1.1", + "version": "7.1.4", "dev": true, "inBundle": true, "license": "MIT", - "dependencies": { - "debug": "^4.3.4" - }, "engines": { "node": ">= 14" } }, - "node_modules/npm/node_modules/aggregate-error": { - "version": "3.1.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/ansi-regex": { - "version": "5.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/ansi-styles": { - "version": "6.2.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/npm/node_modules/aproba": { - "version": "2.0.0", + "version": "2.1.0", "dev": true, "inBundle": true, "license": "ISC" @@ -8223,72 +8309,78 @@ "license": "MIT" }, "node_modules/npm/node_modules/balanced-match": { - "version": "1.0.2", + "version": "4.0.3", "dev": true, "inBundle": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": "20 || >=22" + } }, "node_modules/npm/node_modules/bin-links": { - "version": "4.0.4", + "version": "6.0.0", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "cmd-shim": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0", - "read-cmd-shim": "^4.0.0", - "write-file-atomic": "^5.0.0" + "cmd-shim": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "proc-log": "^6.0.0", + "read-cmd-shim": "^6.0.0", + "write-file-atomic": "^7.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/binary-extensions": { - "version": "2.3.0", + "version": "3.1.0", "dev": true, "inBundle": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18.20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/npm/node_modules/brace-expansion": { - "version": "2.0.1", + "version": "5.0.2", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" } }, "node_modules/npm/node_modules/cacache": { - "version": "18.0.3", + "version": "20.0.3", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/fs": "^3.1.0", + "@npmcli/fs": "^5.0.0", "fs-minipass": "^3.0.0", - "glob": "^10.2.2", - "lru-cache": "^10.0.1", + "glob": "^13.0.0", + "lru-cache": "^11.1.0", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", - "p-map": "^4.0.0", - "ssri": "^10.0.0", - "tar": "^6.1.11", - "unique-filename": "^3.0.0" + "p-map": "^7.0.2", + "ssri": "^13.0.0", + "unique-filename": "^5.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/chalk": { - "version": "5.3.0", + "version": "5.6.2", "dev": true, "inBundle": true, "license": "MIT", @@ -8300,16 +8392,16 @@ } }, "node_modules/npm/node_modules/chownr": { - "version": "2.0.0", + "version": "3.0.0", "dev": true, "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/npm/node_modules/ci-info": { - "version": "4.0.0", + "version": "4.4.0", "dev": true, "funding": [ { @@ -8324,138 +8416,63 @@ } }, "node_modules/npm/node_modules/cidr-regex": { - "version": "4.1.1", + "version": "5.0.3", "dev": true, "inBundle": true, "license": "BSD-2-Clause", - "dependencies": { - "ip-regex": "^5.0.0" - }, "engines": { - "node": ">=14" + "node": ">=20" } }, - "node_modules/npm/node_modules/clean-stack": { - "version": "2.2.0", + "node_modules/npm/node_modules/cmd-shim": { + "version": "8.0.0", "dev": true, "inBundle": true, - "license": "MIT", + "license": "ISC", "engines": { - "node": ">=6" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/cli-columns": { - "version": "4.0.0", + "node_modules/npm/node_modules/common-ancestor-path": { + "version": "2.0.0", "dev": true, "inBundle": true, - "license": "MIT", - "dependencies": { - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, + "license": "BlueOak-1.0.0", "engines": { - "node": ">= 10" + "node": ">= 18" } }, - "node_modules/npm/node_modules/cmd-shim": { - "version": "6.0.3", + "node_modules/npm/node_modules/cssesc": { + "version": "3.0.0", "dev": true, "inBundle": true, - "license": "ISC", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=4" } }, - "node_modules/npm/node_modules/color-convert": { - "version": "2.0.1", + "node_modules/npm/node_modules/debug": { + "version": "4.4.3", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "ms": "^2.1.3" }, "engines": { - "node": ">=7.0.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/npm/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/common-ancestor-path": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/cross-spawn": { - "version": "7.0.3", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/cross-spawn/node_modules/which": { - "version": "2.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/cssesc": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/debug": { - "version": "4.3.5", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/npm/node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "dev": true, - "inBundle": true, - "license": "MIT" - }, "node_modules/npm/node_modules/diff": { - "version": "5.2.0", + "version": "8.0.3", "dev": true, "inBundle": true, "license": "BSD-3-Clause", @@ -8463,18 +8480,6 @@ "node": ">=0.3.1" } }, - "node_modules/npm/node_modules/eastasianwidth": { - "version": "0.2.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/emoji-regex": { - "version": "8.0.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, "node_modules/npm/node_modules/encoding": { "version": "0.1.13", "dev": true, @@ -8501,7 +8506,7 @@ "license": "MIT" }, "node_modules/npm/node_modules/exponential-backoff": { - "version": "3.1.1", + "version": "3.1.3", "dev": true, "inBundle": true, "license": "Apache-2.0" @@ -8515,22 +8520,6 @@ "node": ">= 4.9.1" } }, - "node_modules/npm/node_modules/foreground-child": { - "version": "3.2.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/npm/node_modules/fs-minipass": { "version": "3.0.3", "dev": true, @@ -8544,23 +8533,17 @@ } }, "node_modules/npm/node_modules/glob": { - "version": "10.4.2", + "version": "13.0.6", "dev": true, "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" }, "engines": { - "node": ">=16 || 14 >=14.18" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -8573,19 +8556,19 @@ "license": "ISC" }, "node_modules/npm/node_modules/hosted-git-info": { - "version": "7.0.2", + "version": "9.0.2", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "lru-cache": "^10.0.1" + "lru-cache": "^11.1.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/http-cache-semantics": { - "version": "4.1.1", + "version": "4.2.0", "dev": true, "inBundle": true, "license": "BSD-2-Clause" @@ -8604,12 +8587,12 @@ } }, "node_modules/npm/node_modules/https-proxy-agent": { - "version": "7.0.5", + "version": "7.0.6", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "agent-base": "^7.0.2", + "agent-base": "^7.1.2", "debug": "4" }, "engines": { @@ -8630,15 +8613,15 @@ } }, "node_modules/npm/node_modules/ignore-walk": { - "version": "6.0.5", + "version": "8.0.0", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "minimatch": "^9.0.0" + "minimatch": "^10.0.3" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/imurmurhash": { @@ -8650,131 +8633,69 @@ "node": ">=0.8.19" } }, - "node_modules/npm/node_modules/indent-string": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/npm/node_modules/ini": { - "version": "4.1.3", + "version": "6.0.0", "dev": true, "inBundle": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/init-package-json": { - "version": "6.0.3", + "version": "8.2.5", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/package-json": "^5.0.0", - "npm-package-arg": "^11.0.0", - "promzard": "^1.0.0", - "read": "^3.0.1", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4", - "validate-npm-package-name": "^5.0.0" + "@npmcli/package-json": "^7.0.0", + "npm-package-arg": "^13.0.0", + "promzard": "^3.0.1", + "read": "^5.0.1", + "semver": "^7.7.2", + "validate-npm-package-name": "^7.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/ip-address": { - "version": "9.0.5", + "version": "10.1.0", "dev": true, "inBundle": true, "license": "MIT", - "dependencies": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" - }, "engines": { "node": ">= 12" } }, - "node_modules/npm/node_modules/ip-regex": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/npm/node_modules/is-cidr": { - "version": "5.1.0", + "version": "6.0.3", "dev": true, "inBundle": true, "license": "BSD-2-Clause", "dependencies": { - "cidr-regex": "^4.1.1" + "cidr-regex": "^5.0.1" }, "engines": { - "node": ">=14" - } - }, - "node_modules/npm/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" + "node": ">=20" } }, - "node_modules/npm/node_modules/is-lambda": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "MIT" - }, "node_modules/npm/node_modules/isexe": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/jackspeak": { - "version": "3.4.0", + "version": "4.0.0", "dev": true, "inBundle": true, "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "node": ">=20" } }, - "node_modules/npm/node_modules/jsbn": { - "version": "1.1.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, "node_modules/npm/node_modules/json-parse-even-better-errors": { - "version": "3.0.2", + "version": "5.0.0", "dev": true, "inBundle": true, "license": "MIT", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/json-stringify-nice": { @@ -8808,223 +8729,211 @@ "license": "MIT" }, "node_modules/npm/node_modules/libnpmaccess": { - "version": "8.0.6", + "version": "10.0.3", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "npm-package-arg": "^11.0.2", - "npm-registry-fetch": "^17.0.1" + "npm-package-arg": "^13.0.0", + "npm-registry-fetch": "^19.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmdiff": { - "version": "6.1.4", + "version": "8.1.2", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^7.5.4", - "@npmcli/installed-package-contents": "^2.1.0", - "binary-extensions": "^2.3.0", - "diff": "^5.1.0", - "minimatch": "^9.0.4", - "npm-package-arg": "^11.0.2", - "pacote": "^18.0.6", - "tar": "^6.2.1" + "@npmcli/arborist": "^9.3.1", + "@npmcli/installed-package-contents": "^4.0.0", + "binary-extensions": "^3.0.0", + "diff": "^8.0.2", + "minimatch": "^10.0.3", + "npm-package-arg": "^13.0.0", + "pacote": "^21.0.2", + "tar": "^7.5.1" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmexec": { - "version": "8.1.3", + "version": "10.2.2", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^7.5.4", - "@npmcli/run-script": "^8.1.0", + "@npmcli/arborist": "^9.3.1", + "@npmcli/package-json": "^7.0.0", + "@npmcli/run-script": "^10.0.0", "ci-info": "^4.0.0", - "npm-package-arg": "^11.0.2", - "pacote": "^18.0.6", - "proc-log": "^4.2.0", - "read": "^3.0.1", - "read-package-json-fast": "^3.0.2", + "npm-package-arg": "^13.0.0", + "pacote": "^21.0.2", + "proc-log": "^6.0.0", + "promise-retry": "^2.0.1", + "read": "^5.0.1", "semver": "^7.3.7", - "walk-up-path": "^3.0.1" + "signal-exit": "^4.1.0", + "walk-up-path": "^4.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmfund": { - "version": "5.0.12", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/arborist": "^7.5.4" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/libnpmhook": { - "version": "10.0.5", + "version": "7.0.16", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "aproba": "^2.0.0", - "npm-registry-fetch": "^17.0.1" + "@npmcli/arborist": "^9.3.1" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmorg": { - "version": "6.0.6", + "version": "8.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { "aproba": "^2.0.0", - "npm-registry-fetch": "^17.0.1" + "npm-registry-fetch": "^19.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmpack": { - "version": "7.0.4", + "version": "9.1.2", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^7.5.4", - "@npmcli/run-script": "^8.1.0", - "npm-package-arg": "^11.0.2", - "pacote": "^18.0.6" + "@npmcli/arborist": "^9.3.1", + "@npmcli/run-script": "^10.0.0", + "npm-package-arg": "^13.0.0", + "pacote": "^21.0.2" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmpublish": { - "version": "9.0.9", + "version": "11.1.3", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { + "@npmcli/package-json": "^7.0.0", "ci-info": "^4.0.0", - "normalize-package-data": "^6.0.1", - "npm-package-arg": "^11.0.2", - "npm-registry-fetch": "^17.0.1", - "proc-log": "^4.2.0", + "npm-package-arg": "^13.0.0", + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0", "semver": "^7.3.7", - "sigstore": "^2.2.0", - "ssri": "^10.0.6" + "sigstore": "^4.0.0", + "ssri": "^13.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmsearch": { - "version": "7.0.6", + "version": "9.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "npm-registry-fetch": "^17.0.1" + "npm-registry-fetch": "^19.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmteam": { - "version": "6.0.5", + "version": "8.0.2", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { "aproba": "^2.0.0", - "npm-registry-fetch": "^17.0.1" + "npm-registry-fetch": "^19.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmversion": { - "version": "6.0.3", + "version": "8.0.3", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/git": "^5.0.7", - "@npmcli/run-script": "^8.1.0", - "json-parse-even-better-errors": "^3.0.2", - "proc-log": "^4.2.0", + "@npmcli/git": "^7.0.0", + "@npmcli/run-script": "^10.0.0", + "json-parse-even-better-errors": "^5.0.0", + "proc-log": "^6.0.0", "semver": "^7.3.7" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/lru-cache": { - "version": "10.2.2", + "version": "11.2.6", "dev": true, "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { - "node": "14 || >=16.14" + "node": "20 || >=22" } }, "node_modules/npm/node_modules/make-fetch-happen": { - "version": "13.0.1", + "version": "15.0.3", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/agent": "^2.0.0", - "cacache": "^18.0.0", + "@npmcli/agent": "^4.0.0", + "cacache": "^20.0.1", "http-cache-semantics": "^4.1.1", - "is-lambda": "^1.0.1", "minipass": "^7.0.2", - "minipass-fetch": "^3.0.0", + "minipass-fetch": "^5.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "proc-log": "^4.2.0", + "negotiator": "^1.0.0", + "proc-log": "^6.0.0", "promise-retry": "^2.0.1", - "ssri": "^10.0.0" + "ssri": "^13.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/minimatch": { - "version": "9.0.5", + "version": "10.2.2", "dev": true, "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/npm/node_modules/minipass": { - "version": "7.1.2", + "version": "7.1.3", "dev": true, "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } @@ -9042,17 +8951,17 @@ } }, "node_modules/npm/node_modules/minipass-fetch": { - "version": "3.0.5", + "version": "5.0.1", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { "minipass": "^7.0.3", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" + "minipass-sized": "^2.0.0", + "minizlib": "^3.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" }, "optionalDependencies": { "encoding": "^0.1.13" @@ -9082,6 +8991,12 @@ "node": ">=8" } }, + "node_modules/npm/node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, "node_modules/npm/node_modules/minipass-pipeline": { "version": "1.2.4", "dev": true, @@ -9106,84 +9021,53 @@ "node": ">=8" } }, - "node_modules/npm/node_modules/minipass-sized": { - "version": "1.0.3", + "node_modules/npm/node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", "dev": true, "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } + "license": "ISC" }, - "node_modules/npm/node_modules/minipass-sized/node_modules/minipass": { - "version": "3.3.6", + "node_modules/npm/node_modules/minipass-sized": { + "version": "2.0.0", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "yallist": "^4.0.0" + "minipass": "^7.1.2" }, "engines": { "node": ">=8" } }, "node_modules/npm/node_modules/minizlib": { - "version": "2.1.2", + "version": "3.1.0", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" + "minipass": "^7.1.2" }, "engines": { - "node": ">= 8" + "node": ">= 18" } }, - "node_modules/npm/node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/mkdirp": { - "version": "1.0.4", - "dev": true, - "inBundle": true, - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm/node_modules/ms": { - "version": "2.1.3", + "node_modules/npm/node_modules/ms": { + "version": "2.1.3", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/mute-stream": { - "version": "1.0.0", + "version": "3.0.0", "dev": true, "inBundle": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/negotiator": { - "version": "0.6.3", + "version": "1.0.0", "dev": true, "inBundle": true, "license": "MIT", @@ -9192,90 +9076,67 @@ } }, "node_modules/npm/node_modules/node-gyp": { - "version": "10.1.0", + "version": "12.2.0", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", - "glob": "^10.3.10", "graceful-fs": "^4.2.6", - "make-fetch-happen": "^13.0.0", - "nopt": "^7.0.0", - "proc-log": "^3.0.0", + "make-fetch-happen": "^15.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^4.0.0" + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "which": "^6.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" }, "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/proc-log": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/nopt": { - "version": "7.2.1", + "version": "9.0.0", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "abbrev": "^2.0.0" + "abbrev": "^4.0.0" }, "bin": { "nopt": "bin/nopt.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/normalize-package-data": { - "version": "6.0.2", - "dev": true, - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^7.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/npm-audit-report": { - "version": "5.0.0", + "version": "7.0.0", "dev": true, "inBundle": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/npm-bundled": { - "version": "3.0.1", + "version": "5.0.0", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "npm-normalize-package-bin": "^3.0.0" + "npm-normalize-package-bin": "^5.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/npm-install-checks": { - "version": "6.3.0", + "version": "8.0.0", "dev": true, "inBundle": true, "license": "BSD-2-Clause", @@ -9283,194 +9144,177 @@ "semver": "^7.1.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/npm-normalize-package-bin": { - "version": "3.0.1", + "version": "5.0.0", "dev": true, "inBundle": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/npm-package-arg": { - "version": "11.0.2", + "version": "13.0.2", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "hosted-git-info": "^7.0.0", - "proc-log": "^4.0.0", + "hosted-git-info": "^9.0.0", + "proc-log": "^6.0.0", "semver": "^7.3.5", - "validate-npm-package-name": "^5.0.0" + "validate-npm-package-name": "^7.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/npm-packlist": { - "version": "8.0.2", + "version": "10.0.3", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "ignore-walk": "^6.0.4" + "ignore-walk": "^8.0.0", + "proc-log": "^6.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/npm-pick-manifest": { - "version": "9.1.0", + "version": "11.0.3", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "npm-install-checks": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0", - "npm-package-arg": "^11.0.0", + "npm-install-checks": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "npm-package-arg": "^13.0.0", "semver": "^7.3.5" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/npm-profile": { - "version": "10.0.0", + "version": "12.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "npm-registry-fetch": "^17.0.1", - "proc-log": "^4.0.0" + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/npm-registry-fetch": { - "version": "17.1.0", + "version": "19.1.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/redact": "^2.0.0", + "@npmcli/redact": "^4.0.0", "jsonparse": "^1.3.1", - "make-fetch-happen": "^13.0.0", + "make-fetch-happen": "^15.0.0", "minipass": "^7.0.2", - "minipass-fetch": "^3.0.0", - "minizlib": "^2.1.2", - "npm-package-arg": "^11.0.0", - "proc-log": "^4.0.0" + "minipass-fetch": "^5.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^13.0.0", + "proc-log": "^6.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/npm-user-validate": { - "version": "2.0.1", + "version": "4.0.0", "dev": true, "inBundle": true, "license": "BSD-2-Clause", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/p-map": { - "version": "4.0.0", + "version": "7.0.4", "dev": true, "inBundle": true, "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" - }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/package-json-from-dist": { - "version": "1.0.0", - "dev": true, - "inBundle": true, - "license": "BlueOak-1.0.0" - }, "node_modules/npm/node_modules/pacote": { - "version": "18.0.6", + "version": "21.3.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/git": "^5.0.0", - "@npmcli/installed-package-contents": "^2.0.1", - "@npmcli/package-json": "^5.1.0", - "@npmcli/promise-spawn": "^7.0.0", - "@npmcli/run-script": "^8.0.0", - "cacache": "^18.0.0", + "@npmcli/git": "^7.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "@npmcli/run-script": "^10.0.0", + "cacache": "^20.0.0", "fs-minipass": "^3.0.0", "minipass": "^7.0.2", - "npm-package-arg": "^11.0.0", - "npm-packlist": "^8.0.0", - "npm-pick-manifest": "^9.0.0", - "npm-registry-fetch": "^17.0.0", - "proc-log": "^4.0.0", + "npm-package-arg": "^13.0.0", + "npm-packlist": "^10.0.1", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0", "promise-retry": "^2.0.1", - "sigstore": "^2.2.0", - "ssri": "^10.0.0", - "tar": "^6.1.11" + "sigstore": "^4.0.0", + "ssri": "^13.0.0", + "tar": "^7.4.3" }, "bin": { "pacote": "bin/index.js" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/parse-conflict-json": { - "version": "3.0.1", + "version": "5.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "json-parse-even-better-errors": "^3.0.0", + "json-parse-even-better-errors": "^5.0.0", "just-diff": "^6.0.0", "just-diff-apply": "^5.2.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/path-key": { - "version": "3.1.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/path-scurry": { - "version": "1.11.1", + "version": "2.0.2", "dev": true, "inBundle": true, "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.18" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/npm/node_modules/postcss-selector-parser": { - "version": "6.1.0", + "version": "7.1.1", "dev": true, "inBundle": true, "license": "MIT", @@ -9483,21 +9327,21 @@ } }, "node_modules/npm/node_modules/proc-log": { - "version": "4.2.0", + "version": "6.1.0", "dev": true, "inBundle": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/proggy": { - "version": "2.0.0", + "version": "4.0.0", "dev": true, "inBundle": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/promise-all-reject-late": { @@ -9510,7 +9354,7 @@ } }, "node_modules/npm/node_modules/promise-call-limit": { - "version": "3.0.1", + "version": "3.0.2", "dev": true, "inBundle": true, "license": "ISC", @@ -9518,12 +9362,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/npm/node_modules/promise-inflight": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "ISC" - }, "node_modules/npm/node_modules/promise-retry": { "version": "2.0.1", "dev": true, @@ -9538,15 +9376,15 @@ } }, "node_modules/npm/node_modules/promzard": { - "version": "1.0.2", + "version": "3.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "read": "^3.0.1" + "read": "^5.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/qrcode-terminal": { @@ -9558,37 +9396,24 @@ } }, "node_modules/npm/node_modules/read": { - "version": "3.0.1", + "version": "5.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "mute-stream": "^1.0.0" + "mute-stream": "^3.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/read-cmd-shim": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/read-package-json-fast": { - "version": "3.0.2", + "version": "6.0.0", "dev": true, "inBundle": true, "license": "ISC", - "dependencies": { - "json-parse-even-better-errors": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/retry": { @@ -9608,7 +9433,7 @@ "optional": true }, "node_modules/npm/node_modules/semver": { - "version": "7.6.2", + "version": "7.7.4", "dev": true, "inBundle": true, "license": "ISC", @@ -9619,27 +9444,6 @@ "node": ">=10" } }, - "node_modules/npm/node_modules/shebang-command": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/shebang-regex": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/npm/node_modules/signal-exit": { "version": "4.1.0", "dev": true, @@ -9653,20 +9457,20 @@ } }, "node_modules/npm/node_modules/sigstore": { - "version": "2.3.1", + "version": "4.1.0", "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^2.3.2", - "@sigstore/core": "^1.0.0", - "@sigstore/protobuf-specs": "^0.3.2", - "@sigstore/sign": "^2.3.2", - "@sigstore/tuf": "^2.3.4", - "@sigstore/verify": "^1.2.1" + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.1.0", + "@sigstore/protobuf-specs": "^0.5.0", + "@sigstore/sign": "^4.1.0", + "@sigstore/tuf": "^4.0.1", + "@sigstore/verify": "^3.1.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/smart-buffer": { @@ -9680,12 +9484,12 @@ } }, "node_modules/npm/node_modules/socks": { - "version": "2.8.3", + "version": "2.8.7", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "ip-address": "^9.0.5", + "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" }, "engines": { @@ -9694,12 +9498,12 @@ } }, "node_modules/npm/node_modules/socks-proxy-agent": { - "version": "8.0.4", + "version": "8.0.5", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "agent-base": "^7.1.1", + "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" }, @@ -9707,26 +9511,6 @@ "node": ">= 14" } }, - "node_modules/npm/node_modules/spdx-correct": { - "version": "3.2.0", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/npm/node_modules/spdx-correct/node_modules/spdx-expression-parse": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, "node_modules/npm/node_modules/spdx-exceptions": { "version": "2.5.0", "dev": true, @@ -9744,19 +9528,13 @@ } }, "node_modules/npm/node_modules/spdx-license-ids": { - "version": "3.0.18", + "version": "3.0.22", "dev": true, "inBundle": true, "license": "CC0-1.0" }, - "node_modules/npm/node_modules/sprintf-js": { - "version": "1.1.3", - "dev": true, - "inBundle": true, - "license": "BSD-3-Clause" - }, "node_modules/npm/node_modules/ssri": { - "version": "10.0.6", + "version": "13.0.1", "dev": true, "inBundle": true, "license": "ISC", @@ -9764,123 +9542,35 @@ "minipass": "^7.0.3" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/string-width": { - "version": "4.2.3", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/strip-ansi": { - "version": "6.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/supports-color": { - "version": "9.4.0", + "version": "10.2.2", "dev": true, "inBundle": true, "license": "MIT", "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/npm/node_modules/tar": { - "version": "6.2.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm/node_modules/tar/node_modules/fs-minipass": { - "version": "2.1.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", + "version": "7.5.9", "dev": true, "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "yallist": "^4.0.0" + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/tar/node_modules/minipass": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=8" + "node": ">=18" } }, "node_modules/npm/node_modules/text-table": { @@ -9890,225 +9580,144 @@ "license": "MIT" }, "node_modules/npm/node_modules/tiny-relative-date": { - "version": "1.3.0", + "version": "2.0.2", "dev": true, "inBundle": true, "license": "MIT" }, - "node_modules/npm/node_modules/treeverse": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/tuf-js": { - "version": "2.2.1", + "node_modules/npm/node_modules/tinyglobby": { + "version": "0.2.15", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "@tufjs/models": "2.0.1", - "debug": "^4.3.4", - "make-fetch-happen": "^13.0.1" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/unique-filename": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "unique-slug": "^4.0.0" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/unique-slug": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4" + "node": ">=12.0.0" }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/util-deprecate": { - "version": "1.0.2", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/validate-npm-package-license": { - "version": "3.0.4", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/npm/node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { - "version": "3.0.1", + "node_modules/npm/node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", "dev": true, "inBundle": true, "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/npm/node_modules/validate-npm-package-name": { - "version": "5.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/walk-up-path": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/which": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "isexe": "^3.1.1" + "node": ">=12.0.0" }, - "bin": { - "node-which": "bin/which.js" + "peerDependencies": { + "picomatch": "^3 || ^4" }, - "engines": { - "node": "^16.13.0 || >=18.0.0" + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/npm/node_modules/which/node_modules/isexe": { - "version": "3.1.1", + "node_modules/npm/node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", "dev": true, "inBundle": true, - "license": "ISC", + "license": "MIT", "engines": { - "node": ">=16" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/npm/node_modules/wrap-ansi": { - "version": "8.1.0", + "node_modules/npm/node_modules/treeverse": { + "version": "3.0.0", "dev": true, "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, + "license": "ISC", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", + "node_modules/npm/node_modules/tuf-js": { + "version": "4.1.0", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "@tufjs/models": "4.1.0", + "debug": "^4.4.3", + "make-fetch-happen": "^15.0.1" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", + "node_modules/npm/node_modules/unique-filename": { + "version": "5.0.0", "dev": true, "inBundle": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "color-convert": "^2.0.1" + "unique-slug": "^6.0.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.0.1", + "node_modules/npm/node_modules/unique-slug": { + "version": "6.0.0", "dev": true, "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=12" + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/wrap-ansi/node_modules/emoji-regex": { - "version": "9.2.2", + "node_modules/npm/node_modules/util-deprecate": { + "version": "1.0.2", "dev": true, "inBundle": true, "license": "MIT" }, - "node_modules/npm/node_modules/wrap-ansi/node_modules/string-width": { - "version": "5.1.2", + "node_modules/npm/node_modules/validate-npm-package-name": { + "version": "7.0.2", "dev": true, "inBundle": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, + "license": "ISC", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.0", + "node_modules/npm/node_modules/walk-up-path": { + "version": "4.0.0", "dev": true, "inBundle": true, - "license": "MIT", + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/npm/node_modules/which": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "ansi-regex": "^6.0.1" + "isexe": "^4.0.0" }, - "engines": { - "node": ">=12" + "bin": { + "node-which": "bin/which.js" }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/write-file-atomic": { - "version": "5.0.1", + "version": "7.0.0", "dev": true, "inBundle": true, "license": "ISC", @@ -10117,20 +9726,24 @@ "signal-exit": "^4.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/yallist": { - "version": "4.0.0", + "version": "5.0.0", "dev": true, "inBundle": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -10301,6 +9914,7 @@ "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-4.1.0.tgz", "integrity": "sha512-37/tPdZ3oJwHaS3gNJdenCDB3Tz26i9sjhnguBtvN0vYlRIiDNnvTWkuh+0hETV9rLPdJ3rlL3yVOYPIAnM8rw==", "dev": true, + "license": "MIT", "dependencies": { "p-map": "^7.0.1" }, @@ -10316,6 +9930,7 @@ "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -10351,10 +9966,11 @@ } }, "node_modules/p-map": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.2.tgz", - "integrity": "sha512-z4cYYMMdKHzw4O5UkWJImbZynVIo0lSGTXc7bzB1e/rrDqkgGUNysK/o4bTr+0+xKvvLoTyGqYC4Fgljy9qe1Q==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -10428,13 +10044,15 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/parse5-htmlparser2-tree-adapter": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", "dev": true, + "license": "MIT", "dependencies": { "parse5": "^6.0.1" } @@ -10443,7 +10061,8 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/parseurl": { "version": "1.3.3", @@ -10690,7 +10309,8 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/proxy-addr": { "version": "2.0.7", @@ -10796,6 +10416,7 @@ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -10811,81 +10432,90 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/read-package-up": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz", - "integrity": "sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-12.0.0.tgz", + "integrity": "sha512-Q5hMVBYur/eQNWDdbF4/Wqqr9Bjvtrw2kjGxxBbKLbx8bVCL8gcArjTy8zDUuLGQicftpMuU0riQNcAsbtOVsw==", "dev": true, + "license": "MIT", "dependencies": { - "find-up-simple": "^1.0.0", - "read-pkg": "^9.0.0", - "type-fest": "^4.6.0" + "find-up-simple": "^1.0.1", + "read-pkg": "^10.0.0", + "type-fest": "^5.2.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/read-package-up/node_modules/type-fest": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.22.1.tgz", - "integrity": "sha512-9tHNEa0Ov81YOopiVkcCJVz5TM6AEQ+CHHjFIktqPnE3NV0AHIkx+gh9tiCl58m/66wWxkOC9eltpa75J4lQPA==", + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.4.4.tgz", + "integrity": "sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw==", "dev": true, + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, "engines": { - "node": ">=16" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/read-pkg": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", - "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-10.1.0.tgz", + "integrity": "sha512-I8g2lArQiP78ll51UeMZojewtYgIRCKCWqZEgOO8c/uefTI+XDXvCSXu3+YNUaTNvZzobrL5+SqHjBrByRRTdg==", "dev": true, + "license": "MIT", "dependencies": { - "@types/normalize-package-data": "^2.4.3", - "normalize-package-data": "^6.0.0", - "parse-json": "^8.0.0", - "type-fest": "^4.6.0", - "unicorn-magic": "^0.1.0" + "@types/normalize-package-data": "^2.4.4", + "normalize-package-data": "^8.0.0", + "parse-json": "^8.3.0", + "type-fest": "^5.4.4", + "unicorn-magic": "^0.4.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/read-pkg/node_modules/normalize-package-data": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", - "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-8.0.0.tgz", + "integrity": "sha512-RWk+PI433eESQ7ounYxIp67CYuVsS1uYSonX3kA6ps/3LWfjVQa/ptEg6Y3T6uAMq1mWpX9PQ+qx+QaHpsc7gQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "hosted-git-info": "^7.0.0", + "hosted-git-info": "^9.0.0", "semver": "^7.3.5", "validate-npm-package-license": "^3.0.4" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/read-pkg/node_modules/parse-json": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.1.0.tgz", - "integrity": "sha512-rum1bPifK5SSar35Z6EKZuYPJx85pkNaFrxBK3mwdfSJ1/WKbYrjoW/zTPSjRRamfmVX1ACBIdFAO0VRErW/EA==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.22.13", - "index-to-position": "^0.1.2", - "type-fest": "^4.7.1" + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" }, "engines": { "node": ">=18" @@ -10894,11 +10524,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/read-pkg/node_modules/type-fest": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.22.1.tgz", - "integrity": "sha512-9tHNEa0Ov81YOopiVkcCJVz5TM6AEQ+CHHjFIktqPnE3NV0AHIkx+gh9tiCl58m/66wWxkOC9eltpa75J4lQPA==", + "node_modules/read-pkg/node_modules/parse-json/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=16" }, @@ -10906,6 +10537,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.4.4.tgz", + "integrity": "sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -10979,12 +10626,13 @@ } }, "node_modules/registry-auth-token": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.2.tgz", - "integrity": "sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.1.tgz", + "integrity": "sha512-P7B4+jq8DeD2nMsAcdfaqHbssgHtZ7Z5+++a5ask90fvmJ8p5je4mOa+wzu+DB4vQ5tdJV/xywY+UnVFeQLV5Q==", "dev": true, + "license": "MIT", "dependencies": { - "@pnpm/npm-conf": "^2.1.0" + "@pnpm/npm-conf": "^3.0.2" }, "engines": { "node": ">=14" @@ -11201,16 +10849,17 @@ "dev": true }, "node_modules/semantic-release": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-24.0.0.tgz", - "integrity": "sha512-v46CRPw+9eI3ZuYGF2oAjqPqsfbnfFTwLBgQsv/lch4goD09ytwOTESMN4QIrx/wPLxUGey60/NMx+ANQtWRsA==", + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-25.0.3.tgz", + "integrity": "sha512-WRgl5GcypwramYX4HV+eQGzUbD7UUbljVmS+5G1uMwX/wLgYuJAxGeerXJDMO2xshng4+FXqCgyB5QfClV6WjA==", "dev": true, + "license": "MIT", "dependencies": { - "@semantic-release/commit-analyzer": "^13.0.0-beta.1", + "@semantic-release/commit-analyzer": "^13.0.1", "@semantic-release/error": "^4.0.0", - "@semantic-release/github": "^10.0.0", - "@semantic-release/npm": "^12.0.0", - "@semantic-release/release-notes-generator": "^14.0.0-beta.1", + "@semantic-release/github": "^12.0.0", + "@semantic-release/npm": "^13.1.1", + "@semantic-release/release-notes-generator": "^14.1.0", "aggregate-error": "^5.0.0", "cosmiconfig": "^9.0.0", "debug": "^4.0.0", @@ -11220,27 +10869,54 @@ "find-versions": "^6.0.0", "get-stream": "^6.0.0", "git-log-parser": "^1.2.0", - "hook-std": "^3.0.0", - "hosted-git-info": "^7.0.0", - "import-from-esm": "^1.3.1", + "hook-std": "^4.0.0", + "hosted-git-info": "^9.0.0", + "import-from-esm": "^2.0.0", "lodash-es": "^4.17.21", - "marked": "^12.0.0", - "marked-terminal": "^7.0.0", + "marked": "^15.0.0", + "marked-terminal": "^7.3.0", "micromatch": "^4.0.2", "p-each-series": "^3.0.0", "p-reduce": "^3.0.0", - "read-package-up": "^11.0.0", + "read-package-up": "^12.0.0", "resolve-from": "^5.0.0", "semver": "^7.3.2", - "semver-diff": "^4.0.0", "signale": "^1.2.1", - "yargs": "^17.5.1" + "yargs": "^18.0.0" }, "bin": { "semantic-release": "bin/semantic-release.js" }, "engines": { - "node": ">=20.8.1" + "node": "^22.14.0 || >= 24.10.0" + } + }, + "node_modules/semantic-release/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/semantic-release/node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" } }, "node_modules/semantic-release/node_modules/cosmiconfig": { @@ -11278,6 +10954,52 @@ "node": ">=8" } }, + "node_modules/semantic-release/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/semantic-release/node_modules/yargs": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^7.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/semantic-release/node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -11293,21 +11015,6 @@ "node": ">=10" } }, - "node_modules/semver-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz", - "integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==", - "dev": true, - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/semver-regex": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-4.0.5.tgz", @@ -11600,6 +11307,7 @@ "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", "dev": true, + "license": "MIT", "dependencies": { "unicode-emoji-modifier-base": "^1.0.0" }, @@ -11666,6 +11374,7 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -11844,9 +11553,10 @@ } }, "node_modules/string-width": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz", - "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", @@ -11992,16 +11702,20 @@ } }, "node_modules/supports-hyperlinks": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.0.0.tgz", - "integrity": "sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" }, "engines": { "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" } }, "node_modules/supports-preserve-symlinks-flag": { @@ -12016,20 +11730,35 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/temp-dir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", "integrity": "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.16" } }, "node_modules/tempy": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-3.1.0.tgz", - "integrity": "sha512-7jDLIdD2Zp0bDe5r3D2qtkd1QOCacylBuL7oa4udvN6v2pqr4+LcCr67C8DR1zkpaZ8XosF5m1yQSabKAW6f2g==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-3.2.0.tgz", + "integrity": "sha512-d79HhZya5Djd7am0q+W4RTsSU+D/aJzM+4Y4AGJGuGlgM2L6sx5ZvOYTmZjqPhrDrV6xJTtRSm1JCLj6V6LHLQ==", "dev": true, + "license": "MIT", "dependencies": { "is-stream": "^3.0.0", "temp-dir": "^3.0.0", @@ -12048,6 +11777,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=12.20" }, @@ -12075,6 +11805,7 @@ "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", "dev": true, + "license": "MIT", "dependencies": { "any-promise": "^1.0.0" } @@ -12084,6 +11815,7 @@ "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", "dev": true, + "license": "MIT", "dependencies": { "thenify": ">= 3.1.0 < 4" }, @@ -12309,6 +12041,16 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", "dev": true }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -12513,10 +12255,11 @@ "dev": true }, "node_modules/uglify-js": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.0.tgz", - "integrity": "sha512-wNKHUY2hYYkf6oSFfhwwiHo4WCHzHmzcXsqXYTN9ja3iApYIFbb2U6ics9hBcYLHcYGQoAlwnZlTrf3oF+BL/Q==", + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", "dev": true, + "license": "BSD-2-Clause", "optional": true, "bin": { "uglifyjs": "bin/uglifyjs" @@ -12552,22 +12295,34 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici": { + "version": "7.22.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz", + "integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/unicode-emoji-modifier-base": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/unicorn-magic": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", - "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.0.tgz", + "integrity": "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==", "dev": true, + "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -12578,6 +12333,7 @@ "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", "dev": true, + "license": "MIT", "dependencies": { "crypto-random-string": "^4.0.0" }, @@ -12589,10 +12345,11 @@ } }, "node_modules/universal-user-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz", - "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==", - "dev": true + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", + "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", + "dev": true, + "license": "ISC" }, "node_modules/universalify": { "version": "2.0.0", @@ -12625,6 +12382,7 @@ "resolved": "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz", "integrity": "sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==", "dev": true, + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } @@ -12951,7 +12709,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/wrap-ansi": { "version": "7.0.0", @@ -13161,10 +12920,11 @@ } }, "node_modules/yoctocolors": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz", - "integrity": "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, diff --git a/package.json b/package.json index 4e939a8..bf3b83b 100644 --- a/package.json +++ b/package.json @@ -117,7 +117,7 @@ "express": "^4.21.1", "hash.js": "^1.1.7", "husky": "^8.0.3", - "semantic-release": "^24.0.0", + "semantic-release": "^25.0.3", "stream-throttle": "^0.1.3", "tslib": "^2.6.1", "typedoc": "^0.26.3", From d8cd69a6f8721df9f4857842cc30f7a1fad2fbf8 Mon Sep 17 00:00:00 2001 From: ido Date: Fri, 20 Feb 2026 18:08:31 +0200 Subject: [PATCH 13/33] fix(write-stream): finalization close file handler --- .../download-engine-write-stream-nodejs.ts | 71 ++++++----- .../utils/BytesWriteDebounce.ts | 110 ------------------ .../utils/abortableSleep.ts | 14 --- 3 files changed, 33 insertions(+), 162 deletions(-) delete mode 100644 src/download/download-engine/streams/download-engine-write-stream/utils/BytesWriteDebounce.ts delete mode 100644 src/download/download-engine/streams/download-engine-write-stream/utils/abortableSleep.ts diff --git a/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-nodejs.ts b/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-nodejs.ts index 2f9536b..8e67fbe 100644 --- a/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-nodejs.ts +++ b/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-nodejs.ts @@ -4,11 +4,11 @@ import retry from "async-retry"; import {withLock} from "lifecycle-utils"; import BaseDownloadEngineWriteStream from "./base-download-engine-write-stream.js"; import WriterIsClosedError from "./errors/writer-is-closed-error.js"; -import {BytesWriteDebounce} from "./utils/BytesWriteDebounce.js"; export type DownloadEngineWriteStreamOptionsNodeJS = { retry?: retry.Options mode: string; + /**@deprecated This functionality had been remove duo to performance issues **/ debounceWrite?: { maxTime?: number maxSize?: number @@ -16,23 +16,26 @@ export type DownloadEngineWriteStreamOptionsNodeJS = { }; const DEFAULT_OPTIONS = { - mode: "r+", - debounceWrite: { - maxTime: 1000 * 5, // 5 seconds - maxSize: 1024 * 1024 * 2 // 2 MB - } + mode: "r+" } satisfies DownloadEngineWriteStreamOptionsNodeJS; -const MAX_AUTO_DEBOUNCE_SIZE = 1024 * 1024 * 100; // 100 MB -const AUTO_DEBOUNCE_SIZE_PERCENT = 0.05; const MAX_META_SIZE = 10485760; // 10 MB const NOT_ENOUGH_SPACE_ERROR_CODE = "ENOSPC"; export default class DownloadEngineWriteStreamNodejs extends BaseDownloadEngineWriteStream { + private static _allFd = new Set(); + private static _finalizationRegistry = new FinalizationRegistry(async (fd: FileHandle) => { + if (fd.fd != null) { + await fd.close(); + } + DownloadEngineWriteStreamNodejs._allFd.delete(fd); + }); + + private _finalToken = {}; private _fd: FileHandle | null = null; private _fileWriteFinished = false; - private _writeDebounce: BytesWriteDebounce; private _fileSize = 0; + private _lastWritePromise: Promise | null = null; public readonly options: DownloadEngineWriteStreamOptionsNodeJS; public autoDebounceMaxSize = false; @@ -41,19 +44,10 @@ export default class DownloadEngineWriteStreamNodejs extends BaseDownloadEngineW super(); this.autoDebounceMaxSize = !options.debounceWrite?.maxSize; - const optionsWithDefaults = this.options = { + this.options = { ...DEFAULT_OPTIONS, - ...options, - debounceWrite: { - ...DEFAULT_OPTIONS.debounceWrite, - ...options.debounceWrite - } + ...options }; - - this._writeDebounce = new BytesWriteDebounce({ - ...optionsWithDefaults.debounceWrite, - writev: (cursor, buffers) => this._writeWithoutDebounce(cursor, buffers) - }); } public get fileSize() { @@ -62,13 +56,6 @@ export default class DownloadEngineWriteStreamNodejs extends BaseDownloadEngineW public set fileSize(value) { this._fileSize = value; - - if (this.autoDebounceMaxSize) { - this.options.debounceWrite!.maxSize = Math.max( - Math.min(value * AUTO_DEBOUNCE_SIZE_PERCENT, MAX_AUTO_DEBOUNCE_SIZE), - DEFAULT_OPTIONS.debounceWrite.maxSize - ); - } } private async _ensureFileOpen() { @@ -79,16 +66,15 @@ export default class DownloadEngineWriteStreamNodejs extends BaseDownloadEngineW return await retry(async () => { await fsExtra.ensureFile(this.path); - return this._fd = await fs.open(this.path, this.options.mode); + this._fd = await fs.open(this.path, this.options.mode); + DownloadEngineWriteStreamNodejs._allFd.add(this._fd); + DownloadEngineWriteStreamNodejs._finalizationRegistry.register(this, this._fd, this._finalToken); + return this._fd; }, this.options.retry); }); } async write(cursor: number, buffers: Uint8Array[]) { - await this._writeDebounce.addChunk(cursor, buffers); - } - - async _writeWithoutDebounce(cursor: number, buffers: Uint8Array[]) { let throwError: Error | false = false; await retry(async () => { @@ -109,7 +95,9 @@ export default class DownloadEngineWriteStreamNodejs extends BaseDownloadEngineW } async ensureBytesSynced() { - await this._writeDebounce.writeAllAndFinish(); + if (this._lastWritePromise) { + await this._lastWritePromise; + } } async ftruncate(size = this._fileSize) { @@ -159,21 +147,28 @@ export default class DownloadEngineWriteStreamNodejs extends BaseDownloadEngineW return JSON.parse(metadataString); } catch {} } finally { - this._fd = null; - await fd.close(); + await this.close(); } } private async _writeWithoutRetry(cursor: number, buffers: Uint8Array[]) { - return await withLock(this, "lockWriteOperation", async () => { + return await (this._lastWritePromise = withLock(this, "lockWriteOperation", async () => { const fd = await this._ensureFileOpen(); const {bytesWritten} = await fd.writev(buffers, cursor); return bytesWritten; - }); + })); } override async close() { - await this._fd?.close(); + if (!this._fd) { + return; + } + + if (this._fd.fd != null) { + await this._fd.close(); + } + DownloadEngineWriteStreamNodejs._allFd.delete(this._fd); + DownloadEngineWriteStreamNodejs._finalizationRegistry.unregister(this._finalToken); this._fd = null; } } diff --git a/src/download/download-engine/streams/download-engine-write-stream/utils/BytesWriteDebounce.ts b/src/download/download-engine/streams/download-engine-write-stream/utils/BytesWriteDebounce.ts deleted file mode 100644 index 23ad5f0..0000000 --- a/src/download/download-engine/streams/download-engine-write-stream/utils/BytesWriteDebounce.ts +++ /dev/null @@ -1,110 +0,0 @@ -import {abortableSleep} from "./abortableSleep.js"; -import WriterIsClosedError from "../errors/writer-is-closed-error.js"; - -export type BytesWriteDebounceOptions = { - maxTime: number; - maxSize: number; - writev: (index: number, buffers: Uint8Array[]) => Promise; -}; - -export class BytesWriteDebounce { - private _writeChunks: { - index: number; - buffer: Uint8Array; - }[] = []; - private _lastWriteTime = Date.now(); - private _totalSizeOfChunks = 0; - private _checkWriteInterval = false; - private _abortSleep = new AbortController(); - private _finished = false; - - constructor(private _options: BytesWriteDebounceOptions) { - - } - - async addChunk(index: number, buffers: Uint8Array[]) { - if (this._finished) { - throw new WriterIsClosedError("Cannot write to a finished stream"); - } - - let writeIndex = index; - for (const buffer of buffers) { - this._writeChunks.push({index: writeIndex, buffer}); - this._totalSizeOfChunks += buffer.length; - writeIndex += buffer.length; - } - - await this._writeIfNeeded(); - this.checkIfWriteNeededInterval(); - } - - private async _writeIfNeeded() { - if (this._totalSizeOfChunks >= this._options.maxSize || Date.now() - this._lastWriteTime >= this._options.maxTime) { - await this.writeAll(); - } - } - - private async checkIfWriteNeededInterval() { - if (this._checkWriteInterval) { - return; - } - this._checkWriteInterval = true; - - while (this._writeChunks.length > 0 && !this._finished) { - await this._writeIfNeeded(); - const timeUntilMaxLimitAfterWrite = this._options.maxTime - (Date.now() - this._lastWriteTime); - await abortableSleep(Math.max(timeUntilMaxLimitAfterWrite, 0), this._abortSleep.signal); - } - - this._checkWriteInterval = false; - } - - writeAll() { - if (this._writeChunks.length === 0) { - return; - } - - this._writeChunks = this._writeChunks.sort((a, b) => a.index - b.index); - const firstWrite = this._writeChunks[0]; - - let writeIndex = firstWrite.index; - let buffers: Uint8Array[] = [firstWrite.buffer]; - let buffersTotalLength = firstWrite.buffer.length; - - const writePromises: Promise[] = []; - - for (let i = 1; i < this._writeChunks.length; i++) { - const nextWriteLocation = writeIndex + buffersTotalLength; - const currentWrite = this._writeChunks[i]; - - if (currentWrite.index < nextWriteLocation) { // overlapping, prefer the last buffer (newer data) - const lastBuffer = buffers.pop()!; - buffers.push(currentWrite.buffer); - buffersTotalLength += currentWrite.buffer.length - lastBuffer.length; - } else if (nextWriteLocation === currentWrite.index) { - buffers.push(currentWrite.buffer); - buffersTotalLength += currentWrite.buffer.length; - } else { - writePromises.push(this._options.writev(writeIndex, buffers)); - - writeIndex = currentWrite.index; - buffers = [currentWrite.buffer]; - buffersTotalLength = currentWrite.buffer.length; - } - } - - writePromises.push(this._options.writev(writeIndex, buffers)); - - this._writeChunks = []; - this._totalSizeOfChunks = 0; - this._lastWriteTime = Date.now(); - return Promise.all(writePromises); - } - - - writeAllAndFinish() { - this._finished = true; - this._abortSleep.abort(); - return this.writeAll(); - } -} diff --git a/src/download/download-engine/streams/download-engine-write-stream/utils/abortableSleep.ts b/src/download/download-engine/streams/download-engine-write-stream/utils/abortableSleep.ts deleted file mode 100644 index 504a3c9..0000000 --- a/src/download/download-engine/streams/download-engine-write-stream/utils/abortableSleep.ts +++ /dev/null @@ -1,14 +0,0 @@ -export function abortableSleep(timeMS: number, signal?: AbortSignal): Promise { - return new Promise((resolve) => { - const timeoutId = setTimeout(() => { - resolve(); - }, timeMS); - - if (signal) { - signal.addEventListener("abort", () => { - clearTimeout(timeoutId); - resolve(); - }, {once: true}); - } - }); -} From a4ca68ccde8c0c2238a9f5f2f9d4e29d2ee46e6f Mon Sep 17 00:00:00 2001 From: ido Date: Sun, 8 Mar 2026 18:03:22 +0100 Subject: [PATCH 14/33] feat(GlobalCLI): cache engine order generation --- .../transfer-cli/GlobalCLI.ts | 76 ++++++++++++++----- .../transfer-cli/transfer-cli.ts | 17 ++++- 2 files changed, 71 insertions(+), 22 deletions(-) diff --git a/src/download/transfer-visualize/transfer-cli/GlobalCLI.ts b/src/download/transfer-visualize/transfer-cli/GlobalCLI.ts index 3a0b054..98aa369 100644 --- a/src/download/transfer-visualize/transfer-cli/GlobalCLI.ts +++ b/src/download/transfer-visualize/transfer-cli/GlobalCLI.ts @@ -18,7 +18,7 @@ export type CliProgressDownloadEngineOptions = { cliProgress?: boolean; maxViewDownloads?: number; createMultiProgressBar?: typeof BaseMultiProgressBar, - cliStyle?: AvailableCLIProgressStyle | ((status: CliFormattedStatus) => string) + cliStyle?: AvailableCLIProgressStyle | ((status: CliFormattedStatus) => string); cliName?: string; loadingAnimation?: cliSpinners.SpinnerName; }; @@ -29,6 +29,8 @@ class GlobalCLI { private _transferCLI = GlobalCLI._createOptions({}); private _cliActive = false; private _downloadOptions = new WeakMap(); + private _cachedCliEngines: AllowedDownloadEngine[] = []; + private _isCliEnginesCacheDirty = true; constructor() { this._registerCLIEvents(); @@ -115,29 +117,26 @@ class GlobalCLI { } }); - const getCLIEngines = (multiEngine: DownloadEngineMultiDownload) => { - const enginesToShow: AllowedDownloadEngine[] = []; - for (const engine of multiEngine.downloads) { - const isShowEngine = this._downloadOptions.get(engine)?.cliProgress; - if (engine instanceof DownloadEngineMultiDownload) { - if (isShowEngine) { - enginesToShow.push(...engine._flatEngines); - continue; - } - enginesToShow.push(...getCLIEngines(engine)); - } else if (isShowEngine) { - enginesToShow.push(engine); + + const invalidateCache = () => this._isCliEnginesCacheDirty = true; + this._multiDownloadEngine.on("downloadAdded", function onDownloadAdded(engine) { + if (engine instanceof DownloadEngineMultiDownload) { + for (const flatEngine of engine._flatEngines) { + onDownloadAdded(flatEngine); } + + engine.on("downloadAdded", onDownloadAdded); } - return enginesToShow.filter((engine, index, self) => self.indexOf(engine) === index); - }; + invalidateCache(); + }); - const printProgress = (progress: FormattedStatus) => { - const statues = getCLIEngines(this._multiDownloadEngine) - .map(x => x.status); - this._transferCLI.updateStatues(statues, progress, this._multiDownloadEngine.loadingDownloads); - }; + const printProgress = (progress: FormattedStatus) => + this._transferCLI.updateStatuesLazy(() => [ + this._getCLIStatuses(), + progress, + this._multiDownloadEngine.loadingDownloads + ]); this._multiDownloadEngine.on("progress", (progress) => { if (!this._cliActive) return; @@ -151,10 +150,47 @@ class GlobalCLI { this._transferCLI.isFirstPrint = true; this._multiDownloadEngine = this._createMultiDownloadEngine(); this._eventsRegistered = new Set(); + this._cachedCliEngines = []; + this._isCliEnginesCacheDirty = true; this._registerCLIEvents(); }); } + private _collectCLIEngines(multiEngine: DownloadEngineMultiDownload, engines: Set) { + for (const engine of multiEngine.downloads) { + const isShowEngine = this._downloadOptions.get(engine)?.cliProgress; + if (engine instanceof DownloadEngineMultiDownload) { + if (isShowEngine) { + for (const flatEngine of engine._flatEngines) { + engines.add(flatEngine); + } + continue; + } + this._collectCLIEngines(engine, engines); + } else if (isShowEngine) { + engines.add(engine); + } + } + } + + private _getCLIEngines() { + if (!this._isCliEnginesCacheDirty) { + return this._cachedCliEngines; + } + + const engines = new Set(); + this._collectCLIEngines(this._multiDownloadEngine, engines); + this._cachedCliEngines = Array.from(engines); + this._isCliEnginesCacheDirty = false; + + return this._cachedCliEngines; + } + + private _getCLIStatuses() { + return this._getCLIEngines() + .map(engine => engine.status); + } + private static _createOptions(options: CliProgressDownloadEngineOptions) { const cliOptions: Partial = {...options}; cliOptions.createProgressBar ??= typeof options.cliStyle === "function" ? diff --git a/src/download/transfer-visualize/transfer-cli/transfer-cli.ts b/src/download/transfer-visualize/transfer-cli/transfer-cli.ts index 3379d20..f856df6 100644 --- a/src/download/transfer-visualize/transfer-cli/transfer-cli.ts +++ b/src/download/transfer-visualize/transfer-cli/transfer-cli.ts @@ -31,6 +31,7 @@ export default class TransferCli { protected options: TransferCliOptions; protected stdoutManager = UpdateManager.getInstance(); protected latestProgress: [FormattedStatus[], FormattedStatus, number] = null!; + protected latestProgressGetter: (() => [FormattedStatus[], FormattedStatus, number]) | null = null; private _cliStopped = true; private _updateStatuesDebounce: () => void = this._updateStatues; private _abortDebounce = new AbortController(); @@ -83,6 +84,7 @@ export default class TransferCli { } updateStatues(statues: FormattedStatus[], oneStatus: FormattedStatus, loadingDownloads = 0) { + this.latestProgressGetter = null; this.latestProgress = [statues, oneStatus, loadingDownloads]; if (this.isFirstPrint) { @@ -93,9 +95,20 @@ export default class TransferCli { } } + updateStatuesLazy(getLatestProgress: () => [FormattedStatus[], FormattedStatus, number]) { + this.latestProgressGetter = getLatestProgress; + if (this.isFirstPrint) { + this.isFirstPrint = false; + this._updateStatues(); + } else { + this._updateStatuesDebounce(); + } + } + private _updateStatues() { - if (!this.latestProgress) return; - const printLog = this._multiProgressBar.createMultiProgressBar(...this.latestProgress); + const latestProgress = this.latestProgressGetter?.() ?? this.latestProgress; + if (!latestProgress) return; + const printLog = this._multiProgressBar.createMultiProgressBar(...latestProgress); if (printLog && this._lastProgressLong != printLog) { this._lastProgressLong = printLog; this._logUpdate(printLog); From 64de83dbfb721ea7da6adc7e48a54e81b92370ba Mon Sep 17 00:00:00 2001 From: ido Date: Sat, 14 Mar 2026 21:05:14 +0100 Subject: [PATCH 15/33] feat(write-stream): parallel write queue --- package-lock.json | 21 ++- package.json | 2 +- .../download-file/download-engine-file.ts | 26 +-- .../engine/download-engine-nodejs.ts | 4 +- .../download-engine-write-stream-nodejs.ts | 117 ++++++------- .../utils/WriteQueue.ts | 164 ++++++++++++++++++ 6 files changed, 258 insertions(+), 76 deletions(-) create mode 100644 src/download/download-engine/streams/download-engine-write-stream/utils/WriteQueue.ts diff --git a/package-lock.json b/package-lock.json index 7f74616..47bf383 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,7 +41,7 @@ "@types/express": "^5.0.0", "@types/fs-extra": "^11.0.1", "@types/lodash.debounce": "^4.0.9", - "@types/node": "^20.4.9", + "@types/node": "^20.19.37", "@types/stream-throttle": "^0.1.4", "@typescript-eslint/eslint-plugin": "^6.3.0", "@typescript-eslint/parser": "^6.3.0", @@ -2738,10 +2738,14 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.5.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.1.tgz", - "integrity": "sha512-4tT2UrL5LBqDwoed9wZ6N3umC4Yhz3W3FloMmiiG4JwmUJWpie0c7lcnUNd4gtMKuDEO4wRVS8B6Xa0uMRsMKg==", - "dev": true + "version": "20.19.37", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", + "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } }, "node_modules/@types/normalize-package-data": { "version": "2.4.4", @@ -12305,6 +12309,13 @@ "node": ">=20.18.1" } }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, "node_modules/unicode-emoji-modifier-base": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", diff --git a/package.json b/package.json index bf3b83b..8d3d29b 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,7 @@ "@types/express": "^5.0.0", "@types/fs-extra": "^11.0.1", "@types/lodash.debounce": "^4.0.9", - "@types/node": "^20.4.9", + "@types/node": "^20.19.37", "@types/stream-throttle": "^0.1.4", "@typescript-eslint/eslint-plugin": "^6.3.0", "@typescript-eslint/parser": "^6.3.0", diff --git a/src/download/download-engine/download-file/download-engine-file.ts b/src/download/download-engine/download-file/download-engine-file.ts index ee9af27..2797ad6 100644 --- a/src/download/download-engine/download-file/download-engine-file.ts +++ b/src/download/download-engine/download-file/download-engine-file.ts @@ -22,7 +22,7 @@ export type DownloadEngineFileOptions = { onStartedAsync?: () => Promise onCloseAsync?: () => Promise onPausedAsync?: () => Promise - onSaveProgressAsync?: (progress: SaveProgressInfo) => Promise + onSaveProgress?: (progress: SaveProgressInfo) => void programType?: AvailablePrograms autoIncreaseParallelStreams?: boolean @@ -33,6 +33,7 @@ export type DownloadEngineFileOptions = { export type DownloadEngineFileOptionsWithDefaults = DownloadEngineFileOptions & { chunkSize: number; parallelStreams: number; + progressThrottleMs: number; }; export type DownloadEngineFileEvents = { @@ -52,7 +53,8 @@ const DEFAULT_CHUNKS_SIZE_FOR_STREAM_PROGRAM = 1024 * 1024; // 1MB const DEFAULT_OPTIONS: Omit = { chunkSize: 0, parallelStreams: 3, - autoIncreaseParallelStreams: true + autoIncreaseParallelStreams: true, + progressThrottleMs: 50 }; export default class DownloadEngineFile extends EventEmitter { @@ -283,7 +285,7 @@ export default class DownloadEngineFile extends EventEmitter { getContext().streamBytes = length; - this._sendProgressDownloadPart(); + this._throttledSendProgress(); } }); @@ -361,18 +363,13 @@ export default class DownloadEngineFile extends EventEmitter { - if (thisProgress === this._latestProgressDate && !this._closed && this._downloadStatus !== DownloadStatus.Finished) { - await this.options.onSaveProgressAsync?.(this._progress); - } - }); + this.options.onSaveProgress?.(this._progress); } protected _sendProgressDownloadPart() { @@ -380,6 +377,15 @@ export default class DownloadEngineFile extends EventEmitter= this.options.progressThrottleMs) { + this._sendProgressDownloadPart(); + return; + } + } + public async pause() { if (this.options.fetchStream.paused) { return; diff --git a/src/download/download-engine/engine/download-engine-nodejs.ts b/src/download/download-engine/engine/download-engine-nodejs.ts index 9ff74dc..9d9b1b2 100644 --- a/src/download/download-engine/engine/download-engine-nodejs.ts +++ b/src/download/download-engine/engine/download-engine-nodejs.ts @@ -49,9 +49,9 @@ export default class DownloadEngineNodejs { + this._engine.options.onSaveProgress = (progress) => { if (this.options.skipExisting) return; - await this.options.writeStream.saveMetadataAfterFile(progress); + this.options.writeStream.saveMetadataAfterFlush(progress); }; this._engine.options.onPausedAsync = async () => { diff --git a/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-nodejs.ts b/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-nodejs.ts index 8e67fbe..872ce77 100644 --- a/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-nodejs.ts +++ b/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-nodejs.ts @@ -1,26 +1,28 @@ -import fs, {FileHandle} from "fs/promises"; -import fsExtra from "fs-extra"; import retry from "async-retry"; -import {withLock} from "lifecycle-utils"; +import fsExtra from "fs-extra"; +import fs, { FileHandle } from "fs/promises"; +import { withLock } from "lifecycle-utils"; import BaseDownloadEngineWriteStream from "./base-download-engine-write-stream.js"; -import WriterIsClosedError from "./errors/writer-is-closed-error.js"; +import WriteQueue from "./utils/WriteQueue.js"; export type DownloadEngineWriteStreamOptionsNodeJS = { - retry?: retry.Options + retry?: retry.Options; mode: string; - /**@deprecated This functionality had been remove duo to performance issues **/ + /** @deprecated Use writeBufferMaxBytes instead */ debounceWrite?: { - maxTime?: number - maxSize?: number - } + maxTime?: number; + maxSize?: number; + }; + + /** Maximum bytes to buffer before flushing to disk (default: adaptive 5% of file size, min 2MB, max 64MB) */ + writeBufferMaxBytes?: number; }; +const MAX_META_SIZE = 10485760; // 10 MB + const DEFAULT_OPTIONS = { mode: "r+" } satisfies DownloadEngineWriteStreamOptionsNodeJS; -const MAX_META_SIZE = 10485760; // 10 MB - -const NOT_ENOUGH_SPACE_ERROR_CODE = "ENOSPC"; export default class DownloadEngineWriteStreamNodejs extends BaseDownloadEngineWriteStream { private static _allFd = new Set(); @@ -33,21 +35,33 @@ export default class DownloadEngineWriteStreamNodejs extends BaseDownloadEngineW private _finalToken = {}; private _fd: FileHandle | null = null; - private _fileWriteFinished = false; private _fileSize = 0; - private _lastWritePromise: Promise | null = null; + private _writeQueue: WriteQueue; + private _metadataToSave: any = null; public readonly options: DownloadEngineWriteStreamOptionsNodeJS; - public autoDebounceMaxSize = false; constructor(public path: string, public finalPath: string, options: Partial = {}) { super(); - this.autoDebounceMaxSize = !options.debounceWrite?.maxSize; this.options = { ...DEFAULT_OPTIONS, ...options }; + + this._writeQueue = new WriteQueue({ + getFd: this._ensureFileOpen.bind(this), + writeBufferMaxBytes: this.options.writeBufferMaxBytes || this.options.debounceWrite?.maxSize, + flushMetadata: () => { + if (this._metadataToSave) { + const metadata = this._metadataToSave; + this._metadataToSave = null; + return this._saveMetadata(metadata); + } + + return Promise.resolve(); + } + }); } public get fileSize() { @@ -56,10 +70,15 @@ export default class DownloadEngineWriteStreamNodejs extends BaseDownloadEngineW public set fileSize(value) { this._fileSize = value; + this._writeQueue.setFileSize(value); } - private async _ensureFileOpen() { - return await withLock(this, "_lock", async () => { + private _ensureFileOpen() { + if (this._fd) { + return this._fd; + } + + return withLock(this, "_lock", async () => { if (this._fd) { return this._fd; } @@ -74,52 +93,39 @@ export default class DownloadEngineWriteStreamNodejs extends BaseDownloadEngineW }); } - async write(cursor: number, buffers: Uint8Array[]) { - let throwError: Error | false = false; - - await retry(async () => { - try { - return await this._writeWithoutRetry(cursor, buffers); - } catch (error: any) { - if (error?.code === NOT_ENOUGH_SPACE_ERROR_CODE) { - throwError = error; - return; - } - throw error; - } - }, this.options.retry); - - if (throwError) { - throw throwError; - } + /** + * Buffer a write for the given cursor position. + * Fragments are concatenated into a single Buffer, contiguous regions merged, + * and auto-flushed when the adaptive threshold is exceeded. + * Returns void (synchronous queue); errors surface via ensureBytesSynced() / close(). + */ + write(cursor: number, buffers: Uint8Array[]) { + return this._writeQueue.addWrite(cursor, buffers); } - async ensureBytesSynced() { - if (this._lastWritePromise) { - await this._lastWritePromise; - } + ensureBytesSynced() { + return this._writeQueue.drain(); } async ftruncate(size = this._fileSize) { await this.ensureBytesSynced(); - this._fileWriteFinished = true; await retry(async () => { const fd = await this._ensureFileOpen(); await fd.truncate(size); }, this.options.retry); } - async saveMetadataAfterFile(data: any) { - if (this._fileWriteFinished) { - throw new WriterIsClosedError(); - } + saveMetadataAfterFlush(data: any) { + this._metadataToSave = data; + } + private async _saveMetadata(data: any) { const jsonString = JSON.stringify(data); + const uint8Array = new TextEncoder().encode(jsonString); - const encoder = new TextEncoder(); - const uint8Array = encoder.encode(jsonString); - - await this.write(this._fileSize, [uint8Array]); + const fdResult = this._ensureFileOpen(); + const fd = fdResult instanceof Promise ? await fdResult : fdResult; + await fd.write(uint8Array, 0, uint8Array.length, this._fileSize); } async loadMetadataAfterFileWithoutRetry() { @@ -145,21 +151,16 @@ export default class DownloadEngineWriteStreamNodejs extends BaseDownloadEngineW try { return JSON.parse(metadataString); - } catch {} + } catch { } } finally { await this.close(); } } - private async _writeWithoutRetry(cursor: number, buffers: Uint8Array[]) { - return await (this._lastWritePromise = withLock(this, "lockWriteOperation", async () => { - const fd = await this._ensureFileOpen(); - const {bytesWritten} = await fd.writev(buffers, cursor); - return bytesWritten; - })); - } - override async close() { + this._writeQueue.close(); + await this._writeQueue.drain(); + if (!this._fd) { return; } diff --git a/src/download/download-engine/streams/download-engine-write-stream/utils/WriteQueue.ts b/src/download/download-engine/streams/download-engine-write-stream/utils/WriteQueue.ts new file mode 100644 index 0000000..d473253 --- /dev/null +++ b/src/download/download-engine/streams/download-engine-write-stream/utils/WriteQueue.ts @@ -0,0 +1,164 @@ +import { FileHandle } from "fs/promises"; + +const MIN_BUFFER_SIZE = 2 * 1024 * 1024; // 2 MB +const MAX_BUFFER_SIZE = 64 * 1024 * 1024; // 64 MB +const ADAPTIVE_PERCENT = 0.05; + +export type WriteQueueOptions = { + writeBufferMaxBytes?: number; + flushMetadata: () => Promise; + getFd: () => Promise | FileHandle; +}; + +type PendingRegion = { + cursor: number; + buffers: Uint8Array[]; + length: number; +}; + +/** + * High-performance write buffer that coalesces contiguous writes + * and flushes them as parallel positional writes to the file descriptor. + * + * Design principles: + * - No locks — parallel positional writes (`fd.write` with offset) are safe for non-overlapping regions + * - Contiguous buffer merging — sequential writes from the same stream are combined + * - Adaptive threshold — flush size scales with file size (5%, min 2MB, max 64MB) + * - Data-driven flushes — threshold exceeded → immediate flush + * - Explicit drain on pause/close/truncate — no timers needed + */ +export default class WriteQueue { + private _options: WriteQueueOptions; + private _regions: PendingRegion[] = []; + private _totalBuffered = 0; + private _maxBufferedBytes: number = 0; + private _inFlightWrites = new Set>(); + private _closed = false; + + constructor(options: WriteQueueOptions) { + this._options = options; + + if (options?.writeBufferMaxBytes) { + this._maxBufferedBytes = options.writeBufferMaxBytes; + } + } + + setFileSize(fileSize: number) { + if (this._options?.writeBufferMaxBytes) return; + + this._maxBufferedBytes = Math.min( + Math.max(fileSize * ADAPTIVE_PERCENT, MIN_BUFFER_SIZE), + MAX_BUFFER_SIZE + ); + } + + /** + * Buffer a write. Concatenates fragments into a single Buffer, + * merges with adjacent regions, and flushes when threshold is exceeded. + */ + addWrite(cursor: number, buffers: Uint8Array[]): void | Promise { + if (this._closed) return; + + const length = buffers.reduce((sum, buf) => sum + buf.length, 0); + + const merged = this._tryMerge(cursor, buffers, length); + if (!merged) { + this._regions.push({ cursor, buffers, length }); + } + + this._totalBuffered += length; + if (this._totalBuffered >= this._maxBufferedBytes) { + return this._flushNow(); + } + } + + /** + * Try to merge the new buffer with an existing region if contiguous. + */ + private _tryMerge(cursor: number, buffers: Uint8Array[], length: number): boolean { + for (let i = 0; i < this._regions.length; i++) { + const region = this._regions[i]; + const regionEnd = region.cursor + region.length; + + if (cursor === regionEnd) { + region.buffers.push(...buffers); + region.length += length; + return true; + } + + if (cursor + length === region.cursor) { + region.cursor = cursor; + region.buffers.unshift(...buffers); + region.length += length; + return true; + } + } + + return false; + } + + /** + * Flush all buffered regions to disk as parallel positional writes. + * Non-overlapping positional writes via fd.write(buf, 0, len, position) are safe concurrently. + */ + private _flushNow(): void | Promise { + if (this._regions.length === 0) return; + + const regionsToFlush = this._regions; + this._regions = []; + this._totalBuffered = 0; + + const flushPromise = this._doFlush(regionsToFlush) + .finally(() => this._inFlightWrites.delete(flushPromise)); + + this._inFlightWrites.add(flushPromise); + + return flushPromise; + } + + private async _doFlush(regions: PendingRegion[]): Promise { + const fdResult = this._options.getFd(); + const fd = fdResult instanceof Promise ? await fdResult : fdResult; + + // Write all non-overlapping regions in parallel + const writes: Promise[] = regions.map(region => + fd.writev(region.buffers, region.cursor) + ); + + writes.push(this._options.flushMetadata()); + + await Promise.all(writes); + } + + /** + * Flush all buffered data and wait for all in-flight writes to complete. + * Called by ensureBytesSynced(), close(), ftruncate(). + * Re-throws the first write error encountered since last drain. + */ + async drain(): Promise { + this._flushNow(); + await this._waitForInFlight(); + } + + private async _waitForInFlight(): Promise { + while (this._inFlightWrites.size > 0) { + await Promise.all(this._inFlightWrites); + } + } + + close() { + this._closed = true; + } + + get bufferedBytes(): number { + return this._totalBuffered; + } + + get pendingRegions(): number { + return this._regions.length; + } + + get inFlightCount(): number { + return this._inFlightWrites.size; + } +} From 4ff195e4a3384fa515ced3662b0f835cf95ef094 Mon Sep 17 00:00:00 2001 From: ido Date: Sun, 15 Mar 2026 12:13:26 +0100 Subject: [PATCH 16/33] feat(cli): faster event creation --- package-lock.json | 38 --- package.json | 2 - .../download-file/download-engine-file.ts | 88 +++--- .../download-file/progress-status-file.ts | 87 ++---- .../download-engine-fetch-stream-fetch.ts | 4 +- .../download-engine-fetch-stream-xhr.ts | 16 +- .../format-transfer-status.ts | 25 +- .../progress-statistics-builder.ts | 6 +- .../transfer-cli/GlobalCLI.ts | 29 +- .../multiProgressBars/BaseMultiProgressBar.ts | 2 +- .../fancy-transfer-cli-progress-bar.ts | 13 +- .../transfer-cli/transfer-cli.ts | 60 ++--- .../utils/prettyBytesFast.ts | 253 ++++++++++++++++++ .../transfer-visualize/utils/prettyMSFast.ts | 47 ++++ 14 files changed, 426 insertions(+), 244 deletions(-) create mode 100644 src/download/transfer-visualize/utils/prettyBytesFast.ts create mode 100644 src/download/transfer-visualize/utils/prettyMSFast.ts diff --git a/package-lock.json b/package-lock.json index 47bf383..cceb41b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,8 +22,6 @@ "lifecycle-utils": "^1.3.1", "lodash.debounce": "^4.0.8", "lowdb": "^7.0.1", - "pretty-bytes": "^6.1.0", - "pretty-ms": "^8.0.0", "sleep-promise": "^9.1.0", "slice-ansi": "^7.1.0", "stdout-update": "^4.0.1", @@ -10033,17 +10031,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/parse-ms": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-3.0.0.tgz", - "integrity": "sha512-Tpb8Z7r7XbbtBTrM9UhpkzzaMrqA2VXMT3YChzYltwV3P3pM6t8wl7TvpMnSTosz1aQAdVib7kdoys7vYOPerw==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/parse5": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", @@ -10278,31 +10265,6 @@ "node": ">= 0.8.0" } }, - "node_modules/pretty-bytes": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz", - "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==", - "engines": { - "node": "^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pretty-ms": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-8.0.0.tgz", - "integrity": "sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q==", - "dependencies": { - "parse-ms": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", diff --git a/package.json b/package.json index 8d3d29b..b77ef88 100644 --- a/package.json +++ b/package.json @@ -144,8 +144,6 @@ "lifecycle-utils": "^1.3.1", "lodash.debounce": "^4.0.8", "lowdb": "^7.0.1", - "pretty-bytes": "^6.1.0", - "pretty-ms": "^8.0.0", "sleep-promise": "^9.1.0", "slice-ansi": "^7.1.0", "stdout-update": "^4.0.1", diff --git a/src/download/download-engine/download-file/download-engine-file.ts b/src/download/download-engine/download-file/download-engine-file.ts index 2797ad6..4313f6f 100644 --- a/src/download/download-engine/download-file/download-engine-file.ts +++ b/src/download/download-engine/download-file/download-engine-file.ts @@ -1,30 +1,29 @@ -import ProgressStatusFile, {DownloadFlags, DownloadStatus, ProgressStatus} from "./progress-status-file.js"; -import {ChunkStatus, DownloadFile, SaveProgressInfo} from "../types.js"; +import { DownloadFlags, DownloadStatus, ProgressStatus } from "./progress-status-file.js"; +import { ChunkStatus, DownloadFile, SaveProgressInfo } from "../types.js"; import BaseDownloadEngineFetchStream from "../streams/download-engine-fetch-stream/base-download-engine-fetch-stream.js"; import BaseDownloadEngineWriteStream from "../streams/download-engine-write-stream/base-download-engine-write-stream.js"; import retry from "async-retry"; -import {EventEmitter} from "eventemitter3"; -import {withLock} from "lifecycle-utils"; -import switchProgram, {AvailablePrograms} from "./download-programs/switch-program.js"; +import { EventEmitter } from "eventemitter3"; +import switchProgram, { AvailablePrograms } from "./download-programs/switch-program.js"; import BaseDownloadProgram from "./download-programs/base-download-program.js"; -import {pushComment} from "./utils/push-comment.js"; -import {uid} from "uid"; -import {DownloaderProgramManager} from "./downloaderProgramManager.js"; +import { pushComment } from "./utils/push-comment.js"; +import { uid } from "uid"; +import { DownloaderProgramManager } from "./downloaderProgramManager.js"; export type DownloadEngineFileOptions = { chunkSize?: number; parallelStreams?: number; - retry?: retry.Options - comment?: string + retry?: retry.Options; + comment?: string; fetchStream: BaseDownloadEngineFetchStream, writeStream: BaseDownloadEngineWriteStream, - onFinishAsync?: () => Promise - onStartedAsync?: () => Promise - onCloseAsync?: () => Promise - onPausedAsync?: () => Promise - onSaveProgress?: (progress: SaveProgressInfo) => void - programType?: AvailablePrograms - autoIncreaseParallelStreams?: boolean + onFinishAsync?: () => Promise; + onStartedAsync?: () => Promise; + onCloseAsync?: () => Promise; + onPausedAsync?: () => Promise; + onSaveProgress?: (progress: SaveProgressInfo) => void; + programType?: AvailablePrograms; + autoIncreaseParallelStreams?: boolean; /** @internal */ skipExisting?: boolean; @@ -37,14 +36,14 @@ export type DownloadEngineFileOptionsWithDefaults = DownloadEngineFileOptions & }; export type DownloadEngineFileEvents = { - start: () => void - paused: () => void - resumed: () => void - progress: (progress: ProgressStatus) => void - save: (progress: SaveProgressInfo) => void - finished: () => void - closed: () => void - [key: string]: any + start: () => void; + paused: () => void; + resumed: () => void; + progress: (progress: ProgressStatus) => void; + save: (progress: SaveProgressInfo) => void; + finished: () => void; + closed: () => void; + [key: string]: any; }; const DEFAULT_CHUNKS_SIZE_FOR_CHUNKS_PROGRAM = 1024 * 1024 * 5; // 5MB @@ -54,7 +53,7 @@ const DEFAULT_OPTIONS: Omit { @@ -70,14 +69,14 @@ export default class DownloadEngineFile extends EventEmitter; protected _activeStreamContext: { [key: number]: { streamBytes: number, - retryingAttempts: number + retryingAttempts: number; isRetrying?: boolean, - isStreamNotResponding?: boolean - } + isStreamNotResponding?: boolean; + }; } = {}; protected _activeProgram?: BaseDownloadProgram; @@ -87,8 +86,13 @@ export default class DownloadEngineFile extends EventEmitter c.isRetrying); @@ -215,7 +228,7 @@ export default class DownloadEngineFile extends EventEmitter this._activeStreamContext[startChunk] ??= {streamBytes: 0, retryingAttempts: 0}; + const getContext = () => this._activeStreamContext[startChunk] ??= { streamBytes: 0, retryingAttempts: 0 }; const fetchState = this.options.fetchStream.withSubState({ chunkSize: this._progress.chunkSize, @@ -382,6 +395,7 @@ export default class DownloadEngineFile extends EventEmitter= this.options.progressThrottleMs) { this._sendProgressDownloadPart(); + this._latestProgressDate = Date.now(); return; } } @@ -411,7 +425,7 @@ export default class DownloadEngineFile extends EventEmitter Promise> | ReadableStreamReadResult; export default class DownloadEngineFetchStreamFetch extends BaseDownloadEngineFetchStream { @@ -164,7 +164,7 @@ export default class DownloadEngineFetchStreamFetch extends BaseDownloadEngineFe const timeoutMaxStreamWait = setTimeout(() => { timeoutMaxStreamWaitThrows = true; - reject(new EmptyStreamTimeoutError(`Stream timeout after ${prettyMilliseconds(this.options.maxStreamWait!)}`)); + reject(new EmptyStreamTimeoutError(`Stream timeout after ${prettyMillisecondsCompact(this.options.maxStreamWait!)}`)); this._activeController?.abort(); }, this.options.maxStreamWait); diff --git a/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-xhr.ts b/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-xhr.ts index 344a937..91fd41d 100644 --- a/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-xhr.ts +++ b/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-xhr.ts @@ -1,3 +1,6 @@ +import retry from "async-retry"; +import prettyMillisecondsCompact from "../../../transfer-visualize/utils/prettyMSFast.js"; +import { AvailablePrograms } from "../../download-file/download-programs/switch-program.js"; import BaseDownloadEngineFetchStream, { DownloadInfoResponse, FetchSubState, @@ -6,15 +9,12 @@ import BaseDownloadEngineFetchStream, { WriteCallback } from "./base-download-engine-fetch-stream.js"; import EmptyResponseError from "./errors/empty-response-error.js"; +import { EmptyStreamTimeoutError } from "./errors/EmptyStreamTimeoutError.js"; +import InvalidContentLengthError from "./errors/invalid-content-length-error.js"; import StatusCodeError from "./errors/status-code-error.js"; import XhrError from "./errors/xhr-error.js"; -import InvalidContentLengthError from "./errors/invalid-content-length-error.js"; -import retry from "async-retry"; -import {AvailablePrograms} from "../../download-file/download-programs/switch-program.js"; -import {parseContentDisposition} from "./utils/content-disposition.js"; -import {parseHttpContentRange} from "./utils/httpRange.js"; -import prettyMilliseconds from "pretty-ms"; -import {EmptyStreamTimeoutError} from "./errors/EmptyStreamTimeoutError.js"; +import { parseContentDisposition } from "./utils/content-disposition.js"; +import { parseHttpContentRange } from "./utils/httpRange.js"; export default class DownloadEngineFetchStreamXhr extends BaseDownloadEngineFetchStream { @@ -80,7 +80,7 @@ export default class DownloadEngineFetchStreamXhr extends BaseDownloadEngineFetc }, STREAM_NOT_RESPONDING_TIMEOUT); lastMaxStreamWaitTimeoutIndex = setTimeout(() => { - reject(new EmptyStreamTimeoutError(`Stream timeout after ${prettyMilliseconds(this.options.maxStreamWait!)}`)); + reject(new EmptyStreamTimeoutError(`Stream timeout after ${prettyMillisecondsCompact(this.options.maxStreamWait!)}`)); xhr.abort(); }, this.options.maxStreamWait); }; diff --git a/src/download/transfer-visualize/format-transfer-status.ts b/src/download/transfer-visualize/format-transfer-status.ts index 126360d..c298859 100644 --- a/src/download/transfer-visualize/format-transfer-status.ts +++ b/src/download/transfer-visualize/format-transfer-status.ts @@ -1,7 +1,7 @@ -import {TransferProgressInfo} from "./transfer-statistics.js"; -import prettyBytes, {Options as PrettyBytesOptions} from "pretty-bytes"; -import prettyMilliseconds, {Options as PrettyMsOptions} from "pretty-ms"; -import {DownloadStatus, ProgressStatus} from "../download-engine/download-file/progress-status-file.js"; +import { DownloadStatus, ProgressStatus } from "../download-engine/download-file/progress-status-file.js"; +import { TransferProgressInfo } from "./transfer-statistics.js"; +import prettyBytes, { PrettyBytesOptions, formatTrunc } from "./utils/prettyBytesFast.js"; +import prettyMillisecondsCompact from "./utils/prettyMSFast.js"; const DEFAULT_LOCALIZATION: Intl.LocalesArgument = "en-US"; @@ -26,14 +26,9 @@ const NUMBER_FORMAT_OPTIONS: Intl.NumberFormatOptions = { minimumIntegerDigits: 3 }; -export const PRETTY_MS_OPTIONS: PrettyMsOptions = { - ...NUMBER_FORMAT_OPTIONS, - keepDecimalsOnWholeSeconds: true, - secondsDecimalDigits: 2, - compact: true -}; +const PERCENTAGE_FRACTION_DIGITS = 4; -const PRETTY_BYTES_OPTIONS: PrettyBytesOptions = {...NUMBER_FORMAT_OPTIONS, space: false, locale: DEFAULT_LOCALIZATION}; +const PRETTY_BYTES_OPTIONS: PrettyBytesOptions = {...NUMBER_FORMAT_OPTIONS, space: false }; const DEFAULT_CLI_INFO_STATUS: CliInfoStatus = { speed: 0, @@ -58,12 +53,8 @@ export function createFormattedStatus(status: ProgressStatus | FormattedStatus): const formatTransferred = prettyBytes(fullStatus.transferredBytes, PRETTY_BYTES_OPTIONS); const formatTotal = fullStatus.totalBytes === 0 ? "???" : prettyBytes(fullStatus.totalBytes, PRETTY_BYTES_OPTIONS); const formatTransferredOfTotal = `${formatTransferred}/${formatTotal}`; - const formatTimeLeft = fullStatus.totalBytes === 0 ? "unknown time" : prettyMilliseconds(fullStatus.timeLeft, PRETTY_MS_OPTIONS); - const formattedPercentage = fullStatus.percentage.toLocaleString(DEFAULT_LOCALIZATION, { - minimumIntegerDigits: 1, - minimumFractionDigits: 4 - }) - .slice(0, 5) + "%"; + const formatTimeLeft = fullStatus.totalBytes === 0 ? "unknown time" : prettyMillisecondsCompact(fullStatus.timeLeft); + const formattedPercentage = formatTrunc(fullStatus.percentage, PERCENTAGE_FRACTION_DIGITS, PERCENTAGE_FRACTION_DIGITS).slice(0, 5) + "%"; let fullComment = fullStatus.comment; if (status.downloadStatus === DownloadStatus.Cancelled || status.downloadStatus === DownloadStatus.Paused) { diff --git a/src/download/transfer-visualize/progress-statistics-builder.ts b/src/download/transfer-visualize/progress-statistics-builder.ts index ea29770..9fabac6 100644 --- a/src/download/transfer-visualize/progress-statistics-builder.ts +++ b/src/download/transfer-visualize/progress-statistics-builder.ts @@ -3,7 +3,7 @@ import {EventEmitter} from "eventemitter3"; import TransferStatistics from "./transfer-statistics.js"; import {createFormattedStatus, FormattedStatus} from "./format-transfer-status.js"; import DownloadEngineFile from "../download-engine/download-file/download-engine-file.js"; -import ProgressStatusFile, {DownloadStatus, ProgressStatus} from "../download-engine/download-file/progress-status-file.js"; +import {DownloadStatus, EMPTY_PROGRESS_STATUS, ProgressStatus} from "../download-engine/download-file/progress-status-file.js"; import DownloadEngineMultiDownload from "../download-engine/engine/download-engine-multi-download.js"; import {DownloadEngineRemote} from "../download-engine/engine/DownloadEngineRemote.js"; @@ -179,11 +179,9 @@ export default class ProgressStatisticsBuilder extends EventEmitter - this._transferCLI.updateStatuesLazy(() => [ + const printProgress = (progress: FormattedStatus, debounce = true) => + this._transferCLI.updateStatues(() => [ this._getCLIStatuses(), progress, this._multiDownloadEngine.loadingDownloads - ]); + ], debounce); this._multiDownloadEngine.on("progress", (progress) => { if (!this._cliActive) return; @@ -144,10 +144,7 @@ class GlobalCLI { }); this._multiDownloadEngine.on("finished", () => { - if (this._transferCLI.isFirstPrint) { - printProgress(this._multiDownloadEngine.status); - } - this._transferCLI.isFirstPrint = true; + printProgress(this._multiDownloadEngine.status, false); this._multiDownloadEngine = this._createMultiDownloadEngine(); this._eventsRegistered = new Set(); this._cachedCliEngines = []; @@ -192,7 +189,7 @@ class GlobalCLI { } private static _createOptions(options: CliProgressDownloadEngineOptions) { - const cliOptions: Partial = {...options}; + const cliOptions: Partial = { ...options }; cliOptions.createProgressBar ??= typeof options.cliStyle === "function" ? { createStatusLine: options.cliStyle, diff --git a/src/download/transfer-visualize/transfer-cli/multiProgressBars/BaseMultiProgressBar.ts b/src/download/transfer-visualize/transfer-cli/multiProgressBars/BaseMultiProgressBar.ts index d4c5086..511abc5 100644 --- a/src/download/transfer-visualize/transfer-cli/multiProgressBars/BaseMultiProgressBar.ts +++ b/src/download/transfer-visualize/transfer-cli/multiProgressBars/BaseMultiProgressBar.ts @@ -2,8 +2,8 @@ import {TransferCliProgressBar} from "../progress-bars/base-transfer-cli-progres import {FormattedStatus} from "../../format-transfer-status.js"; import {DownloadStatus} from "../../../download-engine/download-file/progress-status-file.js"; import chalk from "chalk"; -import prettyBytes from "pretty-bytes"; import cliSpinners from "cli-spinners"; +import prettyBytes from "../../utils/prettyBytesFast.js"; export type MultiProgressBarOptions = { maxViewDownloads: number; diff --git a/src/download/transfer-visualize/transfer-cli/progress-bars/fancy-transfer-cli-progress-bar.ts b/src/download/transfer-visualize/transfer-cli/progress-bars/fancy-transfer-cli-progress-bar.ts index e54648e..c88a475 100644 --- a/src/download/transfer-visualize/transfer-cli/progress-bars/fancy-transfer-cli-progress-bar.ts +++ b/src/download/transfer-visualize/transfer-cli/progress-bars/fancy-transfer-cli-progress-bar.ts @@ -1,12 +1,11 @@ import chalk from "chalk"; -import {PRETTY_MS_OPTIONS} from "../../format-transfer-status.js"; -import {DataLine, renderDataLine} from "../../utils/data-line.js"; -import prettyMilliseconds from "pretty-ms"; import sliceAnsi from "slice-ansi"; import stripAnsi from "strip-ansi"; -import {DownloadStatus} from "../../../download-engine/download-file/progress-status-file.js"; +import { DownloadStatus } from "../../../download-engine/download-file/progress-status-file.js"; +import { DataLine, renderDataLine } from "../../utils/data-line.js"; +import prettyMillisecondsCompact from "../../utils/prettyMSFast.js"; +import { STATUS_ICONS } from "../../utils/progressBarIcons.js"; import BaseTransferCliProgressBar from "./base-transfer-cli-progress-bar.js"; -import {STATUS_ICONS} from "../../utils/progressBarIcons.js"; /** * A class to display transfer progress in the terminal, with a progress bar and other information. @@ -79,8 +78,8 @@ export default class FancyTransferCliProgressBar extends BaseTransferCliProgress const downloadTime = (endTime || Date.now()) - startTime; const finishedText = wasSuccessful - ? `downloaded ${this.status.formatTransferred} in ${prettyMilliseconds(downloadTime, PRETTY_MS_OPTIONS)}` - : `failed downloading after ${prettyMilliseconds(endTime - startTime, PRETTY_MS_OPTIONS)}`; + ? `downloaded ${this.status.formatTransferred} in ${prettyMillisecondsCompact(downloadTime)}` + : `failed downloading after ${prettyMillisecondsCompact(endTime - startTime)}`; return renderDataLine([{ type: "status", diff --git a/src/download/transfer-visualize/transfer-cli/transfer-cli.ts b/src/download/transfer-visualize/transfer-cli/transfer-cli.ts index f856df6..0e3e82f 100644 --- a/src/download/transfer-visualize/transfer-cli/transfer-cli.ts +++ b/src/download/transfer-visualize/transfer-cli/transfer-cli.ts @@ -1,17 +1,15 @@ import UpdateManager from "stdout-update"; -import {TransferCliProgressBar} from "./progress-bars/base-transfer-cli-progress-bar.js"; +import { TransferCliProgressBar } from "./progress-bars/base-transfer-cli-progress-bar.js"; import cliSpinners from "cli-spinners"; -import {FormattedStatus} from "../format-transfer-status.js"; +import { FormattedStatus } from "../format-transfer-status.js"; import switchCliProgressStyle from "./progress-bars/switch-cli-progress-style.js"; -import {BaseMultiProgressBar} from "./multiProgressBars/BaseMultiProgressBar.js"; -import {abortableDebounce} from "../utils/abortableDebounce.js"; +import { BaseMultiProgressBar } from "./multiProgressBars/BaseMultiProgressBar.js"; export type TransferCliOptions = { name?: string, maxViewDownloads: number; truncateName: boolean | number; debounceWait: number; - maxDebounceWait: number; createProgressBar: TransferCliProgressBar; createMultiProgressBar: typeof BaseMultiProgressBar, loadingAnimation: cliSpinners.SpinnerName, @@ -20,8 +18,7 @@ export type TransferCliOptions = { export const DEFAULT_TRANSFER_CLI_OPTIONS: TransferCliOptions = { maxViewDownloads: 10, truncateName: true, - debounceWait: 20, - maxDebounceWait: process.platform === "win32" ? 500 : 100, + debounceWait: process.platform === "win32" ? 500 : 45, createProgressBar: switchCliProgressStyle("auto", {truncateName: true}), loadingAnimation: "dots", createMultiProgressBar: BaseMultiProgressBar @@ -33,28 +30,17 @@ export default class TransferCli { protected latestProgress: [FormattedStatus[], FormattedStatus, number] = null!; protected latestProgressGetter: (() => [FormattedStatus[], FormattedStatus, number]) | null = null; private _cliStopped = true; - private _updateStatuesDebounce: () => void = this._updateStatues; - private _abortDebounce = new AbortController(); private _multiProgressBar: BaseMultiProgressBar; - public isFirstPrint = true; private _lastProgressLong = ""; + private _lastUpdateTime = 0; + private _shouldExitOnSIGINT = false; public constructor(options: Partial) { - this.options = {...DEFAULT_TRANSFER_CLI_OPTIONS, ...options}; + this.options = { ...DEFAULT_TRANSFER_CLI_OPTIONS, ...options }; this._multiProgressBar = new this.options.createProgressBar.multiProgressBar(this.options); - this._updateStatues = this._updateStatues.bind(this); + this.updateStatues = this.updateStatues.bind(this); this._processExit = this._processExit.bind(this); - this._resetDebounce(); - } - - private _resetDebounce() { - const maxDebounceWait = this._multiProgressBar.updateIntervalMs || this.options.maxDebounceWait; - this._abortDebounce = new AbortController(); - this._updateStatuesDebounce = abortableDebounce(this._updateStatues.bind(this), { - wait: maxDebounceWait, - signal: this._abortDebounce.signal - }); } start() { @@ -63,46 +49,36 @@ export default class TransferCli { if (this._multiProgressBar.printType === "update") { this.stdoutManager.hook(); } + + this._shouldExitOnSIGINT = process.listenerCount("SIGINT") === 0; process.on("SIGINT", this._processExit); } stop() { if (this._cliStopped) return; this._cliStopped = true; - this._updateStatues(); if (this._multiProgressBar.printType === "update") { this.stdoutManager.unhook(false); } process.off("SIGINT", this._processExit); - this._abortDebounce.abort(); - this._resetDebounce(); } private _processExit() { this.stop(); - process.exit(0); - } - updateStatues(statues: FormattedStatus[], oneStatus: FormattedStatus, loadingDownloads = 0) { - this.latestProgressGetter = null; - this.latestProgress = [statues, oneStatus, loadingDownloads]; - - if (this.isFirstPrint) { - this.isFirstPrint = false; - this._updateStatues(); - } else { - this._updateStatuesDebounce(); + if (this._shouldExitOnSIGINT) { + process.exit(0); } } - updateStatuesLazy(getLatestProgress: () => [FormattedStatus[], FormattedStatus, number]) { + updateStatues(getLatestProgress: () => [FormattedStatus[], FormattedStatus, number], debounce = true) { this.latestProgressGetter = getLatestProgress; - if (this.isFirstPrint) { - this.isFirstPrint = false; - this._updateStatues(); - } else { - this._updateStatuesDebounce(); + if(debounce && Date.now() - this._lastUpdateTime < this.options.debounceWait) { + return; } + + this._lastUpdateTime = Date.now(); + this._updateStatues(); } private _updateStatues() { diff --git a/src/download/transfer-visualize/utils/prettyBytesFast.ts b/src/download/transfer-visualize/utils/prettyBytesFast.ts new file mode 100644 index 0000000..38e671b --- /dev/null +++ b/src/download/transfer-visualize/utils/prettyBytesFast.ts @@ -0,0 +1,253 @@ +export interface PrettyBytesOptions { + bits?: boolean; + binary?: boolean; + space?: boolean; + nonBreakingSpace?: boolean; + signed?: boolean; + locale?: string | string[] | boolean; + minimumFractionDigits?: number; + maximumFractionDigits?: number; + fixedWidth?: number; +} + +const BYTE_UNITS = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; +const BIBYTE_UNITS = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; +const BIT_UNITS = ['b', 'kbit', 'Mbit', 'Gbit', 'Tbit', 'Pbit', 'Ebit', 'Zbit', 'Ybit']; +const BIBIT_UNITS = ['b', 'kibit', 'Mibit', 'Gibit', 'Tibit', 'Pibit', 'Eibit', 'Zibit', 'Yibit']; + +const DECIMAL_DIVISORS = [1, 1e3, 1e6, 1e9, 1e12, 1e15, 1e18, 1e21, 1e24]; +const BINARY_DIVISORS = Array.from({ length: 9 }, (_, i) => 1024 ** i); +const LOG10_1024 = Math.log10(1024); + +const SIG3_GE100 = new Array(901); +const SIG3_GE10 = new Array(901); +const SIG3_GE1 = new Array(901); + +for (let i = 0; i <= 900; i++) { + const v = i + 100; + + SIG3_GE100[i] = String(v); + + const hi10 = (v / 10) | 0; + const lo10 = v - hi10 * 10; + SIG3_GE10[i] = lo10 === 0 ? String(hi10) : hi10 + '.' + lo10; + + const hi1 = (v / 100) | 0; + const rem1 = v - hi1 * 100; + const d1 = (rem1 / 10) | 0; + const d2 = rem1 - d1 * 10; + SIG3_GE1[i] = d2 === 0 + ? (d1 === 0 ? String(hi1) : hi1 + '.' + d1) + : hi1 + '.' + d1 + '' + d2; +} + +function sig3(n: number): string { + if (n >= 100) { + const i = (n + 0.5) | 0; + return SIG3_GE100[(i <= 999 ? i : 1000) - 100]; + } + if (n >= 10) { + const i = (n * 10 + 0.5) | 0; + return SIG3_GE10[(i <= 999 ? i : 1000) - 100]; + } + const i = (n * 100 + 0.5) | 0; + return SIG3_GE1[(i <= 999 ? i : 1000) - 100]; +} + +function sig3Binary(n: number): string { + if (n >= 1000) return String((n + 0.5) | 0); + return sig3(n); +} + +// Fast trunc-mode fraction formatter — avoids toLocaleString when no locale is set +const TRUNC_POWERS = [1, 10, 100, 1000, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10]; + +export function formatTrunc(n: number, minFrac: number, maxFrac: number): string { + const intPart = (n | 0); + const frac = n - intPart; + + if (maxFrac === 0 || (frac === 0 && minFrac === 0)) return String(intPart); + + const scale = TRUNC_POWERS[maxFrac]; + let fracInt = (frac * scale) | 0; + + let digits = maxFrac; + while (digits > minFrac && fracInt % 10 === 0) { + fracInt = (fracInt / 10) | 0; + digits--; + } + + if (digits === 0) return String(intPart); + + let fracStr = String(fracInt); + while (fracStr.length < digits) fracStr = '0' + fracStr; + return intPart + '.' + fracStr; +} + +function toLocaleStr( + num: number, + locale: string | string[] | boolean, + opts: Intl.NumberFormatOptions | undefined, +): string { + if (typeof locale === 'string' || Array.isArray(locale)) { + return num.toLocaleString(locale as string | string[], opts); + } + return num.toLocaleString(undefined, opts); +} + +function applyFixedWidth(result: string, fixedWidth: number): string { + if (typeof fixedWidth !== 'number' || !Number.isSafeInteger(fixedWidth) || fixedWidth < 0) { + throw new TypeError(`Expected fixedWidth to be a non-negative integer, got ${typeof fixedWidth}: ${fixedWidth}`); + } + return fixedWidth > 0 && result.length < fixedWidth ? result.padStart(fixedWidth, ' ') : result; +} + +function decimalExp(n: number): number { + if (n < 1e6) return n < 1e3 ? 0 : 1; + if (n < 1e12) return n < 1e9 ? 2 : 3; + if (n < 1e18) return n < 1e15 ? 4 : 5; + return n < 1e21 ? 6 : n < 1e24 ? 7 : 8; +} + +function binaryExp(n: number): number { + if (n < 1048576) return n < 1024 ? 0 : 1; + if (n < 1099511627776) return n < 1073741824 ? 2 : 3; + if (n < 1125899906842624) return 4; + if (n < BINARY_DIVISORS[6]) return 5; + if (n < BINARY_DIVISORS[7]) return 6; + if (n < BINARY_DIVISORS[8]) return 7; + return 8; +} + +function bigintLog10(n: bigint): number { + const s = n.toString(10); + return s.length + Math.log10(Number('0.' + s.slice(0, 15))); +} + +function bigintDivide(n: bigint, divisor: number): number { + const d = BigInt(divisor); + return Number(n / d) + (Number(n % d) / divisor); +} + +export default function prettyBytes(number: number | bigint, options?: PrettyBytesOptions): string { + if (options === undefined && typeof number === 'number') { + if (!Number.isFinite(number)) { + throw new TypeError(`Expected a finite number, got ${typeof number}: ${number}`); + } + const neg = number < 0; + if (neg) number = -number; + + let s: string; + if (number < 1) { + s = number + ' B'; + } else if (number < 1e3) { + s = sig3(number) + ' B'; + } else if (number < 1e6) { + s = sig3(number / 1e3) + ' kB'; + } else if (number < 1e9) { + s = sig3(number / 1e6) + ' MB'; + } else if (number < 1e12) { + s = sig3(number / 1e9) + ' GB'; + } else if (number < 1e15) { + s = sig3(number / 1e12) + ' TB'; + } else if (number < 1e18) { + s = sig3(number / 1e15) + ' PB'; + } else if (number < 1e21) { + s = sig3(number / 1e18) + ' EB'; + } else if (number < 1e24) { + s = sig3(number / 1e21) + ' ZB'; + } else { + s = sig3(number / 1e24) + ' YB'; + } + return neg ? '-' + s : s; + } + + return prettyBytesFull(number, options); +} + +function prettyBytesFull(number: number | bigint, options: PrettyBytesOptions | undefined): string { + if (typeof number !== 'bigint' && !Number.isFinite(number)) { + throw new TypeError(`Expected a finite number, got ${typeof number}: ${number}`); + } + + const bits = options?.bits ?? false; + const binary = options?.binary ?? false; + const space = options?.space ?? true; + const signed = options?.signed ?? false; + const locale = options?.locale; + const fixedWidth = options?.fixedWidth; + const minimumFractionDigits = options?.minimumFractionDigits; + const maximumFractionDigits = options?.maximumFractionDigits; + + const units = bits + ? (binary ? BIBIT_UNITS : BIT_UNITS) + : (binary ? BIBYTE_UNITS : BYTE_UNITS); + const separator = space ? (options?.nonBreakingSpace ? '\u00A0' : ' ') : ''; + + const isZero = typeof number === 'number' ? number === 0 : number === 0n; + if (signed && isZero) { + const result = ` 0${separator}${units[0]}`; + return fixedWidth !== undefined ? applyFixedWidth(result, fixedWidth) : result; + } + + const isNegative = number < 0; + const prefix = isNegative ? '-' : (signed ? '+' : ''); + if (isNegative) { + number = typeof number === 'bigint' ? -number : -(number as number); + } + + const hasFracOpts = minimumFractionDigits !== undefined || maximumFractionDigits !== undefined; + const useLocale = locale !== undefined; + + let result: string; + + const ltOne = typeof number === 'bigint' ? number < 1n : (number as number) < 1; + if (ltOne) { + const n = Number(number); + let ns: string; + if (useLocale) { + const localeOpts = hasFracOpts ? { + ...(minimumFractionDigits !== undefined && { minimumFractionDigits }), + ...(maximumFractionDigits !== undefined && { maximumFractionDigits }), + roundingMode: 'trunc', + } as Intl.NumberFormatOptions : undefined; + ns = toLocaleStr(n, locale, localeOpts); + } else if (hasFracOpts) { + ns = formatTrunc(n, minimumFractionDigits ?? 0, maximumFractionDigits ?? 20); + } else { + ns = String(n); + } + result = prefix + ns + separator + units[0]; + } else { + let exp: number; + let divided: number; + + if (typeof number === 'bigint') { + const l = bigintLog10(number); + exp = Math.min(Math.floor(binary ? l / LOG10_1024 : l / 3), units.length - 1); + divided = bigintDivide(number, (binary ? 1024 : 1000) ** exp); + } else { + exp = binary ? binaryExp(number as number) : decimalExp(number as number); + divided = (number as number) / (binary ? BINARY_DIVISORS[exp] : DECIMAL_DIVISORS[exp]); + } + + let ns: string; + if (useLocale) { + const localeOpts = hasFracOpts ? { + ...(minimumFractionDigits !== undefined && { minimumFractionDigits }), + ...(maximumFractionDigits !== undefined && { maximumFractionDigits }), + roundingMode: 'trunc', + } as Intl.NumberFormatOptions : undefined; + const val = hasFracOpts ? divided : Number(binary ? sig3Binary(divided) : sig3(divided)); + ns = toLocaleStr(val, locale, localeOpts); + } else if (hasFracOpts) { + ns = formatTrunc(divided, minimumFractionDigits ?? 0, maximumFractionDigits ?? 20); + } else { + ns = binary ? sig3Binary(divided) : sig3(divided); + } + + result = prefix + ns + separator + units[exp]; + } + + return fixedWidth !== undefined ? applyFixedWidth(result, fixedWidth) : result; +} \ No newline at end of file diff --git a/src/download/transfer-visualize/utils/prettyMSFast.ts b/src/download/transfer-visualize/utils/prettyMSFast.ts new file mode 100644 index 0000000..b274ef4 --- /dev/null +++ b/src/download/transfer-visualize/utils/prettyMSFast.ts @@ -0,0 +1,47 @@ +const SECOND = 1000; +const MINUTE = 60 * SECOND; +const HOUR = 60 * MINUTE; +const DAY = 24 * HOUR; +const YEAR = 365 * DAY; + +const SECOND_BIGINT = 1000n; +const MINUTE_BIGINT = 60n * SECOND_BIGINT; +const HOUR_BIGINT = 60n * MINUTE_BIGINT; +const DAY_BIGINT = 24n * HOUR_BIGINT; +const YEAR_BIGINT = 365n * DAY_BIGINT; + +function prettyMillisecondsCompactNumber(milliseconds: number): string { + if (!Number.isFinite(milliseconds)) { + throw new TypeError('Expected a finite number or bigint'); + } + + const sign = milliseconds < 0 ? '-' : ''; + let value = milliseconds < 0 ? -milliseconds : milliseconds; + + if (value >= YEAR) return sign + Math.trunc(value / YEAR) + 'y'; + if (value >= DAY) return sign + Math.trunc(value / DAY) + 'd'; + if (value >= HOUR) return sign + Math.trunc(value / HOUR) + 'h'; + if (value >= MINUTE) return sign + Math.trunc(value / MINUTE) + 'm'; + if (value >= SECOND) return sign + Math.trunc(value / SECOND) + 's'; + + const roundedMilliseconds = value >= 1 ? Math.round(value) : Math.ceil(value); + return sign + roundedMilliseconds + 'ms'; +} + +function prettyMillisecondsCompactBigInt(milliseconds: bigint): string { + const sign = milliseconds < 0n ? '-' : ''; + let value = milliseconds < 0n ? -milliseconds : milliseconds; + + if (value >= YEAR_BIGINT) return sign + String(value / YEAR_BIGINT) + 'y'; + if (value >= DAY_BIGINT) return sign + String(value / DAY_BIGINT) + 'd'; + if (value >= HOUR_BIGINT) return sign + String(value / HOUR_BIGINT) + 'h'; + if (value >= MINUTE_BIGINT) return sign + String(value / MINUTE_BIGINT) + 'm'; + if (value >= SECOND_BIGINT) return sign + String(value / SECOND_BIGINT) + 's'; + return sign + String(value) + 'ms'; +} + +export default function prettyMillisecondsCompact(milliseconds: number | bigint): string { + return typeof milliseconds === 'bigint' + ? prettyMillisecondsCompactBigInt(milliseconds) + : prettyMillisecondsCompactNumber(milliseconds); +} From 264f9184f4d7f4784c00e8a62265ff2ac8cd978c Mon Sep 17 00:00:00 2001 From: ido Date: Sun, 15 Mar 2026 13:28:09 +0100 Subject: [PATCH 17/33] fix(download-stream): event cleanup --- .../base-download-engine-fetch-stream.ts | 94 +++++++++++-------- .../download-engine-fetch-stream-fetch.ts | 36 +++---- .../download-engine-fetch-stream-xhr.ts | 16 +++- 3 files changed, 87 insertions(+), 59 deletions(-) diff --git a/src/download/download-engine/streams/download-engine-fetch-stream/base-download-engine-fetch-stream.ts b/src/download/download-engine/streams/download-engine-fetch-stream/base-download-engine-fetch-stream.ts index 6e75a0f..a1f0100 100644 --- a/src/download/download-engine/streams/download-engine-fetch-stream/base-download-engine-fetch-stream.ts +++ b/src/download/download-engine/streams/download-engine-fetch-stream/base-download-engine-fetch-stream.ts @@ -107,6 +107,7 @@ export default abstract class BaseDownloadEngineFetchStream extends EventEmitter public paused?: Promise; public aborted = false; protected _pausedResolve?: () => void; + protected _cleanupClonedStateListeners?: () => void; public errorCount = {value: 0}; public lastFetchTime = 0; @@ -148,11 +149,23 @@ export default abstract class BaseDownloadEngineFetchStream extends EventEmitter protected cloneState(state: FetchSubState, fetchStream: Fetcher): Fetcher { fetchStream.state = state; fetchStream.errorCount = this.errorCount; - fetchStream.on("errorCountIncreased", this.emit.bind(this, "errorCountIncreased")); - - this.on("aborted", fetchStream.emit.bind(fetchStream, "aborted")); - this.on("paused", fetchStream.emit.bind(fetchStream, "paused")); - this.on("resumed", fetchStream.emit.bind(fetchStream, "resumed")); + const forwardErrorCount = this.emit.bind(this, "errorCountIncreased"); + const forwardAborted = fetchStream.emit.bind(fetchStream, "aborted"); + const forwardPaused = fetchStream.emit.bind(fetchStream, "paused"); + const forwardResumed = fetchStream.emit.bind(fetchStream, "resumed"); + + fetchStream.on("errorCountIncreased", forwardErrorCount); + this.on("aborted", forwardAborted); + this.on("paused", forwardPaused); + this.on("resumed", forwardResumed); + + fetchStream._cleanupClonedStateListeners = () => { + fetchStream.off("errorCountIncreased", forwardErrorCount); + this.off("aborted", forwardAborted); + this.off("paused", forwardPaused); + this.off("resumed", forwardResumed); + fetchStream._cleanupClonedStateListeners = undefined; + }; return fetchStream; } @@ -214,45 +227,49 @@ export default abstract class BaseDownloadEngineFetchStream extends EventEmitter let retryResolvers = retryAsyncStatementSimple(this.options.retry); let retryingOn = false; - // eslint-disable-next-line no-constant-condition - while (true) { - try { - this.lastFetchTime = Date.now(); - return await this.fetchWithoutRetryChunks((...args) => { - if (retryingOn) { - retryingOn = false; - this.emit("retryingOff"); + try { + // eslint-disable-next-line no-constant-condition + while (true) { + try { + this.lastFetchTime = Date.now(); + return await this.fetchWithoutRetryChunks((...args) => { + if (retryingOn) { + retryingOn = false; + this.emit("retryingOff"); + } + callback(...args); + }); + } catch (error: any) { + if (error?.name === "AbortError") return; + + this.errorCount.value++; + this.emit("errorCountIncreased", this.errorCount.value, error); + + const needToRecreateURL = this.shouldRecreateURL(error); + if (!needToRecreateURL && error instanceof HttpError && !this.retryOnServerError(error)) { + throw error; } - callback(...args); - }); - } catch (error: any) { - if (error?.name === "AbortError") return; - this.errorCount.value++; - this.emit("errorCountIncreased", this.errorCount.value, error); - - const needToRecreateURL = this.shouldRecreateURL(error); - if (!needToRecreateURL && error instanceof HttpError && !this.retryOnServerError(error)) { - throw error; - } + retryingOn = true; + this.emit("retryingOn", error, this.errorCount.value); + if (error instanceof StatusCodeError && error.retryAfter) { + await sleep(error.retryAfter * 1000); + continue; + } - retryingOn = true; - this.emit("retryingOn", error, this.errorCount.value); - if (error instanceof StatusCodeError && error.retryAfter) { - await sleep(error.retryAfter * 1000); - continue; - } + if (lastStartLocation !== this.state.startChunk) { + lastStartLocation = this.state.startChunk; + retryResolvers = retryAsyncStatementSimple(this.options.retry); + } - if (lastStartLocation !== this.state.startChunk) { - lastStartLocation = this.state.startChunk; - retryResolvers = retryAsyncStatementSimple(this.options.retry); + await Promise.all([ + retryResolvers(error), + needToRecreateURL && this.recreateDownloadURL() + ]); } - - await Promise.all([ - retryResolvers(error), - needToRecreateURL && this.recreateDownloadURL() - ]); } + } finally { + this._cleanupClonedStateListeners?.(); } } @@ -276,6 +293,7 @@ export default abstract class BaseDownloadEngineFetchStream extends EventEmitter protected abstract fetchWithoutRetryChunks(callback: WriteCallback): Promise | void; public close(): void | Promise { + this._cleanupClonedStateListeners?.(); this.emit("aborted"); } diff --git a/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-fetch.ts b/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-fetch.ts index 4da9eb0..dfab9ae 100644 --- a/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-fetch.ts +++ b/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-fetch.ts @@ -42,30 +42,34 @@ export default class DownloadEngineFetchStreamFetch extends BaseDownloadEngineFe let response: Response | null = null; this._activeController = new AbortController(); - this.on("aborted", () => { + const abortPendingRequest = () => { if (!response) { this._activeController?.abort(); } - }); + }; + this.on("aborted", abortPendingRequest); + try { + response = await fetch(this.appendToURL(this.state.activePart.downloadURL), { + headers, + signal: this._activeController.signal + }); - response = await fetch(this.appendToURL(this.state.activePart.downloadURL), { - headers, - signal: this._activeController.signal - }); + if (response.status < 200 || response.status >= 300) { + throw new StatusCodeError(this.state.activePart.downloadURL, response.status, response.statusText, headers); + } - if (response.status < 200 || response.status >= 300) { - throw new StatusCodeError(this.state.activePart.downloadURL, response.status, response.statusText, headers); - } + const contentLength = parseHttpContentRange(response.headers.get("content-range"))?.length ?? parseInt(response.headers.get("content-length")!); + const expectedContentLength = this._endSize - this._startSize; + if (this.state.activePart.acceptRange && contentLength !== expectedContentLength) { + throw new InvalidContentLengthError(expectedContentLength, contentLength); + } - const contentLength = parseHttpContentRange(response.headers.get("content-range"))?.length ?? parseInt(response.headers.get("content-length")!); - const expectedContentLength = this._endSize - this._startSize; - if (this.state.activePart.acceptRange && contentLength !== expectedContentLength) { - throw new InvalidContentLengthError(expectedContentLength, contentLength); + const reader = response.body!.getReader(); + return await this.chunkGenerator(callback, () => reader.read()); + } finally { + this.off("aborted", abortPendingRequest); } - - const reader = response.body!.getReader(); - return await this.chunkGenerator(callback, () => reader.read()); } protected override async fetchDownloadInfoWithoutRetry(url: string): Promise { diff --git a/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-xhr.ts b/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-xhr.ts index 91fd41d..90a46b4 100644 --- a/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-xhr.ts +++ b/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-xhr.ts @@ -122,13 +122,19 @@ export default class DownloadEngineFetchStreamXhr extends BaseDownloadEngineFetc } }; - xhr.send(); - createStreamTimeout(); - - this.on("aborted", () => { + const abortXhr = () => { clearStreamTimeout(); xhr.abort(); - }); + this.off("aborted", abortXhr); + }; + + xhr.onloadend = () => { + this.off("aborted", abortXhr); + }; + + xhr.send(); + createStreamTimeout(); + this.on("aborted", abortXhr); }); } From 6b1a9948bdf43671d31417a89f484eb0920ccc17 Mon Sep 17 00:00:00 2001 From: ido Date: Sun, 15 Mar 2026 13:29:11 +0100 Subject: [PATCH 18/33] fix(progress): dedup engine on progress building --- .../progress-statistics-builder.ts | 55 ++++++++++++++----- .../transfer-cli/transfer-cli.ts | 5 +- 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/src/download/transfer-visualize/progress-statistics-builder.ts b/src/download/transfer-visualize/progress-statistics-builder.ts index 9fabac6..d18a242 100644 --- a/src/download/transfer-visualize/progress-statistics-builder.ts +++ b/src/download/transfer-visualize/progress-statistics-builder.ts @@ -1,11 +1,11 @@ import BaseDownloadEngine from "../download-engine/engine/base-download-engine.js"; -import {EventEmitter} from "eventemitter3"; +import { EventEmitter } from "eventemitter3"; import TransferStatistics from "./transfer-statistics.js"; -import {createFormattedStatus, FormattedStatus} from "./format-transfer-status.js"; +import { createFormattedStatus, FormattedStatus } from "./format-transfer-status.js"; import DownloadEngineFile from "../download-engine/download-file/download-engine-file.js"; -import {DownloadStatus, EMPTY_PROGRESS_STATUS, ProgressStatus} from "../download-engine/download-file/progress-status-file.js"; +import { DownloadStatus, EMPTY_PROGRESS_STATUS, ProgressStatus } from "../download-engine/download-file/progress-status-file.js"; import DownloadEngineMultiDownload from "../download-engine/engine/download-engine-multi-download.js"; -import {DownloadEngineRemote} from "../download-engine/engine/DownloadEngineRemote.js"; +import { DownloadEngineRemote } from "../download-engine/engine/DownloadEngineRemote.js"; export type ProgressStatusWithIndex = FormattedStatus & { index: number, @@ -18,7 +18,7 @@ interface CliProgressBuilderEvents { export type AnyEngine = DownloadEngineFile | BaseDownloadEngine | DownloadEngineMultiDownload | DownloadEngineRemote; export default class ProgressStatisticsBuilder extends EventEmitter { private _engines = new Set(); - private _activeTransfers: { [index: number]: number } = {}; + private _activeTransfers: { [index: number]: number; } = {}; private _totalBytes = 0; private _transferredBytes = 0; private _latestEngine: AnyEngine | null = null; @@ -37,6 +37,8 @@ export default class ProgressStatisticsBuilder extends EventEmitter = {}; + private _commonTransferAction = ""; constructor() { super(); @@ -76,8 +78,26 @@ export default class ProgressStatisticsBuilder extends EventEmitter { + if(addFileName){ + this._allFileNames += this._allFileNames ? ", " + latestStatus.fileName : latestStatus.fileName; + } + } + + if (engine instanceof DownloadEngineMultiDownload) { + addFileNameFunc(); + + for (const subEngine of engine._flatEngines) { + this.add(subEngine, sendProgress, false); + } + return; + } this._engines.add(engine); this._latestEngine = engine; @@ -86,12 +106,16 @@ export default class ProgressStatisticsBuilder extends EventEmitter { + this._commonTransferActionMap[latestStatus.transferAction]--; + this._calcCommonTransferAction(); + delete this._activeTransfers[index]; this._transferredBytes += engine.downloadSize; }); @@ -119,6 +146,10 @@ export default class ProgressStatisticsBuilder extends EventEmitter a[1] >= b[1] ? a : b)[0]; + } + /** * @internal */ @@ -129,7 +160,6 @@ export default class ProgressStatisticsBuilder extends EventEmitter) { this.options = { ...DEFAULT_TRANSFER_CLI_OPTIONS, ...options }; this._multiProgressBar = new this.options.createProgressBar.multiProgressBar(this.options); + this._debounceWait = this._multiProgressBar.updateIntervalMs || this.options.debounceWait; this.updateStatues = this.updateStatues.bind(this); this._processExit = this._processExit.bind(this); @@ -73,7 +75,8 @@ export default class TransferCli { updateStatues(getLatestProgress: () => [FormattedStatus[], FormattedStatus, number], debounce = true) { this.latestProgressGetter = getLatestProgress; - if(debounce && Date.now() - this._lastUpdateTime < this.options.debounceWait) { + + if(debounce && Date.now() - this._lastUpdateTime < this._debounceWait) { return; } From 9fc3cc8653f8d0427ffbf8dd4b9df60ce1a5084c Mon Sep 17 00:00:00 2001 From: ido Date: Sun, 15 Mar 2026 14:42:42 +0100 Subject: [PATCH 19/33] fix(write-queue): close fd --- .../download-engine-write-stream-nodejs.ts | 5 ++++- .../streams/download-engine-write-stream/utils/WriteQueue.ts | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-nodejs.ts b/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-nodejs.ts index 872ce77..4593846 100644 --- a/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-nodejs.ts +++ b/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-nodejs.ts @@ -153,14 +153,17 @@ export default class DownloadEngineWriteStreamNodejs extends BaseDownloadEngineW return JSON.parse(metadataString); } catch { } } finally { - await this.close(); + await this._closeFd(); } } override async close() { this._writeQueue.close(); await this._writeQueue.drain(); + await this._closeFd(); + } + private async _closeFd(){ if (!this._fd) { return; } diff --git a/src/download/download-engine/streams/download-engine-write-stream/utils/WriteQueue.ts b/src/download/download-engine/streams/download-engine-write-stream/utils/WriteQueue.ts index d473253..09aa2d2 100644 --- a/src/download/download-engine/streams/download-engine-write-stream/utils/WriteQueue.ts +++ b/src/download/download-engine/streams/download-engine-write-stream/utils/WriteQueue.ts @@ -1,4 +1,5 @@ import { FileHandle } from "fs/promises"; +import WriterIsClosedError from "../errors/writer-is-closed-error.js"; const MIN_BUFFER_SIZE = 2 * 1024 * 1024; // 2 MB const MAX_BUFFER_SIZE = 64 * 1024 * 1024; // 64 MB @@ -57,7 +58,9 @@ export default class WriteQueue { * merges with adjacent regions, and flushes when threshold is exceeded. */ addWrite(cursor: number, buffers: Uint8Array[]): void | Promise { - if (this._closed) return; + if (this._closed) { + throw new WriterIsClosedError("Cannot add write to closed WriteQueue"); + } const length = buffers.reduce((sum, buf) => sum + buf.length, 0); From 696c8c7957af4576090cca3afbe45f08dde6af36 Mon Sep 17 00:00:00 2001 From: ido Date: Sun, 15 Mar 2026 21:15:08 +0100 Subject: [PATCH 20/33] fix: format --- .../download-file/download-engine-file.ts | 20 ++--- .../download-file/progress-status-file.ts | 2 +- .../download-engine-fetch-stream-xhr.ts | 8 +- .../download-engine-write-stream-nodejs.ts | 4 +- .../utils/WriteQueue.ts | 4 +- .../format-transfer-status.ts | 8 +- .../progress-statistics-builder.ts | 14 ++-- .../transfer-cli/GlobalCLI.ts | 18 ++-- .../fancy-transfer-cli-progress-bar.ts | 6 +- .../transfer-cli/transfer-cli.ts | 12 +-- .../utils/abortableDebounce.ts | 1 - .../utils/prettyBytesFast.ts | 82 +++++++++---------- .../transfer-visualize/utils/prettyMSFast.ts | 48 +++++------ test/browser.test.ts | 6 +- test/fetchDownloadInfo.test.ts | 4 +- 15 files changed, 118 insertions(+), 119 deletions(-) diff --git a/src/download/download-engine/download-file/download-engine-file.ts b/src/download/download-engine/download-file/download-engine-file.ts index 4313f6f..6701e4e 100644 --- a/src/download/download-engine/download-file/download-engine-file.ts +++ b/src/download/download-engine/download-file/download-engine-file.ts @@ -1,14 +1,14 @@ -import { DownloadFlags, DownloadStatus, ProgressStatus } from "./progress-status-file.js"; -import { ChunkStatus, DownloadFile, SaveProgressInfo } from "../types.js"; +import {DownloadFlags, DownloadStatus, ProgressStatus} from "./progress-status-file.js"; +import {ChunkStatus, DownloadFile, SaveProgressInfo} from "../types.js"; import BaseDownloadEngineFetchStream from "../streams/download-engine-fetch-stream/base-download-engine-fetch-stream.js"; import BaseDownloadEngineWriteStream from "../streams/download-engine-write-stream/base-download-engine-write-stream.js"; import retry from "async-retry"; -import { EventEmitter } from "eventemitter3"; -import switchProgram, { AvailablePrograms } from "./download-programs/switch-program.js"; +import {EventEmitter} from "eventemitter3"; +import switchProgram, {AvailablePrograms} from "./download-programs/switch-program.js"; import BaseDownloadProgram from "./download-programs/base-download-program.js"; -import { pushComment } from "./utils/push-comment.js"; -import { uid } from "uid"; -import { DownloaderProgramManager } from "./downloaderProgramManager.js"; +import {pushComment} from "./utils/push-comment.js"; +import {uid} from "uid"; +import {DownloaderProgramManager} from "./downloaderProgramManager.js"; export type DownloadEngineFileOptions = { chunkSize?: number; @@ -86,12 +86,12 @@ export default class DownloadEngineFile extends EventEmitter this._activeStreamContext[startChunk] ??= { streamBytes: 0, retryingAttempts: 0 }; + const getContext = () => this._activeStreamContext[startChunk] ??= {streamBytes: 0, retryingAttempts: 0}; const fetchState = this.options.fetchStream.withSubState({ chunkSize: this._progress.chunkSize, diff --git a/src/download/download-engine/download-file/progress-status-file.ts b/src/download/download-engine/download-file/progress-status-file.ts index f2b8f9b..0e22e47 100644 --- a/src/download/download-engine/download-file/progress-status-file.ts +++ b/src/download/download-engine/download-file/progress-status-file.ts @@ -48,4 +48,4 @@ export const EMPTY_PROGRESS_STATUS: ProgressStatus = { transferredBytes: 0, startTime: 0, endTime: 0 -}; \ No newline at end of file +}; diff --git a/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-xhr.ts b/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-xhr.ts index 90a46b4..b3d02bf 100644 --- a/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-xhr.ts +++ b/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-xhr.ts @@ -1,6 +1,6 @@ import retry from "async-retry"; import prettyMillisecondsCompact from "../../../transfer-visualize/utils/prettyMSFast.js"; -import { AvailablePrograms } from "../../download-file/download-programs/switch-program.js"; +import {AvailablePrograms} from "../../download-file/download-programs/switch-program.js"; import BaseDownloadEngineFetchStream, { DownloadInfoResponse, FetchSubState, @@ -9,12 +9,12 @@ import BaseDownloadEngineFetchStream, { WriteCallback } from "./base-download-engine-fetch-stream.js"; import EmptyResponseError from "./errors/empty-response-error.js"; -import { EmptyStreamTimeoutError } from "./errors/EmptyStreamTimeoutError.js"; +import {EmptyStreamTimeoutError} from "./errors/EmptyStreamTimeoutError.js"; import InvalidContentLengthError from "./errors/invalid-content-length-error.js"; import StatusCodeError from "./errors/status-code-error.js"; import XhrError from "./errors/xhr-error.js"; -import { parseContentDisposition } from "./utils/content-disposition.js"; -import { parseHttpContentRange } from "./utils/httpRange.js"; +import {parseContentDisposition} from "./utils/content-disposition.js"; +import {parseHttpContentRange} from "./utils/httpRange.js"; export default class DownloadEngineFetchStreamXhr extends BaseDownloadEngineFetchStream { diff --git a/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-nodejs.ts b/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-nodejs.ts index 4593846..1791e15 100644 --- a/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-nodejs.ts +++ b/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-nodejs.ts @@ -1,7 +1,7 @@ import retry from "async-retry"; import fsExtra from "fs-extra"; -import fs, { FileHandle } from "fs/promises"; -import { withLock } from "lifecycle-utils"; +import fs, {FileHandle} from "fs/promises"; +import {withLock} from "lifecycle-utils"; import BaseDownloadEngineWriteStream from "./base-download-engine-write-stream.js"; import WriteQueue from "./utils/WriteQueue.js"; diff --git a/src/download/download-engine/streams/download-engine-write-stream/utils/WriteQueue.ts b/src/download/download-engine/streams/download-engine-write-stream/utils/WriteQueue.ts index 09aa2d2..b6426e5 100644 --- a/src/download/download-engine/streams/download-engine-write-stream/utils/WriteQueue.ts +++ b/src/download/download-engine/streams/download-engine-write-stream/utils/WriteQueue.ts @@ -1,4 +1,4 @@ -import { FileHandle } from "fs/promises"; +import {FileHandle} from "fs/promises"; import WriterIsClosedError from "../errors/writer-is-closed-error.js"; const MIN_BUFFER_SIZE = 2 * 1024 * 1024; // 2 MB @@ -66,7 +66,7 @@ export default class WriteQueue { const merged = this._tryMerge(cursor, buffers, length); if (!merged) { - this._regions.push({ cursor, buffers, length }); + this._regions.push({cursor, buffers, length}); } this._totalBuffered += length; diff --git a/src/download/transfer-visualize/format-transfer-status.ts b/src/download/transfer-visualize/format-transfer-status.ts index c298859..4e4227b 100644 --- a/src/download/transfer-visualize/format-transfer-status.ts +++ b/src/download/transfer-visualize/format-transfer-status.ts @@ -1,6 +1,6 @@ -import { DownloadStatus, ProgressStatus } from "../download-engine/download-file/progress-status-file.js"; -import { TransferProgressInfo } from "./transfer-statistics.js"; -import prettyBytes, { PrettyBytesOptions, formatTrunc } from "./utils/prettyBytesFast.js"; +import {DownloadStatus, ProgressStatus} from "../download-engine/download-file/progress-status-file.js"; +import {TransferProgressInfo} from "./transfer-statistics.js"; +import prettyBytes, {PrettyBytesOptions, formatTrunc} from "./utils/prettyBytesFast.js"; import prettyMillisecondsCompact from "./utils/prettyMSFast.js"; const DEFAULT_LOCALIZATION: Intl.LocalesArgument = "en-US"; @@ -28,7 +28,7 @@ const NUMBER_FORMAT_OPTIONS: Intl.NumberFormatOptions = { const PERCENTAGE_FRACTION_DIGITS = 4; -const PRETTY_BYTES_OPTIONS: PrettyBytesOptions = {...NUMBER_FORMAT_OPTIONS, space: false }; +const PRETTY_BYTES_OPTIONS: PrettyBytesOptions = {...NUMBER_FORMAT_OPTIONS, space: false}; const DEFAULT_CLI_INFO_STATUS: CliInfoStatus = { speed: 0, diff --git a/src/download/transfer-visualize/progress-statistics-builder.ts b/src/download/transfer-visualize/progress-statistics-builder.ts index d18a242..ad1c720 100644 --- a/src/download/transfer-visualize/progress-statistics-builder.ts +++ b/src/download/transfer-visualize/progress-statistics-builder.ts @@ -1,11 +1,11 @@ import BaseDownloadEngine from "../download-engine/engine/base-download-engine.js"; -import { EventEmitter } from "eventemitter3"; +import {EventEmitter} from "eventemitter3"; import TransferStatistics from "./transfer-statistics.js"; -import { createFormattedStatus, FormattedStatus } from "./format-transfer-status.js"; +import {createFormattedStatus, FormattedStatus} from "./format-transfer-status.js"; import DownloadEngineFile from "../download-engine/download-file/download-engine-file.js"; -import { DownloadStatus, EMPTY_PROGRESS_STATUS, ProgressStatus } from "../download-engine/download-file/progress-status-file.js"; +import {DownloadStatus, EMPTY_PROGRESS_STATUS, ProgressStatus} from "../download-engine/download-file/progress-status-file.js"; import DownloadEngineMultiDownload from "../download-engine/engine/download-engine-multi-download.js"; -import { DownloadEngineRemote } from "../download-engine/engine/DownloadEngineRemote.js"; +import {DownloadEngineRemote} from "../download-engine/engine/DownloadEngineRemote.js"; export type ProgressStatusWithIndex = FormattedStatus & { index: number, @@ -85,10 +85,10 @@ export default class ProgressStatisticsBuilder extends EventEmitter { - if(addFileName){ + if (addFileName){ this._allFileNames += this._allFileNames ? ", " + latestStatus.fileName : latestStatus.fileName; } - } + }; if (engine instanceof DownloadEngineMultiDownload) { addFileNameFunc(); @@ -147,7 +147,7 @@ export default class ProgressStatisticsBuilder extends EventEmitter a[1] >= b[1] ? a : b)[0]; + this._commonTransferAction = Object.entries(this._commonTransferActionMap).reduce((a, b) => (a[1] >= b[1] ? a : b))[0]; } /** diff --git a/src/download/transfer-visualize/transfer-cli/GlobalCLI.ts b/src/download/transfer-visualize/transfer-cli/GlobalCLI.ts index abe4e7e..b0ac183 100644 --- a/src/download/transfer-visualize/transfer-cli/GlobalCLI.ts +++ b/src/download/transfer-visualize/transfer-cli/GlobalCLI.ts @@ -1,13 +1,13 @@ -import DownloadEngineMultiDownload, { DownloadEngineMultiAllowedEngines } from "../../download-engine/engine/download-engine-multi-download.js"; -import TransferCli, { TransferCliOptions } from "./transfer-cli.js"; -import { BaseMultiProgressBar } from "./multiProgressBars/BaseMultiProgressBar.js"; -import switchCliProgressStyle, { AvailableCLIProgressStyle } from "./progress-bars/switch-cli-progress-style.js"; -import { CliFormattedStatus } from "./progress-bars/base-transfer-cli-progress-bar.js"; +import DownloadEngineMultiDownload, {DownloadEngineMultiAllowedEngines} from "../../download-engine/engine/download-engine-multi-download.js"; +import TransferCli, {TransferCliOptions} from "./transfer-cli.js"; +import {BaseMultiProgressBar} from "./multiProgressBars/BaseMultiProgressBar.js"; +import switchCliProgressStyle, {AvailableCLIProgressStyle} from "./progress-bars/switch-cli-progress-style.js"; +import {CliFormattedStatus} from "./progress-bars/base-transfer-cli-progress-bar.js"; import cliSpinners from "cli-spinners"; -import { DownloadStatus } from "../../download-engine/download-file/progress-status-file.js"; +import {DownloadStatus} from "../../download-engine/download-file/progress-status-file.js"; import BaseDownloadEngine from "../../download-engine/engine/base-download-engine.js"; -import { DownloadEngineRemote } from "../../download-engine/engine/DownloadEngineRemote.js"; -import { FormattedStatus } from "../format-transfer-status.js"; +import {DownloadEngineRemote} from "../../download-engine/engine/DownloadEngineRemote.js"; +import {FormattedStatus} from "../format-transfer-status.js"; type AllowedDownloadEngine = DownloadEngineMultiDownload | BaseDownloadEngine | DownloadEngineRemote; @@ -189,7 +189,7 @@ class GlobalCLI { } private static _createOptions(options: CliProgressDownloadEngineOptions) { - const cliOptions: Partial = { ...options }; + const cliOptions: Partial = {...options}; cliOptions.createProgressBar ??= typeof options.cliStyle === "function" ? { createStatusLine: options.cliStyle, diff --git a/src/download/transfer-visualize/transfer-cli/progress-bars/fancy-transfer-cli-progress-bar.ts b/src/download/transfer-visualize/transfer-cli/progress-bars/fancy-transfer-cli-progress-bar.ts index c88a475..d26921d 100644 --- a/src/download/transfer-visualize/transfer-cli/progress-bars/fancy-transfer-cli-progress-bar.ts +++ b/src/download/transfer-visualize/transfer-cli/progress-bars/fancy-transfer-cli-progress-bar.ts @@ -1,10 +1,10 @@ import chalk from "chalk"; import sliceAnsi from "slice-ansi"; import stripAnsi from "strip-ansi"; -import { DownloadStatus } from "../../../download-engine/download-file/progress-status-file.js"; -import { DataLine, renderDataLine } from "../../utils/data-line.js"; +import {DownloadStatus} from "../../../download-engine/download-file/progress-status-file.js"; +import {DataLine, renderDataLine} from "../../utils/data-line.js"; import prettyMillisecondsCompact from "../../utils/prettyMSFast.js"; -import { STATUS_ICONS } from "../../utils/progressBarIcons.js"; +import {STATUS_ICONS} from "../../utils/progressBarIcons.js"; import BaseTransferCliProgressBar from "./base-transfer-cli-progress-bar.js"; /** diff --git a/src/download/transfer-visualize/transfer-cli/transfer-cli.ts b/src/download/transfer-visualize/transfer-cli/transfer-cli.ts index 4a6123e..cb14406 100644 --- a/src/download/transfer-visualize/transfer-cli/transfer-cli.ts +++ b/src/download/transfer-visualize/transfer-cli/transfer-cli.ts @@ -1,9 +1,9 @@ import UpdateManager from "stdout-update"; -import { TransferCliProgressBar } from "./progress-bars/base-transfer-cli-progress-bar.js"; +import {TransferCliProgressBar} from "./progress-bars/base-transfer-cli-progress-bar.js"; import cliSpinners from "cli-spinners"; -import { FormattedStatus } from "../format-transfer-status.js"; +import {FormattedStatus} from "../format-transfer-status.js"; import switchCliProgressStyle from "./progress-bars/switch-cli-progress-style.js"; -import { BaseMultiProgressBar } from "./multiProgressBars/BaseMultiProgressBar.js"; +import {BaseMultiProgressBar} from "./multiProgressBars/BaseMultiProgressBar.js"; export type TransferCliOptions = { name?: string, @@ -34,10 +34,10 @@ export default class TransferCli { private _lastProgressLong = ""; private _lastUpdateTime = 0; private _shouldExitOnSIGINT = false; - private _debounceWait: number + private _debounceWait: number; public constructor(options: Partial) { - this.options = { ...DEFAULT_TRANSFER_CLI_OPTIONS, ...options }; + this.options = {...DEFAULT_TRANSFER_CLI_OPTIONS, ...options}; this._multiProgressBar = new this.options.createProgressBar.multiProgressBar(this.options); this._debounceWait = this._multiProgressBar.updateIntervalMs || this.options.debounceWait; @@ -76,7 +76,7 @@ export default class TransferCli { updateStatues(getLatestProgress: () => [FormattedStatus[], FormattedStatus, number], debounce = true) { this.latestProgressGetter = getLatestProgress; - if(debounce && Date.now() - this._lastUpdateTime < this._debounceWait) { + if (debounce && Date.now() - this._lastUpdateTime < this._debounceWait) { return; } diff --git a/src/download/transfer-visualize/utils/abortableDebounce.ts b/src/download/transfer-visualize/utils/abortableDebounce.ts index 4e0d209..22d4a60 100644 --- a/src/download/transfer-visualize/utils/abortableDebounce.ts +++ b/src/download/transfer-visualize/utils/abortableDebounce.ts @@ -2,7 +2,6 @@ * Creates a debounced function that can be aborted using an AbortSignal. * The function will execute after a specified wait time, but can also execute immediately * if the maximum wait time is reached since the last call. - * * @param func - The function to debounce. * @param options - Options for the debounce behavior. * @returns A debounced version of the provided function. diff --git a/src/download/transfer-visualize/utils/prettyBytesFast.ts b/src/download/transfer-visualize/utils/prettyBytesFast.ts index 38e671b..6a7d261 100644 --- a/src/download/transfer-visualize/utils/prettyBytesFast.ts +++ b/src/download/transfer-visualize/utils/prettyBytesFast.ts @@ -10,13 +10,13 @@ export interface PrettyBytesOptions { fixedWidth?: number; } -const BYTE_UNITS = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; -const BIBYTE_UNITS = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; -const BIT_UNITS = ['b', 'kbit', 'Mbit', 'Gbit', 'Tbit', 'Pbit', 'Ebit', 'Zbit', 'Ybit']; -const BIBIT_UNITS = ['b', 'kibit', 'Mibit', 'Gibit', 'Tibit', 'Pibit', 'Eibit', 'Zibit', 'Yibit']; +const BYTE_UNITS = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; +const BIBYTE_UNITS = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"]; +const BIT_UNITS = ["b", "kbit", "Mbit", "Gbit", "Tbit", "Pbit", "Ebit", "Zbit", "Ybit"]; +const BIBIT_UNITS = ["b", "kibit", "Mibit", "Gibit", "Tibit", "Pibit", "Eibit", "Zibit", "Yibit"]; const DECIMAL_DIVISORS = [1, 1e3, 1e6, 1e9, 1e12, 1e15, 1e18, 1e21, 1e24]; -const BINARY_DIVISORS = Array.from({ length: 9 }, (_, i) => 1024 ** i); +const BINARY_DIVISORS = Array.from({length: 9}, (_, i) => 1024 ** i); const LOG10_1024 = Math.log10(1024); const SIG3_GE100 = new Array(901); @@ -30,15 +30,15 @@ for (let i = 0; i <= 900; i++) { const hi10 = (v / 10) | 0; const lo10 = v - hi10 * 10; - SIG3_GE10[i] = lo10 === 0 ? String(hi10) : hi10 + '.' + lo10; + SIG3_GE10[i] = lo10 === 0 ? String(hi10) : hi10 + "." + lo10; const hi1 = (v / 100) | 0; const rem1 = v - hi1 * 100; const d1 = (rem1 / 10) | 0; const d2 = rem1 - d1 * 10; SIG3_GE1[i] = d2 === 0 - ? (d1 === 0 ? String(hi1) : hi1 + '.' + d1) - : hi1 + '.' + d1 + '' + d2; + ? (d1 === 0 ? String(hi1) : hi1 + "." + d1) + : hi1 + "." + d1 + "" + d2; } function sig3(n: number): string { @@ -80,26 +80,26 @@ export function formatTrunc(n: number, minFrac: number, maxFrac: number): string if (digits === 0) return String(intPart); let fracStr = String(fracInt); - while (fracStr.length < digits) fracStr = '0' + fracStr; - return intPart + '.' + fracStr; + while (fracStr.length < digits) fracStr = "0" + fracStr; + return intPart + "." + fracStr; } function toLocaleStr( num: number, locale: string | string[] | boolean, - opts: Intl.NumberFormatOptions | undefined, + opts: Intl.NumberFormatOptions | undefined ): string { - if (typeof locale === 'string' || Array.isArray(locale)) { + if (typeof locale === "string" || Array.isArray(locale)) { return num.toLocaleString(locale as string | string[], opts); } return num.toLocaleString(undefined, opts); } function applyFixedWidth(result: string, fixedWidth: number): string { - if (typeof fixedWidth !== 'number' || !Number.isSafeInteger(fixedWidth) || fixedWidth < 0) { + if (typeof fixedWidth !== "number" || !Number.isSafeInteger(fixedWidth) || fixedWidth < 0) { throw new TypeError(`Expected fixedWidth to be a non-negative integer, got ${typeof fixedWidth}: ${fixedWidth}`); } - return fixedWidth > 0 && result.length < fixedWidth ? result.padStart(fixedWidth, ' ') : result; + return fixedWidth > 0 && result.length < fixedWidth ? result.padStart(fixedWidth, " ") : result; } function decimalExp(n: number): number { @@ -121,7 +121,7 @@ function binaryExp(n: number): number { function bigintLog10(n: bigint): number { const s = n.toString(10); - return s.length + Math.log10(Number('0.' + s.slice(0, 15))); + return s.length + Math.log10(Number("0." + s.slice(0, 15))); } function bigintDivide(n: bigint, divisor: number): number { @@ -130,7 +130,7 @@ function bigintDivide(n: bigint, divisor: number): number { } export default function prettyBytes(number: number | bigint, options?: PrettyBytesOptions): string { - if (options === undefined && typeof number === 'number') { + if (options === undefined && typeof number === "number") { if (!Number.isFinite(number)) { throw new TypeError(`Expected a finite number, got ${typeof number}: ${number}`); } @@ -139,34 +139,34 @@ export default function prettyBytes(number: number | bigint, options?: PrettyByt let s: string; if (number < 1) { - s = number + ' B'; + s = number + " B"; } else if (number < 1e3) { - s = sig3(number) + ' B'; + s = sig3(number) + " B"; } else if (number < 1e6) { - s = sig3(number / 1e3) + ' kB'; + s = sig3(number / 1e3) + " kB"; } else if (number < 1e9) { - s = sig3(number / 1e6) + ' MB'; + s = sig3(number / 1e6) + " MB"; } else if (number < 1e12) { - s = sig3(number / 1e9) + ' GB'; + s = sig3(number / 1e9) + " GB"; } else if (number < 1e15) { - s = sig3(number / 1e12) + ' TB'; + s = sig3(number / 1e12) + " TB"; } else if (number < 1e18) { - s = sig3(number / 1e15) + ' PB'; + s = sig3(number / 1e15) + " PB"; } else if (number < 1e21) { - s = sig3(number / 1e18) + ' EB'; + s = sig3(number / 1e18) + " EB"; } else if (number < 1e24) { - s = sig3(number / 1e21) + ' ZB'; + s = sig3(number / 1e21) + " ZB"; } else { - s = sig3(number / 1e24) + ' YB'; + s = sig3(number / 1e24) + " YB"; } - return neg ? '-' + s : s; + return neg ? "-" + s : s; } return prettyBytesFull(number, options); } function prettyBytesFull(number: number | bigint, options: PrettyBytesOptions | undefined): string { - if (typeof number !== 'bigint' && !Number.isFinite(number)) { + if (typeof number !== "bigint" && !Number.isFinite(number)) { throw new TypeError(`Expected a finite number, got ${typeof number}: ${number}`); } @@ -182,18 +182,18 @@ function prettyBytesFull(number: number | bigint, options: PrettyBytesOptions | const units = bits ? (binary ? BIBIT_UNITS : BIT_UNITS) : (binary ? BIBYTE_UNITS : BYTE_UNITS); - const separator = space ? (options?.nonBreakingSpace ? '\u00A0' : ' ') : ''; + const separator = space ? (options?.nonBreakingSpace ? "\u00A0" : " ") : ""; - const isZero = typeof number === 'number' ? number === 0 : number === 0n; + const isZero = typeof number === "number" ? number === 0 : number === 0n; if (signed && isZero) { const result = ` 0${separator}${units[0]}`; return fixedWidth !== undefined ? applyFixedWidth(result, fixedWidth) : result; } const isNegative = number < 0; - const prefix = isNegative ? '-' : (signed ? '+' : ''); + const prefix = isNegative ? "-" : (signed ? "+" : ""); if (isNegative) { - number = typeof number === 'bigint' ? -number : -(number as number); + number = typeof number === "bigint" ? -number : -(number as number); } const hasFracOpts = minimumFractionDigits !== undefined || maximumFractionDigits !== undefined; @@ -201,15 +201,15 @@ function prettyBytesFull(number: number | bigint, options: PrettyBytesOptions | let result: string; - const ltOne = typeof number === 'bigint' ? number < 1n : (number as number) < 1; + const ltOne = typeof number === "bigint" ? number < 1n : (number as number) < 1; if (ltOne) { const n = Number(number); let ns: string; if (useLocale) { const localeOpts = hasFracOpts ? { - ...(minimumFractionDigits !== undefined && { minimumFractionDigits }), - ...(maximumFractionDigits !== undefined && { maximumFractionDigits }), - roundingMode: 'trunc', + ...(minimumFractionDigits !== undefined && {minimumFractionDigits}), + ...(maximumFractionDigits !== undefined && {maximumFractionDigits}), + roundingMode: "trunc" } as Intl.NumberFormatOptions : undefined; ns = toLocaleStr(n, locale, localeOpts); } else if (hasFracOpts) { @@ -222,7 +222,7 @@ function prettyBytesFull(number: number | bigint, options: PrettyBytesOptions | let exp: number; let divided: number; - if (typeof number === 'bigint') { + if (typeof number === "bigint") { const l = bigintLog10(number); exp = Math.min(Math.floor(binary ? l / LOG10_1024 : l / 3), units.length - 1); divided = bigintDivide(number, (binary ? 1024 : 1000) ** exp); @@ -234,9 +234,9 @@ function prettyBytesFull(number: number | bigint, options: PrettyBytesOptions | let ns: string; if (useLocale) { const localeOpts = hasFracOpts ? { - ...(minimumFractionDigits !== undefined && { minimumFractionDigits }), - ...(maximumFractionDigits !== undefined && { maximumFractionDigits }), - roundingMode: 'trunc', + ...(minimumFractionDigits !== undefined && {minimumFractionDigits}), + ...(maximumFractionDigits !== undefined && {maximumFractionDigits}), + roundingMode: "trunc" } as Intl.NumberFormatOptions : undefined; const val = hasFracOpts ? divided : Number(binary ? sig3Binary(divided) : sig3(divided)); ns = toLocaleStr(val, locale, localeOpts); @@ -250,4 +250,4 @@ function prettyBytesFull(number: number | bigint, options: PrettyBytesOptions | } return fixedWidth !== undefined ? applyFixedWidth(result, fixedWidth) : result; -} \ No newline at end of file +} diff --git a/src/download/transfer-visualize/utils/prettyMSFast.ts b/src/download/transfer-visualize/utils/prettyMSFast.ts index b274ef4..221e498 100644 --- a/src/download/transfer-visualize/utils/prettyMSFast.ts +++ b/src/download/transfer-visualize/utils/prettyMSFast.ts @@ -11,37 +11,37 @@ const DAY_BIGINT = 24n * HOUR_BIGINT; const YEAR_BIGINT = 365n * DAY_BIGINT; function prettyMillisecondsCompactNumber(milliseconds: number): string { - if (!Number.isFinite(milliseconds)) { - throw new TypeError('Expected a finite number or bigint'); - } + if (!Number.isFinite(milliseconds)) { + throw new TypeError("Expected a finite number or bigint"); + } - const sign = milliseconds < 0 ? '-' : ''; - let value = milliseconds < 0 ? -milliseconds : milliseconds; + const sign = milliseconds < 0 ? "-" : ""; + const value = milliseconds < 0 ? -milliseconds : milliseconds; - if (value >= YEAR) return sign + Math.trunc(value / YEAR) + 'y'; - if (value >= DAY) return sign + Math.trunc(value / DAY) + 'd'; - if (value >= HOUR) return sign + Math.trunc(value / HOUR) + 'h'; - if (value >= MINUTE) return sign + Math.trunc(value / MINUTE) + 'm'; - if (value >= SECOND) return sign + Math.trunc(value / SECOND) + 's'; + if (value >= YEAR) return sign + Math.trunc(value / YEAR) + "y"; + if (value >= DAY) return sign + Math.trunc(value / DAY) + "d"; + if (value >= HOUR) return sign + Math.trunc(value / HOUR) + "h"; + if (value >= MINUTE) return sign + Math.trunc(value / MINUTE) + "m"; + if (value >= SECOND) return sign + Math.trunc(value / SECOND) + "s"; - const roundedMilliseconds = value >= 1 ? Math.round(value) : Math.ceil(value); - return sign + roundedMilliseconds + 'ms'; + const roundedMilliseconds = value >= 1 ? Math.round(value) : Math.ceil(value); + return sign + roundedMilliseconds + "ms"; } function prettyMillisecondsCompactBigInt(milliseconds: bigint): string { - const sign = milliseconds < 0n ? '-' : ''; - let value = milliseconds < 0n ? -milliseconds : milliseconds; - - if (value >= YEAR_BIGINT) return sign + String(value / YEAR_BIGINT) + 'y'; - if (value >= DAY_BIGINT) return sign + String(value / DAY_BIGINT) + 'd'; - if (value >= HOUR_BIGINT) return sign + String(value / HOUR_BIGINT) + 'h'; - if (value >= MINUTE_BIGINT) return sign + String(value / MINUTE_BIGINT) + 'm'; - if (value >= SECOND_BIGINT) return sign + String(value / SECOND_BIGINT) + 's'; - return sign + String(value) + 'ms'; + const sign = milliseconds < 0n ? "-" : ""; + const value = milliseconds < 0n ? -milliseconds : milliseconds; + + if (value >= YEAR_BIGINT) return sign + String(value / YEAR_BIGINT) + "y"; + if (value >= DAY_BIGINT) return sign + String(value / DAY_BIGINT) + "d"; + if (value >= HOUR_BIGINT) return sign + String(value / HOUR_BIGINT) + "h"; + if (value >= MINUTE_BIGINT) return sign + String(value / MINUTE_BIGINT) + "m"; + if (value >= SECOND_BIGINT) return sign + String(value / SECOND_BIGINT) + "s"; + return sign + String(value) + "ms"; } export default function prettyMillisecondsCompact(milliseconds: number | bigint): string { - return typeof milliseconds === 'bigint' - ? prettyMillisecondsCompactBigInt(milliseconds) - : prettyMillisecondsCompactNumber(milliseconds); + return typeof milliseconds === "bigint" + ? prettyMillisecondsCompactBigInt(milliseconds) + : prettyMillisecondsCompactNumber(milliseconds); } diff --git a/test/browser.test.ts b/test/browser.test.ts index 157f483..c51829b 100644 --- a/test/browser.test.ts +++ b/test/browser.test.ts @@ -19,7 +19,7 @@ describe("Browser Fetch API", () => { await downloader.download(); const hash = hashBuffer(downloader.writeStream.result); context.expect(hash) - .toMatchInlineSnapshot(`"0e1a20347e130a168a5c555826915a1302e3fae467b85db6aae53c243c2b0a26"`); + .toMatchInlineSnapshot("\"0e1a20347e130a168a5c555826915a1302e3fae467b85db6aae53c243c2b0a26\""); }); test.concurrent("Download file browser", {repeats: 4, concurrent: true}, async (context) => { @@ -55,7 +55,7 @@ describe("Browser Fetch API", () => { context.expect(lastWrite) .toBe(downloader.file.totalSize); context.expect(hashBuffer(bigBuffer)) - .toMatchInlineSnapshot(`"0e1a20347e130a168a5c555826915a1302e3fae467b85db6aae53c243c2b0a26"`); + .toMatchInlineSnapshot("\"0e1a20347e130a168a5c555826915a1302e3fae467b85db6aae53c243c2b0a26\""); }); }); @@ -66,7 +66,7 @@ describe("Browser Fetch memory", () => { const buffer = Buffer.from(await fs.readFile(response)); const hash = hashBuffer(buffer); context.expect(hash) - .toMatchInlineSnapshot(`"0e1a20347e130a168a5c555826915a1302e3fae467b85db6aae53c243c2b0a26"`); + .toMatchInlineSnapshot("\"0e1a20347e130a168a5c555826915a1302e3fae467b85db6aae53c243c2b0a26\""); }); test.skip("Download file browser - memory (xhr)", async (context) => { diff --git a/test/fetchDownloadInfo.test.ts b/test/fetchDownloadInfo.test.ts index 0cde6ec..4f5058e 100644 --- a/test/fetchDownloadInfo.test.ts +++ b/test/fetchDownloadInfo.test.ts @@ -45,7 +45,7 @@ describe("Fetch download info", () => { return; } res.setHeader("Content-Type", "application/octet-stream"); - res.setHeader("Content-Disposition", `attachment; filename="file.gguf"`); + res.setHeader("Content-Disposition", "attachment; filename=\"file.gguf\""); res.setHeader("Accept-Ranges", "bytes"); res.setHeader("Content-Length", response.headers.get("Content-Length") || "0"); Readable.fromWeb(response.body as any) @@ -85,6 +85,6 @@ describe("Fetch download info", () => { await downloader.download(); const fileSize = (await fsPromise.stat(downloader.finalFileAbsolutePath)).size; context.expect(fileSize) - .toMatchInlineSnapshot(`599171040`); + .toMatchInlineSnapshot("599171040"); }); }); From 2b648e73658aa46ff9b7fb3b22aee6847031d4e8 Mon Sep 17 00:00:00 2001 From: ido Date: Sun, 15 Mar 2026 21:26:44 +0100 Subject: [PATCH 21/33] fix: remove unused var --- src/download/transfer-visualize/format-transfer-status.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/download/transfer-visualize/format-transfer-status.ts b/src/download/transfer-visualize/format-transfer-status.ts index 4e4227b..5c41c90 100644 --- a/src/download/transfer-visualize/format-transfer-status.ts +++ b/src/download/transfer-visualize/format-transfer-status.ts @@ -3,8 +3,6 @@ import {TransferProgressInfo} from "./transfer-statistics.js"; import prettyBytes, {PrettyBytesOptions, formatTrunc} from "./utils/prettyBytesFast.js"; import prettyMillisecondsCompact from "./utils/prettyMSFast.js"; -const DEFAULT_LOCALIZATION: Intl.LocalesArgument = "en-US"; - export type CliInfoStatus = TransferProgressInfo & { fileName?: string, comment?: string From 18ef713d0ef5a8ed32536f93f8adfc46507ee999 Mon Sep 17 00:00:00 2001 From: ido Date: Sun, 15 Mar 2026 21:36:08 +0100 Subject: [PATCH 22/33] fix: remove unused file --- .../utils/abortableDebounce.ts | 49 ------------------- 1 file changed, 49 deletions(-) delete mode 100644 src/download/transfer-visualize/utils/abortableDebounce.ts diff --git a/src/download/transfer-visualize/utils/abortableDebounce.ts b/src/download/transfer-visualize/utils/abortableDebounce.ts deleted file mode 100644 index 22d4a60..0000000 --- a/src/download/transfer-visualize/utils/abortableDebounce.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Creates a debounced function that can be aborted using an AbortSignal. - * The function will execute after a specified wait time, but can also execute immediately - * if the maximum wait time is reached since the last call. - * @param func - The function to debounce. - * @param options - Options for the debounce behavior. - * @returns A debounced version of the provided function. - */ -type AbortableDebounceOptions = { - maxWait?: number; // Maximum wait time in milliseconds - wait?: number; // Wait time in milliseconds - signal?: AbortSignal; // Abort signal to cancel the debounce -}; - -export function abortableDebounce void>(func: T, {wait = 0, maxWait = wait, signal}: AbortableDebounceOptions): (...args: Parameters) => void { - let timeoutId: NodeJS.Timeout | null = null; - let lastCallTime = 0; - - signal?.addEventListener("abort", () => { - if (timeoutId) { - clearTimeout(timeoutId); - timeoutId = null; - } - }); - - return (...args: Parameters) => { - const now = Date.now(); - - if (timeoutId) { - clearTimeout(timeoutId); - } - - if (signal?.aborted) { - return; // If the signal is aborted, do nothing - } - - const timeSinceLastCall = now - lastCallTime; - - if (timeSinceLastCall >= maxWait) { - func(...args); - lastCallTime = now; - } else { - timeoutId = setTimeout(() => { - func(...args); - lastCallTime = Date.now(); - }, Math.max(wait - timeSinceLastCall, 0)); - } - }; -} From 31c4a7e89af0b5625ef78a627174f076695d7365 Mon Sep 17 00:00:00 2001 From: ido Date: Sun, 15 Mar 2026 21:46:01 +0100 Subject: [PATCH 23/33] fix(multi-engine): close before throw --- .../download-engine/engine/download-engine-multi-download.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/download/download-engine/engine/download-engine-multi-download.ts b/src/download/download-engine/engine/download-engine-multi-download.ts index 0de48d2..052e0d5 100644 --- a/src/download/download-engine/engine/download-engine-multi-download.ts +++ b/src/download/download-engine/engine/download-engine-multi-download.ts @@ -238,10 +238,9 @@ export default class DownloadEngineMultiDownload Date: Sun, 15 Mar 2026 22:05:05 +0100 Subject: [PATCH 24/33] fix(cli): file indexing --- src/cli/cli.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/cli.ts b/src/cli/cli.ts index c2cb7fd..8cee933 100644 --- a/src/cli/cli.ts +++ b/src/cli/cli.ts @@ -44,7 +44,7 @@ pullCommand directory = path.dirname(saveLocation); const basename = path.basename(saveLocation); - const fileIndex = file.length > 1 ? ((index + 1) + "-") : ""; + const fileIndex = files.length > 1 ? ((index + 1) + "-") : ""; fileName = fileIndex + basename; } } From 1143ae5ae1829b97a5de061fa6480dd909c88e7e Mon Sep 17 00:00:00 2001 From: ido Date: Sun, 15 Mar 2026 22:23:26 +0100 Subject: [PATCH 25/33] fix(download-size): get range request to fetch size --- .../download-engine-fetch-stream-fetch.ts | 11 ++++++----- .../download-engine-fetch-stream-xhr.ts | 4 +++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-fetch.ts b/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-fetch.ts index dfab9ae..5d97bf1 100644 --- a/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-fetch.ts +++ b/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-fetch.ts @@ -27,7 +27,7 @@ export default class DownloadEngineFetchStreamFetch extends BaseDownloadEngineFe } protected override async fetchWithoutRetryChunks(callback: WriteCallback) { - const headers: { [key: string]: any } = { + const headers: { [key: string]: any; } = { accept: "*/*", ...this.options.headers }; @@ -105,13 +105,14 @@ export default class DownloadEngineFetchStreamFetch extends BaseDownloadEngineFe const fileName = parseContentDisposition(response.headers.get("content-disposition")); let length = parseInt(response.headers.get("content-length")!) || 0; - const contentEncoding = response.headers.get("content-encoding"); + const someLengthInfo = length; + const contentEncoding = response.headers.get("content-encoding"); if (contentEncoding && contentEncoding !== "identity") { length = 0; // If content is encoded, we cannot determine the length reliably } - if (acceptRange && length === 0 && browserCheck() && MIN_LENGTH_FOR_MORE_INFO_REQUEST < length) { + if (length === 0 && (acceptRange || browserCheck() && (method === "GET" || MIN_LENGTH_FOR_MORE_INFO_REQUEST < someLengthInfo))) { length = await this.fetchDownloadInfoWithoutRetryContentRange(url, method === "GET" ? response : undefined); } @@ -194,8 +195,8 @@ export default class DownloadEngineFetchStreamFetch extends BaseDownloadEngineFe } - protected static convertHeadersToRecord(headers: Headers): { [key: string]: string } { - const headerObj: { [key: string]: string } = {}; + protected static convertHeadersToRecord(headers: Headers): { [key: string]: string; } { + const headerObj: { [key: string]: string; } = {}; headers.forEach((value, key) => { headerObj[key] = value; }); diff --git a/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-xhr.ts b/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-xhr.ts index b3d02bf..4060dd0 100644 --- a/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-xhr.ts +++ b/src/download/download-engine/streams/download-engine-fetch-stream/download-engine-fetch-stream-xhr.ts @@ -215,11 +215,13 @@ export default class DownloadEngineFetchStreamXhr extends BaseDownloadEngineFetc const contentEncoding = xhr.getResponseHeader("content-encoding"); let length = parseInt(xhr.getResponseHeader("content-length")!) || 0; + const someLengthInfo = length; + if (contentEncoding && contentEncoding !== "identity") { length = 0; // If content is encoded, we cannot determine the length reliably } - if (acceptRange && length === 0 && MIN_LENGTH_FOR_MORE_INFO_REQUEST < length) { + if (length === 0 && (acceptRange || method === "GET" || MIN_LENGTH_FOR_MORE_INFO_REQUEST < someLengthInfo)) { length = await this.fetchDownloadInfoWithoutRetryContentRange(url, method === "GET" ? xhr : undefined); } From a94bd9bec20279ad2de7103f6f40186ab074b504 Mon Sep 17 00:00:00 2001 From: ido Date: Sun, 15 Mar 2026 22:24:39 +0100 Subject: [PATCH 26/33] fix(write-queue): default buffer size --- .../streams/download-engine-write-stream/utils/WriteQueue.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/download/download-engine/streams/download-engine-write-stream/utils/WriteQueue.ts b/src/download/download-engine/streams/download-engine-write-stream/utils/WriteQueue.ts index b6426e5..2d3f742 100644 --- a/src/download/download-engine/streams/download-engine-write-stream/utils/WriteQueue.ts +++ b/src/download/download-engine/streams/download-engine-write-stream/utils/WriteQueue.ts @@ -32,7 +32,7 @@ export default class WriteQueue { private _options: WriteQueueOptions; private _regions: PendingRegion[] = []; private _totalBuffered = 0; - private _maxBufferedBytes: number = 0; + private _maxBufferedBytes: number = MIN_BUFFER_SIZE; private _inFlightWrites = new Set>(); private _closed = false; From 26e3a4da503cd15c7d2280286fc848fcb5e70870 Mon Sep 17 00:00:00 2001 From: ido Date: Sun, 15 Mar 2026 22:29:18 +0100 Subject: [PATCH 27/33] fix(write-stream): catch close errors --- .../download-engine-write-stream-nodejs.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-nodejs.ts b/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-nodejs.ts index 1791e15..b57279a 100644 --- a/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-nodejs.ts +++ b/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-nodejs.ts @@ -1,7 +1,7 @@ import retry from "async-retry"; import fsExtra from "fs-extra"; -import fs, {FileHandle} from "fs/promises"; -import {withLock} from "lifecycle-utils"; +import fs, { FileHandle } from "fs/promises"; +import { withLock } from "lifecycle-utils"; import BaseDownloadEngineWriteStream from "./base-download-engine-write-stream.js"; import WriteQueue from "./utils/WriteQueue.js"; @@ -26,11 +26,12 @@ const DEFAULT_OPTIONS = { export default class DownloadEngineWriteStreamNodejs extends BaseDownloadEngineWriteStream { private static _allFd = new Set(); - private static _finalizationRegistry = new FinalizationRegistry(async (fd: FileHandle) => { + private static _finalizationRegistry = new FinalizationRegistry((fd: FileHandle) => { if (fd.fd != null) { - await fd.close(); + fd.close().catch(() => { }).finally(() => { + DownloadEngineWriteStreamNodejs._allFd.delete(fd); + }); } - DownloadEngineWriteStreamNodejs._allFd.delete(fd); }); private _finalToken = {}; @@ -163,7 +164,7 @@ export default class DownloadEngineWriteStreamNodejs extends BaseDownloadEngineW await this._closeFd(); } - private async _closeFd(){ + private async _closeFd() { if (!this._fd) { return; } From df7a24d98bc1b788c3430906bc9c518f6ec39e0f Mon Sep 17 00:00:00 2001 From: ido Date: Mon, 16 Mar 2026 10:07:19 +0100 Subject: [PATCH 28/33] fix(write-queue): prevent flashing the same region twice --- .../utils/WriteQueue.ts | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/download/download-engine/streams/download-engine-write-stream/utils/WriteQueue.ts b/src/download/download-engine/streams/download-engine-write-stream/utils/WriteQueue.ts index 2d3f742..55cf4df 100644 --- a/src/download/download-engine/streams/download-engine-write-stream/utils/WriteQueue.ts +++ b/src/download/download-engine/streams/download-engine-write-stream/utils/WriteQueue.ts @@ -70,7 +70,7 @@ export default class WriteQueue { } this._totalBuffered += length; - if (this._totalBuffered >= this._maxBufferedBytes) { + if (this._inFlightWrites.size === 0 && this._totalBuffered >= this._maxBufferedBytes) { return this._flushNow(); } } @@ -104,22 +104,30 @@ export default class WriteQueue { * Flush all buffered regions to disk as parallel positional writes. * Non-overlapping positional writes via fd.write(buf, 0, len, position) are safe concurrently. */ - private _flushNow(): void | Promise { + private _flushNow(flashMetadata = true, flashAll = false): void | Promise { if (this._regions.length === 0) return; const regionsToFlush = this._regions; this._regions = []; this._totalBuffered = 0; - const flushPromise = this._doFlush(regionsToFlush) + const flushPromise = this._doFlush(regionsToFlush, flashMetadata) .finally(() => this._inFlightWrites.delete(flushPromise)); this._inFlightWrites.add(flushPromise); - return flushPromise; + return flushPromise.then(async () => { + if(this._inFlightWrites.size > 0){ + await this._waitForInFlight(); + } + + if(this._totalBuffered >= this._maxBufferedBytes || flashAll && this._regions.length > 0){ + return this._flushNow(flashMetadata, flashAll); + } + }); } - private async _doFlush(regions: PendingRegion[]): Promise { + private async _doFlush(regions: PendingRegion[], flashMetadata = true): Promise { const fdResult = this._options.getFd(); const fd = fdResult instanceof Promise ? await fdResult : fdResult; @@ -128,19 +136,24 @@ export default class WriteQueue { fd.writev(region.buffers, region.cursor) ); - writes.push(this._options.flushMetadata()); - await Promise.all(writes); + + if(flashMetadata){ + await this._options.flushMetadata(); + } } /** - * Flush all buffered data and wait for all in-flight writes to complete. + * Flush all buffered data and wait for all in-flight writes to complete, after it flushes metadata. * Called by ensureBytesSynced(), close(), ftruncate(). - * Re-throws the first write error encountered since last drain. */ async drain(): Promise { - this._flushNow(); - await this._waitForInFlight(); + if(this._inFlightWrites.size > 0){ + await this._waitForInFlight(); + } + + await this._flushNow(false, true); + await this._options.flushMetadata(); } private async _waitForInFlight(): Promise { From f009e317770634c6de6135aa1e30297031546d98 Mon Sep 17 00:00:00 2001 From: ido Date: Mon, 16 Mar 2026 10:20:18 +0100 Subject: [PATCH 29/33] fix(pretty-bytes-fast): large numbers --- .../utils/prettyBytesFast.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/download/transfer-visualize/utils/prettyBytesFast.ts b/src/download/transfer-visualize/utils/prettyBytesFast.ts index 6a7d261..b98ec09 100644 --- a/src/download/transfer-visualize/utils/prettyBytesFast.ts +++ b/src/download/transfer-visualize/utils/prettyBytesFast.ts @@ -62,26 +62,30 @@ function sig3Binary(n: number): string { // Fast trunc-mode fraction formatter — avoids toLocaleString when no locale is set const TRUNC_POWERS = [1, 10, 100, 1000, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10]; -export function formatTrunc(n: number, minFrac: number, maxFrac: number): string { - const intPart = (n | 0); +function getTruncScale(maxFrac: number): number { + return maxFrac < TRUNC_POWERS.length ? TRUNC_POWERS[maxFrac] : 10 ** maxFrac; +} + +function formatTrunc(n: number, minFrac: number, maxFrac: number): string { + const intPart = Math.trunc(n); const frac = n - intPart; if (maxFrac === 0 || (frac === 0 && minFrac === 0)) return String(intPart); - const scale = TRUNC_POWERS[maxFrac]; - let fracInt = (frac * scale) | 0; + const scale = getTruncScale(maxFrac); + let fracInt = Math.trunc(frac * scale); let digits = maxFrac; while (digits > minFrac && fracInt % 10 === 0) { - fracInt = (fracInt / 10) | 0; + fracInt = Math.trunc(fracInt / 10); digits--; } if (digits === 0) return String(intPart); let fracStr = String(fracInt); - while (fracStr.length < digits) fracStr = "0" + fracStr; - return intPart + "." + fracStr; + while (fracStr.length < digits) fracStr = '0' + fracStr; + return intPart + '.' + fracStr; } function toLocaleStr( From 011a8bcaa6922bc68834a616a41b4811397d6f3a Mon Sep 17 00:00:00 2001 From: ido Date: Mon, 16 Mar 2026 10:22:13 +0100 Subject: [PATCH 30/33] fix(cli): check if path is directory by fs --- src/cli/cli.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/cli/cli.ts b/src/cli/cli.ts index 8cee933..d6da5b1 100644 --- a/src/cli/cli.ts +++ b/src/cli/cli.ts @@ -6,7 +6,7 @@ import {downloadFile, downloadSequence} from "../download/node-download.js"; import {setCommand} from "./commands/set.js"; import findDownloadDir, {findFileName} from "./utils/find-download-dir.js"; import {AvailableCLIProgressStyle} from "../download/transfer-visualize/transfer-cli/progress-bars/switch-cli-progress-style.js"; - +import fs from "fs/promises"; const pullCommand = new Command(); pullCommand @@ -29,7 +29,15 @@ pullCommand process.exit(0); } - const saveLocationIsDirectory = saveLocation && path.extname(saveLocation) === ""; + let saveLocationIsDirectory = false; + if (saveLocation) { + try { + const stat = await fs.lstat(saveLocation); + saveLocationIsDirectory = stat.isDirectory(); + } catch { + saveLocationIsDirectory = saveLocation.endsWith(path.sep); + } + } const fileDownloads = await Promise.all( files.map(async (file, index) => { From b2653727a743f1efb21b32303bac497c068b1c47 Mon Sep 17 00:00:00 2001 From: ido Date: Mon, 16 Mar 2026 10:43:53 +0100 Subject: [PATCH 31/33] fix(pretty-bytes-fast): bigint division --- .../transfer-visualize/utils/prettyBytesFast.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/download/transfer-visualize/utils/prettyBytesFast.ts b/src/download/transfer-visualize/utils/prettyBytesFast.ts index b98ec09..64d4242 100644 --- a/src/download/transfer-visualize/utils/prettyBytesFast.ts +++ b/src/download/transfer-visualize/utils/prettyBytesFast.ts @@ -17,6 +17,9 @@ const BIBIT_UNITS = ["b", "kibit", "Mibit", "Gibit", "Tibit", "Pibit", "Eibit", const DECIMAL_DIVISORS = [1, 1e3, 1e6, 1e9, 1e12, 1e15, 1e18, 1e21, 1e24]; const BINARY_DIVISORS = Array.from({length: 9}, (_, i) => 1024 ** i); +const DECIMAL_DIVISORS_BIGINT = [1n, 1000n, 1000000n, 1000000000n, 1000000000000n, 1000000000000000n, 1000000000000000000n, 1000000000000000000000n, 1000000000000000000000000n]; +const BINARY_DIVISORS_BIGINT = [1n, 1024n, 1048576n, 1073741824n, 1099511627776n, 1125899906842624n, 1152921504606846976n, 1180591620717411303424n, 1208925819614629174706176n]; + const LOG10_1024 = Math.log10(1024); const SIG3_GE100 = new Array(901); @@ -128,9 +131,8 @@ function bigintLog10(n: bigint): number { return s.length + Math.log10(Number("0." + s.slice(0, 15))); } -function bigintDivide(n: bigint, divisor: number): number { - const d = BigInt(divisor); - return Number(n / d) + (Number(n % d) / divisor); +function bigintDivide(n: bigint, divisor: bigint): number { + return Number(n / divisor) + (Number(n % divisor) / Number(divisor)); } export default function prettyBytes(number: number | bigint, options?: PrettyBytesOptions): string { @@ -229,7 +231,7 @@ function prettyBytesFull(number: number | bigint, options: PrettyBytesOptions | if (typeof number === "bigint") { const l = bigintLog10(number); exp = Math.min(Math.floor(binary ? l / LOG10_1024 : l / 3), units.length - 1); - divided = bigintDivide(number, (binary ? 1024 : 1000) ** exp); + divided = bigintDivide(number, binary ? BINARY_DIVISORS_BIGINT[exp] : DECIMAL_DIVISORS_BIGINT[exp]); } else { exp = binary ? binaryExp(number as number) : decimalExp(number as number); divided = (number as number) / (binary ? BINARY_DIVISORS[exp] : DECIMAL_DIVISORS[exp]); From 81bb92e20baeff7a5f6fb42041dae15dab7076d1 Mon Sep 17 00:00:00 2001 From: ido Date: Mon, 16 Mar 2026 10:45:18 +0100 Subject: [PATCH 32/33] fix(pretty-bytes-fast): export formatTrunc --- src/download/transfer-visualize/utils/prettyBytesFast.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/download/transfer-visualize/utils/prettyBytesFast.ts b/src/download/transfer-visualize/utils/prettyBytesFast.ts index 64d4242..1c1b78d 100644 --- a/src/download/transfer-visualize/utils/prettyBytesFast.ts +++ b/src/download/transfer-visualize/utils/prettyBytesFast.ts @@ -69,7 +69,7 @@ function getTruncScale(maxFrac: number): number { return maxFrac < TRUNC_POWERS.length ? TRUNC_POWERS[maxFrac] : 10 ** maxFrac; } -function formatTrunc(n: number, minFrac: number, maxFrac: number): string { +export function formatTrunc(n: number, minFrac: number, maxFrac: number): string { const intPart = Math.trunc(n); const frac = n - intPart; From b9faef4a154c741c1b6a6eecf8bd3e86c3ece7b4 Mon Sep 17 00:00:00 2001 From: ido Date: Mon, 16 Mar 2026 10:45:41 +0100 Subject: [PATCH 33/33] format --- .../download-engine-write-stream-nodejs.ts | 11 ++++++----- .../download-engine-write-stream/utils/WriteQueue.ts | 8 ++++---- .../transfer-visualize/utils/prettyBytesFast.ts | 4 ++-- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-nodejs.ts b/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-nodejs.ts index b57279a..d89817d 100644 --- a/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-nodejs.ts +++ b/src/download/download-engine/streams/download-engine-write-stream/download-engine-write-stream-nodejs.ts @@ -1,7 +1,7 @@ import retry from "async-retry"; import fsExtra from "fs-extra"; -import fs, { FileHandle } from "fs/promises"; -import { withLock } from "lifecycle-utils"; +import fs, {FileHandle} from "fs/promises"; +import {withLock} from "lifecycle-utils"; import BaseDownloadEngineWriteStream from "./base-download-engine-write-stream.js"; import WriteQueue from "./utils/WriteQueue.js"; @@ -28,9 +28,10 @@ export default class DownloadEngineWriteStreamNodejs extends BaseDownloadEngineW private static _allFd = new Set(); private static _finalizationRegistry = new FinalizationRegistry((fd: FileHandle) => { if (fd.fd != null) { - fd.close().catch(() => { }).finally(() => { - DownloadEngineWriteStreamNodejs._allFd.delete(fd); - }); + fd.close().catch(() => { }) + .finally(() => { + DownloadEngineWriteStreamNodejs._allFd.delete(fd); + }); } }); diff --git a/src/download/download-engine/streams/download-engine-write-stream/utils/WriteQueue.ts b/src/download/download-engine/streams/download-engine-write-stream/utils/WriteQueue.ts index 55cf4df..df33b11 100644 --- a/src/download/download-engine/streams/download-engine-write-stream/utils/WriteQueue.ts +++ b/src/download/download-engine/streams/download-engine-write-stream/utils/WriteQueue.ts @@ -117,11 +117,11 @@ export default class WriteQueue { this._inFlightWrites.add(flushPromise); return flushPromise.then(async () => { - if(this._inFlightWrites.size > 0){ + if (this._inFlightWrites.size > 0){ await this._waitForInFlight(); } - if(this._totalBuffered >= this._maxBufferedBytes || flashAll && this._regions.length > 0){ + if (this._totalBuffered >= this._maxBufferedBytes || flashAll && this._regions.length > 0){ return this._flushNow(flashMetadata, flashAll); } }); @@ -138,7 +138,7 @@ export default class WriteQueue { await Promise.all(writes); - if(flashMetadata){ + if (flashMetadata){ await this._options.flushMetadata(); } } @@ -148,7 +148,7 @@ export default class WriteQueue { * Called by ensureBytesSynced(), close(), ftruncate(). */ async drain(): Promise { - if(this._inFlightWrites.size > 0){ + if (this._inFlightWrites.size > 0){ await this._waitForInFlight(); } diff --git a/src/download/transfer-visualize/utils/prettyBytesFast.ts b/src/download/transfer-visualize/utils/prettyBytesFast.ts index 1c1b78d..67046ed 100644 --- a/src/download/transfer-visualize/utils/prettyBytesFast.ts +++ b/src/download/transfer-visualize/utils/prettyBytesFast.ts @@ -87,8 +87,8 @@ export function formatTrunc(n: number, minFrac: number, maxFrac: number): string if (digits === 0) return String(intPart); let fracStr = String(fracInt); - while (fracStr.length < digits) fracStr = '0' + fracStr; - return intPart + '.' + fracStr; + while (fracStr.length < digits) fracStr = "0" + fracStr; + return intPart + "." + fracStr; } function toLocaleStr(