diff --git a/.changeset/large-masks-confess.md b/.changeset/large-masks-confess.md new file mode 100644 index 00000000..29274e90 --- /dev/null +++ b/.changeset/large-masks-confess.md @@ -0,0 +1,5 @@ +--- +'@lottiefiles/dotlottie-web': minor +--- + +feat(node-support): 🎸 Enable playing dotLottie animations in non-browser environments diff --git a/.gitattributes b/.gitattributes index 693b6bf3..361b633a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -6,3 +6,4 @@ *.json linguist-language=JSON-with-Comments *.wasm binary +*.gif binary diff --git a/README.md b/README.md index 42e31831..fa857d25 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,7 @@ Discover how to implement and utilize the dotlottie-web packages with our exampl Available examples: * [dotlottie-web-example](apps/dotlottie-web-example/src/main.ts): A basic typescript example app of how to use `@lottiefiles/dotlottie-web` to render a Lottie or dotLottie animation in the browser. +* [dotlottie-web-node-example](apps/dotlottie-web-node-example/index.ts): This example demonstrates how to use the `@lottiefiles/dotlottie-web` in a Node.js environment. It showcases controlling animation playback, rendering frame by frame, and converting a dotLottie animation into a GIF file. for more information, see the [README](apps/dotlottie-web-node-example/README.md). #### Running Examples diff --git a/apps/dotlottie-web-node-example/.gitignore b/apps/dotlottie-web-node-example/.gitignore new file mode 100644 index 00000000..53752db2 --- /dev/null +++ b/apps/dotlottie-web-node-example/.gitignore @@ -0,0 +1 @@ +output diff --git a/apps/dotlottie-web-node-example/README.md b/apps/dotlottie-web-node-example/README.md new file mode 100644 index 00000000..2636cb7d --- /dev/null +++ b/apps/dotlottie-web-node-example/README.md @@ -0,0 +1,39 @@ +# DotLottie Web Node.js Example + +## Overview + +This example demonstrates how to use the DotLottie Web package in a Node.js environment. It showcases controlling animation playback, rendering frame by frame, and converting a Lottie animation into a GIF file. + +## How to Run + +To run the example with default settings, execute: + +```bash +pnpm start +``` + +The output GIF will be saved to `./output/animation.gif`. + +![Alt Text](./demo.gif) + +To customize settings and explore different options, use the command line arguments as follows: + +```bash +pnpm start --width [width] --height [height] --fps [fps] --repeat [repeat] --quality [quality] --input [input file or URL] --speed [speed] +``` + +### Arguments + +* `--width`: Width of the output GIF (default: 200) +* `--height`: Height of the output GIF (default: 200) +* `--fps`: Frames per second for the output GIF (default: 60) +* `--repeat`: Number of times the GIF should repeat (default: 0 for infinite) +* `--input`: Path or URL to the Lottie animation file (default: example animation) +* `--speed`: Speed multiplier for the animation (default: 1) +* `--quality`: Quality of the output GIF ('high', 'mid', or 'low'; default: 'mid') + +### Example Command + +```bash +pnpm start --width 200 --height 200 --fps 30 --repeat 0 --quality high --input https://lottie.host/aaccfd1e-487e-4e9a-9d20-c57299089cfc/iVNpuLw0co.lottie --speed 1.5 +``` diff --git a/apps/dotlottie-web-node-example/demo.gif b/apps/dotlottie-web-node-example/demo.gif new file mode 100644 index 00000000..72e3da0f Binary files /dev/null and b/apps/dotlottie-web-node-example/demo.gif differ diff --git a/apps/dotlottie-web-node-example/index.ts b/apps/dotlottie-web-node-example/index.ts new file mode 100644 index 00000000..fa6977d9 --- /dev/null +++ b/apps/dotlottie-web-node-example/index.ts @@ -0,0 +1,131 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +import fs from 'node:fs'; +import path from 'node:path'; + +import type { Config } from '@lottiefiles/dotlottie-web'; +import { DotLottie } from '@lottiefiles/dotlottie-web'; +import { createCanvas } from '@napi-rs/canvas'; +import GIFEncoder from 'gif-encoder'; +import minimist from 'minimist'; + +const wasmBase64 = fs.readFileSync('./node_modules/@lottiefiles/dotlottie-web/dist/renderer.wasm').toString('base64'); +const wasmDataUri = `data:application/octet-stream;base64,${wasmBase64}`; + +// This is only required for testing the local version of the renderer +DotLottie.setWasmUrl(wasmDataUri); + +const rawArgs = minimist(process.argv.slice(2)); + +interface Args { + fps: number; + height: number; + input: string; + quality: 'high' | 'mid' | 'low'; + repeat: number; + speed: number; + width: number; +} + +const qualityMap = { + high: 1, + low: 20, + mid: 10, +}; + +const args: Args = { + width: Number(rawArgs['width']) || 200, + height: Number(rawArgs['height']) || 200, + fps: Number(rawArgs['fps']) || 60, + repeat: Number(rawArgs['repeat']) || 0, + input: rawArgs['input'] || 'https://lottie.host/195549aa-ad39-4c51-80ee-a8899c2264ee/cWdgpn8n7B.lottie', + speed: Number(rawArgs['speed']) || 1, + quality: rawArgs['quality'] || 'mid', +}; + +if (!args.input) { + console.error('No input file provided. Use --input to specify the Lottie animation file.'); + process.exit(1); +} + +const canvas = createCanvas(args.width, args.height); +const ctx = canvas.getContext('2d'); + +const dotLottieConfig: Config = { + speed: args.speed, + canvas: canvas as unknown as HTMLCanvasElement, +}; + +if (args.input.startsWith('http://') || args.input.startsWith('https://')) { + dotLottieConfig.src = args.input; +} else { + const filePath = path.resolve(args.input); + + if (!fs.existsSync(filePath)) { + console.error(`File not found: ${filePath}`); + process.exit(1); + } + + dotLottieConfig.data = fs.readFileSync(filePath, 'utf-8'); +} + +const gif = new GIFEncoder(args.width, args.height); + +const dotLottie = new DotLottie(dotLottieConfig); + +const outputPath = path.resolve('./output'); + +dotLottie.addEventListener('load', () => { + if (!fs.existsSync(outputPath)) { + fs.mkdirSync(outputPath); + } + + const file = fs.createWriteStream(path.resolve(outputPath, 'animation.gif')); + + gif.pipe(file); + + gif.setRepeat(args.repeat); + gif.setFrameRate(args.fps); + gif.setQuality(qualityMap[args.quality]); + + gif.writeHeader(); + + const frameRateAdjustedForSpeed = args.fps / dotLottie.speed; + const delay = 1 / frameRateAdjustedForSpeed; + + console.info( + ` + Recording GIF with the following settings: + - Width: ${args.width} + - Height: ${args.height} + - FPS: ${args.fps} + - Repeat: ${args.repeat} + - Animation Speed: ${args.speed} + - Quality: ${args.quality} + - Delay: ${delay} + - Total Frames: ${dotLottie.totalFrames} + - Duration: ${dotLottie.duration} + - Output Path: ${outputPath} + `, + ); + + for (let timeElapsed = 0; timeElapsed <= dotLottie.duration; timeElapsed += delay) { + const frameNo = (dotLottie.totalFrames - 1) * (timeElapsed / dotLottie.duration); + + dotLottie.setFrame(frameNo); + } +}); + +dotLottie.addEventListener('frame', (event) => { + const frame = ctx.getImageData(0, 0, args.width, args.height).data; + + if (Math.round(event.currentFrame) >= dotLottie.totalFrames - 1) { + console.log('Finished recording GIF'); + gif.finish(); + } else { + gif.addFrame(frame); + gif._read(1); + } +}); diff --git a/apps/dotlottie-web-node-example/package.json b/apps/dotlottie-web-node-example/package.json new file mode 100644 index 00000000..199227fc --- /dev/null +++ b/apps/dotlottie-web-node-example/package.json @@ -0,0 +1,22 @@ +{ + "name": "@lottiefiles/dotlottie-web-node-example", + "version": "0.0.0", + "type": "module", + "license": "MIT", + "scripts": { + "build": "tsc --noEmit", + "start": "tsx index.ts" + }, + "dependencies": { + "@lottiefiles/dotlottie-web": "workspace:*", + "@napi-rs/canvas": "^0.1.44", + "gif-encoder": "^0.7.2", + "minimist": "^1.2.8" + }, + "devDependencies": { + "@types/gif-encoder": "^0.7.4", + "@types/minimist": "^1.2.5", + "@types/node": "^20.10.2", + "tsx": "^4.6.2" + } +} diff --git a/apps/dotlottie-web-node-example/tsconfig.json b/apps/dotlottie-web-node-example/tsconfig.json new file mode 100644 index 00000000..832e098c --- /dev/null +++ b/apps/dotlottie-web-node-example/tsconfig.json @@ -0,0 +1,12 @@ +{ + // Extend from the build config + "extends": "../../tsconfig.build.json", + + // Compiler options + "compilerOptions": { + // Source root directory + "rootDir": ".", + + "resolveJsonModule": true + } +} diff --git a/packages/web/package.json b/packages/web/package.json index 828111bc..e7ac1306 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -48,6 +48,7 @@ "@dotlottie/dotlottie-js": "^0.6.0" }, "devDependencies": { + "@types/node": "^20.10.2", "cross-env": "7.0.3", "tsup": "7.2.0", "typescript": "5.0.4" diff --git a/packages/web/src/animation-frame-manager.ts b/packages/web/src/animation-frame-manager.ts new file mode 100644 index 00000000..ccd84a07 --- /dev/null +++ b/packages/web/src/animation-frame-manager.ts @@ -0,0 +1,64 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +/* eslint-disable max-classes-per-file */ + +import { IS_BROWSER } from './constants'; + +interface AnimationFrameStrategy { + cancelAnimationFrame(id: number): void; + requestAnimationFrame(callback: (time: number) => void): number; +} + +class WebAnimationFrameStrategy implements AnimationFrameStrategy { + public requestAnimationFrame(callback: (time: number) => void): number { + return window.requestAnimationFrame(callback); + } + + public cancelAnimationFrame(id: number): void { + window.cancelAnimationFrame(id); + } +} + +class NodeAnimationFrameStrategy implements AnimationFrameStrategy { + private _lastHandleId: number = 0; + + private _lastImmediate: NodeJS.Immediate | null = null; + + public requestAnimationFrame(callback: (time: number) => void): number { + if (this._lastHandleId >= Number.MAX_SAFE_INTEGER) { + this._lastHandleId = 0; + } + + this._lastHandleId += 1; + + this._lastImmediate = setImmediate(() => { + callback(Date.now()); + }); + + return this._lastHandleId; + } + + public cancelAnimationFrame(_id: number): void { + if (this._lastImmediate) { + clearImmediate(this._lastImmediate); + } + } +} + +export class AnimationFrameManager { + private readonly _strategy: AnimationFrameStrategy; + + public constructor() { + this._strategy = IS_BROWSER ? new WebAnimationFrameStrategy() : new NodeAnimationFrameStrategy(); + } + + public requestAnimationFrame(callback: (time: number) => void): number { + return this._strategy.requestAnimationFrame(callback); + } + + public cancelAnimationFrame(id: number): void { + this._strategy.cancelAnimationFrame(id); + } +} diff --git a/packages/web/src/constants.ts b/packages/web/src/constants.ts new file mode 100644 index 00000000..0216a973 --- /dev/null +++ b/packages/web/src/constants.ts @@ -0,0 +1,6 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +export const IS_BROWSER = typeof window === 'object'; +export const MS_TO_SEC_FACTOR = 1000; diff --git a/packages/web/src/dotlottie.ts b/packages/web/src/dotlottie.ts index 293291c5..459cec2e 100644 --- a/packages/web/src/dotlottie.ts +++ b/packages/web/src/dotlottie.ts @@ -5,15 +5,14 @@ /* eslint-disable promise/prefer-await-to-then */ /* eslint-disable @typescript-eslint/unbound-method */ +import { AnimationFrameManager } from './animation-frame-manager'; +import { IS_BROWSER, MS_TO_SEC_FACTOR } from './constants'; import type { EventListener, EventType } from './event-manager'; import { EventManager } from './event-manager'; import type { Renderer } from './renderer-wasm'; import { WasmLoader } from './renderer-wasm'; import { getAnimationJSONFromDotLottie, loadAnimationJSONFromURL } from './utils'; -const ENVIRONMENT_IS_WEB = typeof window !== 'undefined'; -const MS_TO_SEC_FACTOR = 1000; - export type Mode = 'forward' | 'reverse' | 'bounce' | 'bounce-reverse'; type PlaybackState = 'playing' | 'paused' | 'stopped'; @@ -130,6 +129,8 @@ export class DotLottie { private _renderConfig: RenderConfig = {}; + private readonly _animationFrameManager = new AnimationFrameManager(); + public constructor(config: Config) { this._animationLoop = this._animationLoop.bind(this); @@ -381,7 +382,10 @@ export class DotLottie { } const clampedBuffer = new Uint8ClampedArray(buffer); - const imageData = new ImageData(clampedBuffer, this._canvas.width, this._canvas.height); + + const imageData = this._context.createImageData(width, height); + + imageData.data.set(clampedBuffer); this._context.putImageData(imageData, 0, 0); } @@ -428,8 +432,7 @@ export class DotLottie { } // clamp the current frame within the effective range and round it - this._currentFrame = - Math.round(Math.max(effectiveStartFrame, Math.min(this._currentFrame, effectiveEndFrame)) * 100) / 100; + this._currentFrame = Math.max(effectiveStartFrame, Math.min(this._currentFrame, effectiveEndFrame)); let shouldUpdate = false; @@ -479,7 +482,7 @@ export class DotLottie { if (this.isPlaying && this._update()) { this._render(); - this._animationFrameId = window.requestAnimationFrame(this._animationLoop); + this._animationFrameId = this._animationFrameManager.requestAnimationFrame(this._animationLoop); } } @@ -490,7 +493,7 @@ export class DotLottie { */ private _stopAnimationLoop(): void { if (this._animationFrameId) { - window.cancelAnimationFrame(this._animationFrameId); + this._animationFrameManager.cancelAnimationFrame(this._animationFrameId); this._animationFrameId = null; } } @@ -502,7 +505,7 @@ export class DotLottie { */ private _startAnimationLoop(): void { if (!this._animationFrameId) { - this._animationFrameId = window.requestAnimationFrame(this._animationLoop); + this._animationFrameId = this._animationFrameManager.requestAnimationFrame(this._animationLoop); } } @@ -583,7 +586,7 @@ export class DotLottie { this._eventManager.dispatch({ type: 'play', }); - this._animationFrameId = window.requestAnimationFrame(this._animationLoop); + this._animationFrameId = this._animationFrameManager.requestAnimationFrame(this._animationLoop); } } @@ -864,7 +867,7 @@ export class DotLottie { public setBackgroundColor(color: string): void { this._backgroundColor = color; - if (ENVIRONMENT_IS_WEB) { + if (IS_BROWSER) { // eslint-disable-next-line no-warning-comments // TODO: Change the background color from the renderer instead of the canvas to support non web environments this._canvas.style.backgroundColor = color; @@ -881,7 +884,7 @@ export class DotLottie { * */ public resize(): void { - if (!ENVIRONMENT_IS_WEB) return; + if (!IS_BROWSER) return; const { height, width } = this._canvas.getBoundingClientRect(); diff --git a/packages/web/src/utils.ts b/packages/web/src/utils.ts index 08a63a6d..e5396ed8 100644 --- a/packages/web/src/utils.ts +++ b/packages/web/src/utils.ts @@ -43,18 +43,3 @@ export async function loadAnimationJSONFromURL(animationURL: string): Promise void>( - func: T, - wait: number, -): (...funcArgs: Parameters) => void { - let timeoutId: number | undefined; - - return function debounced(...args: Parameters) { - clearTimeout(timeoutId); - - timeoutId = window.setTimeout(() => { - func(...args); - }, wait); - }; -} diff --git a/packages/web/tsup.config.cjs b/packages/web/tsup.config.cjs index 3da9d724..adebd5ff 100644 --- a/packages/web/tsup.config.cjs +++ b/packages/web/tsup.config.cjs @@ -10,7 +10,7 @@ const { defineConfig } = require('tsup'); module.exports = defineConfig((options) => ({ bundle: true, metafile: false, - splitting: true, + splitting: false, treeshake: true, clean: true, dts: true, @@ -18,8 +18,8 @@ module.exports = defineConfig((options) => ({ sourcemap: true, entry: ['./src/*.ts'], format: ['esm'], - platform: 'browser', - target: ['es2020'], + platform: 'neutral', + target: ['es2020', 'node18'], tsconfig: 'tsconfig.build.json', onSuccess: () => { fs.copyFileSync( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4456b7ae..3fb030d7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -79,12 +79,43 @@ importers: specifier: ^4.4.5 version: 4.5.0(@types/node@20.5.1) + apps/dotlottie-web-node-example: + dependencies: + '@lottiefiles/dotlottie-web': + specifier: workspace:* + version: link:../../packages/web + '@napi-rs/canvas': + specifier: ^0.1.44 + version: 0.1.44 + gif-encoder: + specifier: ^0.7.2 + version: 0.7.2 + minimist: + specifier: ^1.2.8 + version: 1.2.8 + devDependencies: + '@types/gif-encoder': + specifier: ^0.7.4 + version: 0.7.4 + '@types/minimist': + specifier: ^1.2.5 + version: 1.2.5 + '@types/node': + specifier: ^20.10.2 + version: 20.10.2 + tsx: + specifier: ^4.6.2 + version: 4.6.2 + packages/web: dependencies: '@dotlottie/dotlottie-js': specifier: ^0.6.0 version: 0.6.0 devDependencies: + '@types/node': + specifier: ^20.10.2 + version: 20.10.2 cross-env: specifier: 7.0.3 version: 7.0.3 @@ -1502,6 +1533,102 @@ packages: resolution: {integrity: sha512-WrHvO8PDL8wd8T2+zBGKrMwVL5IyzR3ryWUsl0PXgEV0QHup4mTLi0QcATefGI6Gx9Anu7vthPyyyLpY0EpiQg==} dev: true + /@napi-rs/canvas-android-arm64@0.1.44: + resolution: {integrity: sha512-3UDlVf1CnibdUcM0+0xPH4L4/d/tCI895or0y7mr/Xlaa1tDmvcQCvBYl9G54IpXsm+e4T1XkVrGGJD4k1NfSg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: false + optional: true + + /@napi-rs/canvas-darwin-arm64@0.1.44: + resolution: {integrity: sha512-Y1Yx0H45Iicx2b6pcrlICjlwgylLtqi0t5OJgeUXnxLcJ1+aEpmjLr16tddqHkmGUw/nBRAwfPJrf3GaOwWowQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@napi-rs/canvas-darwin-x64@0.1.44: + resolution: {integrity: sha512-gbzeNz13DFH0Ak5ENyQ5ZEuSuCjNDxA/OV9P5f19lywbOVL5Ol+qgKX0BXBcP3O3IXWahruOvmmLUBn9h1MHpA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@napi-rs/canvas-linux-arm-gnueabihf@0.1.44: + resolution: {integrity: sha512-Sad3/eGyzTZiyJFeFrmX1M3aRp0n3qTAXeCm6EeAjCFGk8TWd4cINCGT3IRY4wmCvNnpe6C4fM03K07cU5YYwA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@napi-rs/canvas-linux-arm64-gnu@0.1.44: + resolution: {integrity: sha512-bCrI9naYGPRFHePMGN+wlrWzC+Swi6uc1YzFg4/wOYzHKSte8FXHrGspHOPPr12BCEmgg3yXK8nnLjxGdlAWtg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@napi-rs/canvas-linux-arm64-musl@0.1.44: + resolution: {integrity: sha512-gB/ao9zBQaOJik4arOKJisZaG+v7DuyBW7UdG+0L80msAuJTTH2UgWOnmXfZwPxzxNbFKzOa8r48uVzfTaAHGQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@napi-rs/canvas-linux-x64-gnu@0.1.44: + resolution: {integrity: sha512-pvHy1bJ0DDD4Bsx6yuFnqpIyBW7+2iIK5BpvmL36zXE+7w2MEeaYzLUWTBhrXj8rzHys6MwLmHNlkw65R80YbQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@napi-rs/canvas-linux-x64-musl@0.1.44: + resolution: {integrity: sha512-5QaeYqNZ/u1QI2E/UqvnmuORT6cI1qTtLosPp/y4awaK+/LXQEzotHNv0nan0z4EV/0mXsJswY9JpISRJzx+Kw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@napi-rs/canvas-win32-x64-msvc@0.1.44: + resolution: {integrity: sha512-pbeTGLox+I+sMVl/FFO21Xvp0PhijsuEr9gaynmN2X7FPTg+CCuuBDhfSU5iMAtcCCYFCk8ridZIWy5jkcf72w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@napi-rs/canvas@0.1.44: + resolution: {integrity: sha512-IyhSndjw29LR1WqkUZvTJI4j8Ve1QGbZYtpdQjJjcFvsvJS4/WHzOWV8ZciLPJBhrYvSQf/JbZJy5LHmFV+plg==} + engines: {node: '>= 10'} + optionalDependencies: + '@napi-rs/canvas-android-arm64': 0.1.44 + '@napi-rs/canvas-darwin-arm64': 0.1.44 + '@napi-rs/canvas-darwin-x64': 0.1.44 + '@napi-rs/canvas-linux-arm-gnueabihf': 0.1.44 + '@napi-rs/canvas-linux-arm64-gnu': 0.1.44 + '@napi-rs/canvas-linux-arm64-musl': 0.1.44 + '@napi-rs/canvas-linux-x64-gnu': 0.1.44 + '@napi-rs/canvas-linux-x64-musl': 0.1.44 + '@napi-rs/canvas-win32-x64-msvc': 0.1.44 + dev: false + /@next/eslint-plugin-next@12.1.0: resolution: {integrity: sha512-WFiyvSM2G5cQmh32t/SiQuJ+I2O+FHVlK/RFw5b1565O2kEM/36EXncjt88Pa+X5oSc+1SS+tWxowWJd1lqI+g==} dependencies: @@ -1905,7 +2032,7 @@ packages: /@types/concat-stream@2.0.1: resolution: {integrity: sha512-v5HP9ZsRbzFq5XRo2liUZPKzwbGK5SuGVMWZjE6iJOm/JNdESk3/rkfcPe0lcal0C32PTLVlYUYqGpMGNdDsDg==} dependencies: - '@types/node': 17.0.45 + '@types/node': 20.10.2 dev: true /@types/debug@4.1.10: @@ -1945,7 +2072,13 @@ packages: /@types/fs-extra@9.0.13: resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==} dependencies: - '@types/node': 17.0.45 + '@types/node': 20.10.2 + dev: true + + /@types/gif-encoder@0.7.4: + resolution: {integrity: sha512-tke9j2sFpRpX2C1JLAxZpTMAzVILlWkkhuSGCbxWuyBvXYtA+og7KpxL5ag+4dbDYJxZ2zVmBH0taxwihgz0ZQ==} + dependencies: + '@types/node': 20.10.2 dev: true /@types/hast@2.3.7: @@ -2006,6 +2139,10 @@ packages: resolution: {integrity: sha512-Kfe/D3hxHTusnPNRbycJE1N77WHDsdS4AjUYIzlDzhDrS47NrwuL3YW4VITxwR7KCVpzwgy4Rbj829KSSQmwXQ==} dev: true + /@types/minimist@1.2.5: + resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} + dev: true + /@types/ms@0.7.33: resolution: {integrity: sha512-AuHIyzR5Hea7ij0P9q7vx7xu4z0C28ucwjAZC0ja7JhINyCnOw8/DnvAPQQ9TfOlCtZAmCERKQX9+o1mgQhuOQ==} dev: true @@ -2024,16 +2161,16 @@ packages: undici-types: 5.26.5 dev: true - /@types/node@20.5.1: - resolution: {integrity: sha512-4tT2UrL5LBqDwoed9wZ6N3umC4Yhz3W3FloMmiiG4JwmUJWpie0c7lcnUNd4gtMKuDEO4wRVS8B6Xa0uMRsMKg==} - dev: true - - /@types/node@20.8.9: - resolution: {integrity: sha512-UzykFsT3FhHb1h7yD4CA4YhBHq545JC0YnEz41xkipN88eKQtL6rSgocL5tbAP6Ola9Izm/Aw4Ora8He4x0BHg==} + /@types/node@20.10.2: + resolution: {integrity: sha512-37MXfxkb0vuIlRKHNxwCkb60PNBpR94u4efQuN4JgIAm66zfCDXGSAFCef9XUWFovX2R1ok6Z7MHhtdVXXkkIw==} dependencies: undici-types: 5.26.5 dev: true + /@types/node@20.5.1: + resolution: {integrity: sha512-4tT2UrL5LBqDwoed9wZ6N3umC4Yhz3W3FloMmiiG4JwmUJWpie0c7lcnUNd4gtMKuDEO4wRVS8B6Xa0uMRsMKg==} + dev: true + /@types/normalize-package-data@2.4.3: resolution: {integrity: sha512-ehPtgRgaULsFG8x0NeYJvmyH1hmlfsNLujHe9dQEia/7MAJYdzMSi19JtchUHjmBA6XC/75dK55mzZH+RyieSg==} dev: true @@ -2061,7 +2198,7 @@ packages: /@types/svgo@2.6.4: resolution: {integrity: sha512-l4cmyPEckf8moNYHdJ+4wkHvFxjyW6ulm9l4YGaOxeyBWPhBOT0gvni1InpFPdzx1dKf/2s62qGITwxNWnPQng==} dependencies: - '@types/node': 20.8.9 + '@types/node': 20.10.2 dev: true /@types/text-table@0.2.4: @@ -2087,14 +2224,14 @@ packages: /@types/ws@8.5.8: resolution: {integrity: sha512-flUksGIQCnJd6sZ1l5dqCEG/ksaoAg/eUwiLAGTJQcfgvZJKF++Ta4bJA6A5aPSJmsr+xlseHn4KLgVlNnvPTg==} dependencies: - '@types/node': 20.8.9 + '@types/node': 20.10.2 dev: true /@types/yauzl@2.10.2: resolution: {integrity: sha512-Km7XAtUIduROw7QPgvcft0lIupeG8a8rdKL8RiSyKvlE7dYY31fEn41HVuQsRFDuROA8tA4K2UVL+WdfFmErBA==} requiresBuild: true dependencies: - '@types/node': 20.8.9 + '@types/node': 20.10.2 dev: true optional: true @@ -3330,6 +3467,10 @@ packages: requiresBuild: true dev: true + /core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + dev: false + /cosmiconfig-toml-loader@1.0.0: resolution: {integrity: sha512-H/2gurFWVi7xXvCyvsWRLCMekl4tITJcX0QEsDMpzxtuxDyM59xLatYNg4s/k9AA/HdtCYfj2su8mgA0GSDLDA==} dependencies: @@ -5467,6 +5608,12 @@ packages: get-intrinsic: 1.2.2 dev: true + /get-tsconfig@4.7.2: + resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==} + dependencies: + resolve-pkg-maps: 1.0.0 + dev: true + /get-uri@6.0.2: resolution: {integrity: sha512-5KLucCJobh8vBY1K07EFV4+cPZH3mrV9YeAruUseCQKHB58SGjjT2l9/eA9LD082IiuMjSlFJEcdJ27TXvbZNw==} engines: {node: '>= 14'} @@ -5479,6 +5626,13 @@ packages: - supports-color dev: true + /gif-encoder@0.7.2: + resolution: {integrity: sha512-rEe2DJCb8quqOElV5orqRjyk2KNjz+Hdy+eWYVNWn7s1/33QQ6boIJHkgOto5qU2NrGxI2coPDRqcEaGFZkQ1w==} + engines: {node: '>= 6.0.0'} + dependencies: + readable-stream: 1.1.14 + dev: false + /git-raw-commits@2.0.11: resolution: {integrity: sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==} engines: {node: '>=10'} @@ -6328,6 +6482,10 @@ packages: engines: {node: '>=12'} dev: true + /isarray@0.0.1: + resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==} + dev: false + /isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} dev: true @@ -6365,7 +6523,7 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 20.8.9 + '@types/node': 20.10.2 merge-stream: 2.0.0 supports-color: 8.1.1 dev: true @@ -9020,6 +9178,15 @@ packages: strip-bom: 4.0.0 dev: true + /readable-stream@1.1.14: + resolution: {integrity: sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==} + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 0.0.1 + string_decoder: 0.10.31 + dev: false + /readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} @@ -9687,6 +9854,10 @@ packages: global-dirs: 0.1.1 dev: true + /resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + dev: true + /resolve@1.19.0: resolution: {integrity: sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==} dependencies: @@ -10319,6 +10490,10 @@ packages: es-abstract: 1.22.3 dev: true + /string_decoder@0.10.31: + resolution: {integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==} + dev: false + /string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} dependencies: @@ -10795,6 +10970,17 @@ packages: typescript: 5.0.4 dev: true + /tsx@4.6.2: + resolution: {integrity: sha512-QPpBdJo+ZDtqZgAnq86iY/PD2KYCUPSUGIunHdGwyII99GKH+f3z3FZ8XNFLSGQIA4I365ui8wnQpl8OKLqcsg==} + engines: {node: '>=18.0.0'} + hasBin: true + dependencies: + esbuild: 0.18.20 + get-tsconfig: 4.7.2 + optionalDependencies: + fsevents: 2.3.3 + dev: true + /tty-table@4.2.2: resolution: {integrity: sha512-2gvCArMZLxgvpZ2NvQKdnYWIFLe7I/z5JClMuhrDXunmKgSZcQKcZRjN9XjAFiToMz2pUo1dEIXyrm0AwgV5Tw==} engines: {node: '>=8.0.0'}