diff --git a/.gitignore b/.gitignore index 3c737af..355e7e1 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,6 @@ node_modules /dist-docs .DS_Store /docs/.vitepress/cache +/docs/api packages/mp3-encoder/dist \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index baff025..7a1398e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,6 @@ { - "editor.defaultFormatter": "dbaeumer.vscode-eslint" + "editor.defaultFormatter": "dbaeumer.vscode-eslint", + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + } } diff --git a/README.md b/README.md index 5218bec..53a5c3b 100644 --- a/README.md +++ b/README.md @@ -184,6 +184,7 @@ npm run build # Production build with type definitions npm run check # Type checking npm run lint # ESLint +npm run docs:generate # Generates API docs npm run docs:dev # Start docs development server npm run dev # Start examples development server diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index bcb0330..723737b 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -3,6 +3,8 @@ import footnote from 'markdown-it-footnote'; import tailwindcss from '@tailwindcss/vite'; import llmstxt from 'vitepress-plugin-llms'; import { HeadConfig } from 'vitepress'; +// @ts-expect-error This file gets generated once docs:generate is run +import apiRoutes from '../api/index.json'; const DESCRIPTION = 'A JavaScript library for reading, writing, and converting media files. Directly in the browser,' + ' and faster than anybunny else.'; @@ -32,56 +34,61 @@ export default withMermaid({ // https://vitepress.dev/reference/default-theme-config nav: [ { text: 'Guide', link: '/guide/introduction', activeMatch: '/guide' }, + { text: 'API', link: '/api', activeMatch: '/api' }, { text: 'Examples', link: '/examples', activeMatch: '/examples' }, { text: 'Sponsors', link: '/#sponsors', activeMatch: '/#sponsors' }, { text: 'License', link: 'https://github.com/Vanilagy/mediabunny#license' }, ], - sidebar: [ - { - text: 'Getting started', - items: [ - { text: 'Introduction', link: '/guide/introduction' }, - { text: 'Installation', link: '/guide/installation' }, - { text: 'Quick start', link: '/guide/quick-start' }, - ], - }, - { - text: 'Reading', - items: [ - { text: 'Reading media files', link: '/guide/reading-media-files' }, - { text: 'Media sinks', link: '/guide/media-sinks' }, - { text: 'Input formats', link: '/guide/input-formats' }, - ], - }, - { - text: 'Writing', - items: [ - { text: 'Writing media files', link: '/guide/writing-media-files' }, - { text: 'Media sources', link: '/guide/media-sources' }, - { text: 'Output formats', link: '/guide/output-formats' }, - ], - }, - { - text: 'Conversion', - items: [ - { text: 'Converting media files', link: '/guide/converting-media-files' }, - ], - }, - { - text: 'Miscellaneous', - items: [ - { text: 'Packets & samples', link: '/guide/packets-and-samples' }, - { text: 'Supported formats & codecs', link: '/guide/supported-formats-and-codecs' }, - ], - }, - { - text: 'Extensions', - items: [ - { text: 'mp3-encoder', link: '/guide/extensions/mp3-encoder' }, - ], - }, - ], + sidebar: { + '/guide': [ + { + text: 'Getting started', + items: [ + { text: 'Introduction', link: '/guide/introduction' }, + { text: 'Installation', link: '/guide/installation' }, + { text: 'Quick start', link: '/guide/quick-start' }, + ], + }, + { + text: 'Reading', + items: [ + { text: 'Reading media files', link: '/guide/reading-media-files' }, + { text: 'Media sinks', link: '/guide/media-sinks' }, + { text: 'Input formats', link: '/guide/input-formats' }, + ], + }, + { + text: 'Writing', + items: [ + { text: 'Writing media files', link: '/guide/writing-media-files' }, + { text: 'Media sources', link: '/guide/media-sources' }, + { text: 'Output formats', link: '/guide/output-formats' }, + ], + }, + { + text: 'Conversion', + items: [ + { text: 'Converting media files', link: '/guide/converting-media-files' }, + ], + }, + { + text: 'Miscellaneous', + items: [ + { text: 'Packets & samples', link: '/guide/packets-and-samples' }, + { text: 'Supported formats & codecs', link: '/guide/supported-formats-and-codecs' }, + ], + }, + { + text: 'Extensions', + items: [ + { text: 'mp3-encoder', link: '/guide/extensions/mp3-encoder' }, + ], + }, + ], + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + '/api': apiRoutes, + }, socialLinks: [ { icon: 'github', link: 'https://github.com/Vanilagy/mediabunny' }, diff --git a/docs/api-config.json b/docs/api-config.json new file mode 100644 index 0000000..e160b55 --- /dev/null +++ b/docs/api-config.json @@ -0,0 +1,22 @@ +{ + "heading": "Mediabunny API reference", + "intro": "Here you can find detailed documentation for all classes, functions, constants and types exposed by Mediabunny's public API.", + + "Samples": "Raw, unencoded chunks of media data, such as video frames or sections of audio.", + "Packets": "Chunks of encoded media data.", + "Input files & tracks": "Read input files and their tracks; demuxer API.", + "Input formats": "Container formats that Mediabunny can read.", + "Input sources": "The sources that can provide data to an `Input`.", + "Output files": "Create and write new media files; muxer API.", + "Output formats": "Container formats that Mediabunny can write.", + "Output targets": "The targets where `Output` writes data to.", + "Media sinks": "Methods for extracting media data from input files.", + "Media sources": "Methods for adding media data to output files.", + "Conversion": "A simple API for converting and transforming media files.", + "Codecs": "Codecs understood by Mediabunny.", + "Encoding": "Encoder configuration and encodability checks.", + "Custom coders": "API for adding custom encoders and decoders.", + "Miscellaneous": "Whatever's left.", + + "@mediabunny/mp3-encoder": "Adds MP3 encoder support to Mediabunny." +} diff --git a/docs/index.md b/docs/index.md index 37bb161..74fe63b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -14,6 +14,9 @@ hero: - theme: brand text: Hop in link: /guide/introduction + - theme: alt + text: API + link: /api - theme: alt text: Examples link: /examples diff --git a/package-lock.json b/package-lock.json index 3bec6d7..be2fc0f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mediabunny", - "version": "1.13.0", + "version": "1.13.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mediabunny", - "version": "1.13.0", + "version": "1.13.1", "license": "MPL-2.0", "workspaces": [ "packages/*" @@ -24,6 +24,7 @@ "@tailwindcss/vite": "^4.1.7", "@types/markdown-it-footnote": "^3.0.4", "@types/node": "^22.13.10", + "@vitest/browser": "3.2.4", "esbuild": "^0.25.1", "esbuild-plugin-external-global": "^1.0.1", "eslint": "^9.22.0", @@ -39,7 +40,8 @@ "vitepress": "^1.6.3", "vitepress-plugin-llms": "^1.5.1", "vitepress-plugin-mermaid": "^2.0.17", - "vitest": "3.2.4" + "vitest": "3.2.4", + "webdriverio": "9.19.2" }, "funding": { "type": "individual", @@ -324,6 +326,21 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", @@ -360,6 +377,16 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/runtime": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz", + "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/types": { "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.0.tgz", @@ -1230,6 +1257,109 @@ "node": "20 || >=22" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": 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/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz", + "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": 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/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -1413,6 +1543,79 @@ "node": ">= 8" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@promptbook/utils": { + "version": "0.69.5", + "resolved": "https://registry.npmjs.org/@promptbook/utils/-/utils-0.69.5.tgz", + "integrity": "sha512-xm5Ti/Hp3o4xHrsK9Yy3MS6KbDxYbq485hDsFvxqaNA7equHLPdo8H8faTitTeb14QCDfLW4iwCxdVYu5sn6YQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://buymeacoffee.com/hejny" + }, + { + "type": "github", + "url": "https://github.com/webgptorg/promptbook/blob/main/README.md#%EF%B8%8F-contributing" + } + ], + "license": "CC-BY-4.0", + "dependencies": { + "spacetrim": "0.11.59" + } + }, + "node_modules/@puppeteer/browsers": { + "version": "2.10.8", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.8.tgz", + "integrity": "sha512-f02QYEnBDE0p8cteNoPYHHjbDuwyfbe4cCIVlNi8/MRicIxFW4w4CfgU0LNgWEID6s06P+hRJ1qjpBLMhPRCiQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.4.1", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.5.0", + "semver": "^7.7.2", + "tar-fs": "^3.1.0", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@puppeteer/browsers/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.37.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.37.0.tgz", @@ -2173,6 +2376,47 @@ "vite": "^5.2.0 || ^6" } }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/user-event": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/argparse": { "version": "1.0.38", "resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz", @@ -2180,6 +2424,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/chai": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", @@ -2594,6 +2845,13 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz", + "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -2615,6 +2873,34 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz", + "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.26.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.1.tgz", @@ -2855,6 +3141,42 @@ "vue": "^3.2.25" } }, + "node_modules/@vitest/browser": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-3.2.4.tgz", + "integrity": "sha512-tJxiPrWmzH8a+w9nLKlQMzAKX/7VjFs50MWgcAj7p9XQ7AQ9/35fByFYptgPELyLw+0aixTnC4pUWV+APcZ/kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@testing-library/dom": "^10.4.0", + "@testing-library/user-event": "^14.6.1", + "@vitest/mocker": "3.2.4", + "@vitest/utils": "3.2.4", + "magic-string": "^0.30.17", + "sirv": "^3.0.1", + "tinyrainbow": "^2.0.0", + "ws": "^8.18.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "playwright": "*", + "vitest": "3.2.4", + "webdriverio": "^7.0.0 || ^8.0.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "playwright": { + "optional": true + }, + "safaridriver": { + "optional": true + }, + "webdriverio": { + "optional": true + } + } + }, "node_modules/@vitest/expect": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", @@ -3231,6 +3553,187 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/@wdio/config": { + "version": "9.19.2", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.19.2.tgz", + "integrity": "sha512-OVCzPQxav0QDk5rktQ6LYARZ5ueUuJXIqTXUpS3A9Jt6PF+ZUI5sbO/y+z+qHQXqDq+LkscmFsmkzgnoHzHcfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@wdio/logger": "9.18.0", + "@wdio/types": "9.19.2", + "@wdio/utils": "9.19.2", + "deepmerge-ts": "^7.0.3", + "glob": "^10.2.2", + "import-meta-resolve": "^4.0.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/logger": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.18.0.tgz", + "integrity": "sha512-HdzDrRs+ywAqbXGKqe1i/bLtCv47plz4TvsHFH3j729OooT5VH38ctFn5aLXgECmiAKDkmH/A6kOq2Zh5DIxww==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "safe-regex2": "^5.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/logger/node_modules/ansi-regex": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz", + "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@wdio/logger/node_modules/chalk": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.0.tgz", + "integrity": "sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@wdio/logger/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@wdio/protocols": { + "version": "9.16.2", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.16.2.tgz", + "integrity": "sha512-h3k97/lzmyw5MowqceAuY3HX/wGJojXHkiPXA3WlhGPCaa2h4+GovV2nJtRvknCKsE7UHA1xB5SWeI8MzloBew==", + "dev": true, + "license": "MIT" + }, + "node_modules/@wdio/repl": { + "version": "9.16.2", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-9.16.2.tgz", + "integrity": "sha512-FLTF0VL6+o5BSTCO7yLSXocm3kUnu31zYwzdsz4n9s5YWt83sCtzGZlZpt7TaTzb3jVUfxuHNQDTb8UMkCu0lQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^20.1.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/repl/node_modules/@types/node": { + "version": "20.19.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.11.tgz", + "integrity": "sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@wdio/types": { + "version": "9.19.2", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.19.2.tgz", + "integrity": "sha512-fBI7ljL+YcPXSXUhdk2+zVuz7IYP1aDMTq1eVmMme9GY0y67t0dCNPOt6xkCAEdL5dOcV6D2L1r6Cf/M2ifTvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^20.1.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/types/node_modules/@types/node": { + "version": "20.19.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.11.tgz", + "integrity": "sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@wdio/utils": { + "version": "9.19.2", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.19.2.tgz", + "integrity": "sha512-caimJiTsxDUfXn/gRAzcYTO3RydSl7XzD+QpjfWZYJjzr8a2XfNnj+Vdmr8gG4BSkiVHirW9mFCZeQp2eTD7rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.18.0", + "@wdio/types": "9.19.2", + "decamelize": "^6.0.0", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^6.1.2", + "geckodriver": "^5.0.0", + "get-port": "^7.0.0", + "import-meta-resolve": "^4.0.0", + "locate-app": "^2.2.24", + "mitt": "^3.0.1", + "safaridriver": "^1.0.0", + "split2": "^4.2.0", + "wait-port": "^1.1.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@zip.js/zip.js": { + "version": "2.7.73", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.73.tgz", + "integrity": "sha512-I2UP8/rdQE5hTtVVL08B7P8XuwXiKuuMUPjNuFOVL/9b+8IsExR9S5jz2H58u0rJjU4M1BikLgqEMG8gZJZVBw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "bun": ">=0.7.0", + "deno": ">=1.0.0", + "node": ">=16.5.0" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/acorn": { "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", @@ -3252,6 +3755,16 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "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, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/ajv": { "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", @@ -3361,6 +3874,44 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/archiver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.2", + "async": "^3.2.4", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^6.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^10.0.0", + "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -3371,6 +3922,16 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -3381,6 +3942,33 @@ "node": ">=12" } }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/bail": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", @@ -3398,6 +3986,114 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/bare-events": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.6.1.tgz", + "integrity": "sha512-AuTJkq9XmE6Vk0FJVNq5QxETrSA/vKHarWVBG5l/JbdCL1prJemiyJqUS0jrlXO0MftuPq4m3YVYhoNc5+aE/g==", + "dev": true, + "license": "Apache-2.0", + "optional": true + }, + "node_modules/bare-fs": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.2.2.tgz", + "integrity": "sha512-5vn+bdnlCYMwETIm1FqQXDP6TYPbxr2uJd88ve40kr4oPbiTZJVrTNzqA3/4sfWZeWKuQR/RkboBt7qEEDtfMA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-os": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", + "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", + "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "streamx": "^2.21.0" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/birpc": { "version": "0.2.19", "resolved": "https://registry.npmjs.org/birpc/-/birpc-0.2.19.tgz", @@ -3437,6 +4133,41 @@ "node": ">=8" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/byte-size": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/byte-size/-/byte-size-9.0.1.tgz", @@ -3707,6 +4438,23 @@ "dev": true, "license": "ISC" }, + "node_modules/compress-commons": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3735,6 +4483,13 @@ "url": "https://github.com/sponsors/mesqueeb" } }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, "node_modules/cose-base": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", @@ -3744,6 +4499,33 @@ "layout-base": "^1.0.0" } }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", + "dev": true, + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3774,6 +4556,19 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/css-shorthand-properties": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/css-shorthand-properties/-/css-shorthand-properties-1.1.2.tgz", + "integrity": "sha512-C2AugXIpRGQTxaCW0N7n5jD/p5irUmCrwl03TrnMFBHDbdq44CFWR2zO7rK9xPN4Eo3pUxC4vQzQgbIpzrD1PQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/css-value": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/css-value/-/css-value-0.0.1.tgz", + "integrity": "sha512-FUV3xaJ63buRLgHrLQVlVgQnQdR4yqdLGaDu7g8CQcWjInDfM9plBTPI9FRfpahju1UBSaMckeb2/46ApS/V1Q==", + "dev": true + }, "node_modules/css-what": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", @@ -4301,6 +5096,16 @@ "lodash-es": "^4.17.21" } }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/dayjs": { "version": "1.11.13", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", @@ -4325,6 +5130,19 @@ } } }, + "node_modules/decamelize": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.1.tgz", + "integrity": "sha512-G7Cqgaelq68XHJNGlZ7lrNQyhZGsFqpwtGFexqUv4IQdjKoSYF7ipZ9UuTJZUSQXFj/XaoBLuEVIVqr8EJngEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/decode-named-character-reference": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", @@ -4355,6 +5173,31 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/deepmerge-ts": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", + "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/delaunator": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", @@ -4398,6 +5241,13 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT" + }, "node_modules/dom-serializer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", @@ -4471,6 +5321,110 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/edge-paths": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz", + "integrity": "sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/which": "^2.0.1", + "which": "^2.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/shirshak55" + } + }, + "node_modules/edgedriver": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/edgedriver/-/edgedriver-6.1.2.tgz", + "integrity": "sha512-UvFqd/IR81iPyWMcxXbUNi+xKWR7JjfoHjfuwjqsj9UHQKn80RpQmS0jf+U25IPi+gKVPcpOSKm0XkqgGMq4zQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@wdio/logger": "^9.1.3", + "@zip.js/zip.js": "^2.7.53", + "decamelize": "^6.0.0", + "edge-paths": "^3.0.5", + "fast-xml-parser": "^5.0.8", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "node-fetch": "^3.3.2", + "which": "^5.0.0" + }, + "bin": { + "edgedriver": "bin/edgedriver.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/edgedriver/node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/edgedriver/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/edgedriver/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/edgedriver/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -4485,6 +5439,30 @@ "dev": true, "license": "MIT" }, + "node_modules/encoding-sniffer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/enhanced-resolve": { "version": "5.18.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", @@ -4601,6 +5579,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, "node_modules/eslint": { "version": "9.22.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.22.0.tgz", @@ -4815,6 +5815,26 @@ "node": ">=0.10.0" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/expect-type": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", @@ -4851,12 +5871,40 @@ "node": ">=0.10.0" } }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -4899,6 +5947,25 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", @@ -4923,6 +5990,40 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -5010,6 +6111,23 @@ "tabbable": "^6.2.0" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/format": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", @@ -5019,6 +6137,19 @@ "node": ">=0.4.x" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/fs-extra": { "version": "11.3.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", @@ -5058,6 +6189,85 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/geckodriver": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-5.0.0.tgz", + "integrity": "sha512-vn7TtQ3b9VMJtVXsyWtQQl1fyBVFhQy7UvJF96kPuuJ0or5THH496AD3eUyaDD11+EqCxH9t6V+EP9soZQk4YQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@wdio/logger": "^9.1.3", + "@zip.js/zip.js": "^2.7.53", + "decamelize": "^6.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "node-fetch": "^3.3.2", + "tar-fs": "^3.0.6", + "which": "^5.0.0" + }, + "bin": { + "geckodriver": "bin/geckodriver.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/geckodriver/node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/geckodriver/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/geckodriver/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/geckodriver/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -5068,6 +6278,35 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-port": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.1.0.tgz", + "integrity": "sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-tsconfig": { "version": "4.10.0", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz", @@ -5081,6 +6320,42 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/get-uri": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "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" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -5093,6 +6368,32 @@ "node": ">=10.13.0" } }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", @@ -5113,6 +6414,13 @@ "dev": true, "license": "ISC" }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true, + "license": "MIT" + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -5233,6 +6541,13 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/htmlfy": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/htmlfy/-/htmlfy-0.8.1.tgz", + "integrity": "sha512-xWROBw9+MEGwxpotll0h672KCaLrKKiCYzsyN8ZgL9cQbVumFnyvsk2JqiB9ELAV1GLj1GG/jxZUjV9OZZi/yQ==", + "dev": true, + "license": "MIT" + }, "node_modules/htmlparser2": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", @@ -5261,6 +6576,34 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "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" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "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.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -5273,6 +6616,27 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -5282,6 +6646,13 @@ "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "dev": true, + "license": "MIT" + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -5309,6 +6680,17 @@ "node": ">=8" } }, + "node_modules/import-meta-resolve": { + "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" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -5318,6 +6700,13 @@ "node": ">=0.8.19" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, "node_modules/internmap": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", @@ -5327,6 +6716,16 @@ "node": ">=12" } }, + "node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/is-core-module": { "version": "2.15.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", @@ -5406,6 +6805,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-what": { "version": "4.1.16", "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", @@ -5419,12 +6831,35 @@ "url": "https://github.com/sponsors/mesqueeb" } }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jiti": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", @@ -5447,9 +6882,7 @@ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true, - "license": "MIT", - "optional": true, - "peer": true + "license": "MIT" }, "node_modules/js-yaml": { "version": "4.1.0", @@ -5503,6 +6936,52 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dev": true, + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/juice": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/juice/-/juice-8.1.0.tgz", @@ -5600,6 +7079,52 @@ "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", "dev": true }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -5613,6 +7138,16 @@ "node": ">= 0.8.0" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lightningcss": { "version": "1.30.1", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", @@ -5869,6 +7404,28 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/locate-app": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/locate-app/-/locate-app-2.5.0.tgz", + "integrity": "sha512-xIqbzPMBYArJRmPGUZD9CzV9wOqmVtQnaAn3wrj3s6WYW0bQvPI7x+sPYUGmDTYMHefVK//zc6HEYZ1qnxIK+Q==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://buymeacoffee.com/hejny" + }, + { + "type": "github", + "url": "https://github.com/hejny/locate-app/blob/main/README.md#%EF%B8%8F-contributing" + } + ], + "license": "Apache-2.0", + "dependencies": { + "@promptbook/utils": "0.69.5", + "type-fest": "4.26.0", + "userhome": "1.0.1" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -5896,12 +7453,47 @@ "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", "dev": true }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.zip": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.zip/-/lodash.zip-4.2.0.tgz", + "integrity": "sha512-C7IOaBBK/0gMORRBd8OETNx3kmOkgIWIPvyDpZSCTwUrpYmgZwJkjZeOD8ww4xbOUOs4/attY+pciKvadNfFbg==", + "dev": true, + "license": "MIT" + }, + "node_modules/loglevel": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", + "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" + } + }, + "node_modules/loglevel-plugin-prefix": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/loglevel-plugin-prefix/-/loglevel-plugin-prefix-0.8.4.tgz", + "integrity": "sha512-WpG9CcFAOjz/FtNht+QJeGpvVl/cdR6P0z6OcXSkr8wFJOsV2GRj2j10JLfjuA4aYkcKCNIEqRGCyTife9R8/g==", + "dev": true, + "license": "MIT" + }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -5948,6 +7540,16 @@ "node": ">=10" } }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, "node_modules/magic-string": { "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", @@ -6147,9 +7749,9 @@ } }, "node_modules/mediabunny": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.12.1.tgz", - "integrity": "sha512-Jidi7oARd9/oUVg8tf8bbj5yQHTBAB2vp+iz+txQlqTFwuzqjRrij384pkTezJZRyeE1I1JKE2IY3/GQDMelfA==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.13.0.tgz", + "integrity": "sha512-eAtCVOzceN3VkExQmYrDB+3bM6d98PfB6AhgueojYG6T5SNzc4cRR2gWm0oZ1HjhNN0J4hjtvtEpJ+AAMXF/dQ==", "license": "MPL-2.0", "peer": true, "workspaces": [ @@ -6833,6 +8435,16 @@ "pathe": "^2.0.1" } }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -6864,6 +8476,37 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -6891,6 +8534,16 @@ "dev": true, "optional": true }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -6903,6 +8556,16 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/oniguruma-to-es": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-3.1.1.tgz", @@ -6962,6 +8625,47 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dev": true, + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/package-manager-detector": { "version": "0.2.11", "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.11.tgz", @@ -6971,6 +8675,13 @@ "quansync": "^0.2.7" } }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true, + "license": "(MIT AND Zlib)" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -6999,6 +8710,45 @@ "parse5": "^6.0.1" } }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/parse5-parser-stream/node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/path-data-parser": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", @@ -7029,6 +8779,30 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/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/path-to-regexp": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", @@ -7055,6 +8829,13 @@ "node": ">= 14.16" } }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, "node_modules/perfect-debounce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", @@ -7262,6 +9043,61 @@ "node": ">= 0.8.0" } }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.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, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/property-information": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.0.0.tgz", @@ -7273,6 +9109,54 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -7298,6 +9182,13 @@ } ] }, + "node_modules/query-selector-shadow-dom": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/query-selector-shadow-dom/-/query-selector-shadow-dom-1.0.1.tgz", + "integrity": "sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==", + "dev": true, + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -7334,6 +9225,63 @@ "node": ">=0.10.0" } }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/regex/-/regex-6.0.1.tgz", @@ -7485,6 +9433,33 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/resq": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/resq/-/resq-1.11.0.tgz", + "integrity": "sha512-G10EBz+zAAy3zUd/CDoBbXRL6ia9kOo3xRHrMDsHljI0GDkhYlyjwoCx5+3eCC4swi1uCoZQhskuJkj7Gp57Bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^2.0.1" + } + }, + "node_modules/resq/node_modules/fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==", + "dev": true, + "license": "MIT" + }, + "node_modules/ret": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz", + "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -7503,6 +9478,13 @@ "dev": true, "license": "MIT" }, + "node_modules/rgb2hex": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.2.5.tgz", + "integrity": "sha512-22MOP1Rh7sAo1BZpDG6R5RFYzR2lYEgwq7HEmyW2qcsOqR2lQKmn+O//xV3YG/0rrhMC6KVX2hU+ZXuaw9a5bw==", + "dev": true, + "license": "MIT" + }, "node_modules/robust-predicates": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", @@ -7591,6 +9573,57 @@ "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", "dev": true }, + "node_modules/safaridriver": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safaridriver/-/safaridriver-1.0.0.tgz", + "integrity": "sha512-J92IFbskyo7OYB3Dt4aTdyhag1GlInrfbPCmMteb7aBK7PwlnGz1HI0+oyNN97j7pV9DqUAVoVgkNRMrfY47mQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-regex2": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.0.0.tgz", + "integrity": "sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ret": "~0.5.0" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -7635,6 +9668,42 @@ "node": ">=10" } }, + "node_modules/serialize-error": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-12.0.0.tgz", + "integrity": "sha512-ZYkZLAvKTKQXWuh5XpBw7CdbSzagarX39WyZ2H07CDLC5/KfsRGlIXV8d4+tfqX1M7916mRqR1QfNHSij+c9Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^4.31.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serialize-error/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/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true, + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -7680,6 +9749,34 @@ "dev": true, "license": "ISC" }, + "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, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sirv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.1.tgz", + "integrity": "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/slick": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/slick/-/slick-1.12.2.tgz", @@ -7689,6 +9786,47 @@ "node": "*" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -7719,6 +9857,23 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/spacetrim": { + "version": "0.11.59", + "resolved": "https://registry.npmjs.org/spacetrim/-/spacetrim-0.11.59.tgz", + "integrity": "sha512-lLYsktklSRKprreOm7NXReW8YiX2VBjbgmXYEziOoGf/qsJqAEACaDvoTtUOycwjpaSh+bT8eu0KrJn7UNxiCg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://buymeacoffee.com/hejny" + }, + { + "type": "github", + "url": "https://github.com/hejny/spacetrim/blob/main/README.md#%EF%B8%8F-contributing" + } + ], + "license": "Apache-2.0" + }, "node_modules/speakingurl": { "version": "14.0.1", "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", @@ -7752,6 +9907,16 @@ "node": "^12.20.0 || >=14" } }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -7773,6 +9938,30 @@ "dev": true, "license": "MIT" }, + "node_modules/streamx": { + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.1.tgz", + "integrity": "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-argv": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", @@ -7798,6 +9987,22 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "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", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/stringify-entities": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", @@ -7826,6 +10031,20 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "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" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom-string": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", @@ -7868,6 +10087,19 @@ "dev": true, "license": "MIT" }, + "node_modules/strnum": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", + "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/stylis": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", @@ -7957,6 +10189,33 @@ "node": ">=18" } }, + "node_modules/tar-fs": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.0.tgz", + "integrity": "sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, "node_modules/tar/node_modules/yallist": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", @@ -7967,6 +10226,16 @@ "node": ">=18" } }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -8075,6 +10344,16 @@ "dev": true, "license": "MIT" }, + "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, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -8163,6 +10442,19 @@ "node": ">= 0.8.0" } }, + "node_modules/type-fest": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.26.0.tgz", + "integrity": "sha512-OduNjVJsFbifKb57UqZ2EMP1i4u64Xwow3NYXUtBbD4vIwJdQd4+xl8YDou1dlm4DVrtwT/7Ky8z8WyCULVfxw==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typescript": { "version": "5.8.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", @@ -8206,6 +10498,16 @@ "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", "dev": true }, + "node_modules/undici": { + "version": "6.21.3", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", + "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -8341,6 +10643,30 @@ "punycode": "^2.1.0" } }, + "node_modules/urlpattern-polyfill": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.1.0.tgz", + "integrity": "sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/userhome": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/userhome/-/userhome-1.0.1.tgz", + "integrity": "sha512-5cnLm4gseXjAclKowC4IjByaGsjtAoV6PrOQOljplNB54ReUYJP8HdAFq2muHinSDAh09PPX/uXDPfdxRHvuSA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, "node_modules/uuid": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", @@ -9261,6 +11587,34 @@ } } }, + "node_modules/wait-port": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/wait-port/-/wait-port-1.1.0.tgz", + "integrity": "sha512-3e04qkoN3LxTMLakdqeWth8nih8usyg+sf1Bgdf9wwUkp05iuK1eSY/QpLvscT/+F/gA89+LpUmmgBtesbqI2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "commander": "^9.3.0", + "debug": "^4.3.4" + }, + "bin": { + "wait-port": "bin/wait-port.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/wait-port/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/web-resource-inliner": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/web-resource-inliner/-/web-resource-inliner-6.0.1.tgz", @@ -9317,12 +11671,323 @@ "url": "https://github.com/fb55/htmlparser2?sponsor=1" } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/webdriver": { + "version": "9.19.2", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.19.2.tgz", + "integrity": "sha512-kw6dSwNzimU8/CkGVlM36pqWHZ7BhCwV4/d8fu6rpIYGeQbPwcNc4M90TfJuzYMA7Au3NdrwT/EVQgVLQ9Ju8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^20.1.0", + "@types/ws": "^8.5.3", + "@wdio/config": "9.19.2", + "@wdio/logger": "9.18.0", + "@wdio/protocols": "9.16.2", + "@wdio/types": "9.19.2", + "@wdio/utils": "9.19.2", + "deepmerge-ts": "^7.0.3", + "https-proxy-agent": "^7.0.6", + "undici": "^6.21.3", + "ws": "^8.8.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/webdriver/node_modules/@types/node": { + "version": "20.19.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.11.tgz", + "integrity": "sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/webdriverio": { + "version": "9.19.2", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.19.2.tgz", + "integrity": "sha512-xP/9odQ9tt2pEuMgo0Oobklhu1lObgL1KmejZeyxVStwnrSTbFmn1AAqPq5pfXizUsyv2PR5+id9frrarx/c4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^20.11.30", + "@types/sinonjs__fake-timers": "^8.1.5", + "@wdio/config": "9.19.2", + "@wdio/logger": "9.18.0", + "@wdio/protocols": "9.16.2", + "@wdio/repl": "9.16.2", + "@wdio/types": "9.19.2", + "@wdio/utils": "9.19.2", + "archiver": "^7.0.1", + "aria-query": "^5.3.0", + "cheerio": "^1.0.0-rc.12", + "css-shorthand-properties": "^1.1.1", + "css-value": "^0.0.1", + "grapheme-splitter": "^1.0.4", + "htmlfy": "^0.8.1", + "is-plain-obj": "^4.1.0", + "jszip": "^3.10.1", + "lodash.clonedeep": "^4.5.0", + "lodash.zip": "^4.2.0", + "query-selector-shadow-dom": "^1.0.1", + "resq": "^1.11.0", + "rgb2hex": "0.2.5", + "serialize-error": "^12.0.0", + "urlpattern-polyfill": "^10.0.0", + "webdriver": "9.19.2" + }, + "engines": { + "node": ">=18.20.0" + }, + "peerDependencies": { + "puppeteer-core": ">=22.x || <=24.x" + }, + "peerDependenciesMeta": { + "puppeteer-core": { + "optional": true + } + } + }, + "node_modules/webdriverio/node_modules/@types/node": { + "version": "20.19.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.11.tgz", + "integrity": "sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/webdriverio/node_modules/cheerio": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.2.tgz", + "integrity": "sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.1", + "htmlparser2": "^10.0.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^7.12.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=20.18.1" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/webdriverio/node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/webdriverio/node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/webdriverio/node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/webdriverio/node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/webdriverio/node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/webdriverio/node_modules/htmlparser2": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", + "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.1", + "entities": "^6.0.0" + } + }, + "node_modules/webdriverio/node_modules/htmlparser2/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/webdriverio/node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/webdriverio/node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/webdriverio/node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/webdriverio/node_modules/undici": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.15.0.tgz", + "integrity": "sha512-7oZJCPvvMvTd0OlqWsIxTuItTpJBpU1tcbVl24FMn3xt3+VSunwUasmfPJRE57oNO1KsZ4PgA1xTdAX4hq8NyQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "dev": true }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -9398,6 +12063,54 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/xmldom-sre": { "version": "0.1.31", "resolved": "https://registry.npmjs.org/xmldom-sre/-/xmldom-sre-0.1.31.tgz", @@ -9424,6 +12137,21 @@ "dev": true, "license": "ISC" }, + "node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -9453,6 +12181,27 @@ "node": ">=12" } }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yauzl/node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -9465,6 +12214,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zip-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", @@ -9478,7 +12242,7 @@ }, "packages/mp3-encoder": { "name": "@mediabunny/mp3-encoder", - "version": "1.13.0", + "version": "1.13.1", "license": "MPL-2.0", "devDependencies": { "@types/emscripten": "^1.40.1" diff --git a/package.json b/package.json index 1d6512f..e5ae439 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mediabunny", "author": "Vanilagy", - "version": "1.13.0", + "version": "1.13.1", "description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.", "type": "module", "workspaces": [ @@ -27,12 +27,15 @@ "build": "./build.sh", "watch": "tsx scripts/bundle.ts --watch", "lint": "eslint .", - "test": "vitest --run", + "test-node": "cd test && vitest node --run", + "test-web": "cd test && vitest browser --run --browser", + "test": "npm run test-node && npm run test-web", "check": "tsc -p src --noEmit && tsc -p packages/mp3-encoder/src --noEmit && tsc -p scripts --noEmit && tsc -p tsconfig.vite.json --noEmit && rm tsconfig.vite.tsbuildinfo", "check-docblocks": "tsx scripts/check-docblocks.ts dist/mediabunny.d.ts", "docs:dev": "vitepress dev docs", - "docs:build": "vitepress build docs && npm run examples:build", + "docs:build": "npm run docs:generate && vitepress build docs && npm run examples:build", "docs:preview": "vitepress preview docs", + "docs:generate": "tsx scripts/generate-api-docs.ts src/index.ts packages/mp3-encoder/src/index.ts docs/api-config.json", "dev": "vite", "examples:build": "vite build", "fix-build-import-paths": "tsx scripts/add-import-extensions.ts", @@ -82,7 +85,9 @@ "vitepress": "^1.6.3", "vitepress-plugin-llms": "^1.5.1", "vitepress-plugin-mermaid": "^2.0.17", - "vitest": "3.2.4" + "vitest": "3.2.4", + "@vitest/browser": "3.2.4", + "webdriverio": "9.19.2" }, "keywords": [ "media", diff --git a/packages/mp3-encoder/package.json b/packages/mp3-encoder/package.json index e512b75..70c7ec4 100644 --- a/packages/mp3-encoder/package.json +++ b/packages/mp3-encoder/package.json @@ -1,7 +1,7 @@ { "name": "@mediabunny/mp3-encoder", "author": "Vanilagy", - "version": "1.13.0", + "version": "1.13.1", "description": "MP3 encoder extension for Mediabunny, based on LAME.", "main": "./dist/bundles/mediabunny-mp3-encoder.mjs", "module": "./dist/bundles/mediabunny-mp3-encoder.mjs", diff --git a/packages/mp3-encoder/src/index.ts b/packages/mp3-encoder/src/index.ts index 33e4a38..7319e2d 100644 --- a/packages/mp3-encoder/src/index.ts +++ b/packages/mp3-encoder/src/index.ts @@ -200,6 +200,7 @@ class Mp3Encoder extends CustomAudioEncoder { * } * ``` * + * @group \@mediabunny/mp3-encoder * @public */ export const registerMp3Encoder = () => { diff --git a/scripts/check-docblocks.ts b/scripts/check-docblocks.ts index 493d121..5589a9c 100644 --- a/scripts/check-docblocks.ts +++ b/scripts/check-docblocks.ts @@ -16,6 +16,7 @@ const checkDocblocks = (filePath: string) => { if ( ts.isInterfaceDeclaration(node) || ts.isClassDeclaration(node) + || ts.isConstructorDeclaration(node) || ts.isMethodDeclaration(node) || ts.isGetAccessorDeclaration(node) || ts.isSetAccessorDeclaration(node) @@ -60,7 +61,13 @@ const checkDocblocks = (filePath: string) => { let name = 'anonymous'; const kind = ts.SyntaxKind[node.kind].replace(/Declaration|Statement/g, '').toLowerCase(); - if ('name' in node && node.name) { + if (ts.isConstructorDeclaration(node)) { + // For constructors, use the parent class name + const parent = node.parent; + if (ts.isClassDeclaration(parent) && parent.name) { + name = parent.name.text; + } + } else if ('name' in node && node.name) { if (ts.isIdentifier(node.name)) { name = node.name.text; } else if ('getText' in node.name) { @@ -137,9 +144,22 @@ const checkDocblocks = (filePath: string) => { for (const node of jsDocNodes) { if (ts.isJSDoc(node)) { - const commentText = node.comment ?? ''; - if (typeof commentText !== 'string') { - throw new Error('Can\'t handle this yet!'); + let commentText = ''; + + if (typeof node.comment === 'string') { + commentText = node.comment; + } else if (Array.isArray(node.comment)) { + // Handle JSDoc comment parts (including @link tags) + commentText = node.comment + .map((part) => { + if (typeof part === 'string') { + return part; + } else if (part && typeof part === 'object' && 'text' in part) { + return part.text || ''; + } + return ''; + }) + .join(''); } // Remove all @tags with regex diff --git a/scripts/generate-api-docs.ts b/scripts/generate-api-docs.ts new file mode 100644 index 0000000..27af5a2 --- /dev/null +++ b/scripts/generate-api-docs.ts @@ -0,0 +1,1445 @@ +// This script has been 100% vibe-coded with Claude, meaning I literally haven't looked at any of the code. It's +// probably a mess, but it solves a one-off problem where only the output matters, and the output is indeed good, which +// is the point of a custom script for this: full, precise control. + +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @stylistic/max-len */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @stylistic/brace-style */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ + +import * as ts from 'typescript'; +import * as fs from 'fs'; +import * as path from 'path'; + +const generateDocs = (entryFiles: string[], apiConfigFile: string) => { + const program = ts.createProgram(entryFiles, { + target: ts.ScriptTarget.ES2020, + module: ts.ModuleKind.ESNext, + moduleResolution: ts.ModuleResolutionKind.Node10, + allowJs: false, + declaration: true, + esModuleInterop: true, + skipLibCheck: true, + strict: true, + }); + + const sourceFiles = entryFiles.map((entryFile) => { + const sourceFile = program.getSourceFile(entryFile); + if (!sourceFile) { + throw new Error(`Could not find source file: ${entryFile}`); + } + return sourceFile; + }); + + const typeChecker = program.getTypeChecker(); + const outputDir = path.resolve(process.cwd(), 'docs/api'); + + // Load API config + const apiConfigPath = path.resolve(process.cwd(), apiConfigFile); + if (!fs.existsSync(apiConfigPath)) { + throw new Error(`API config file not found: ${apiConfigPath}`); + } + const apiConfig: Record = JSON.parse(fs.readFileSync(apiConfigPath, 'utf-8')); + + // Extract special fields + const headingText = apiConfig['heading'] || 'API Reference'; + const introText = apiConfig['intro']; + + // Create a copy without the special fields for group processing + const groupConfig = { ...apiConfig }; + delete groupConfig['heading']; + delete groupConfig['intro']; + + // Clear and recreate output directory + if (fs.existsSync(outputDir)) { + fs.rmSync(outputDir, { recursive: true }); + } + fs.mkdirSync(outputDir, { recursive: true }); + + // Collect all exported types for cross-referencing + const exportedTypes = new Set(); + const classHierarchy = new Map(); // Maps parent class to array of subclasses + const classInstances = new Map(); // Maps class name to array of instance variable names + + const collectExportedTypes = (module: ts.Symbol, visited = new Set()): void => { + if (visited.has(module)) return; + visited.add(module); + + const exports = typeChecker.getExportsOfModule(module); + exports.forEach((exportSymbol) => { + const declaration = exportSymbol.valueDeclaration || exportSymbol.declarations?.[0]; + if (!declaration) return; + + // Collect classes, interfaces, types, enums, variables (only if @public) + if (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration) + || ts.isTypeAliasDeclaration(declaration) || ts.isEnumDeclaration(declaration) + || ts.isVariableDeclaration(declaration)) { + const hasPublicTag = ts.getJSDocTags(declaration).some(tag => tag.tagName.text === 'public'); + if (hasPublicTag) { + exportedTypes.add(exportSymbol.getName()); + } + } + + // Follow reexports + else if (exportSymbol.flags & ts.SymbolFlags.Alias) { + const aliasedSymbol = typeChecker.getAliasedSymbol(exportSymbol); + const aliasedDeclaration = aliasedSymbol.valueDeclaration || aliasedSymbol.declarations?.[0]; + if (aliasedDeclaration) { + // Check if the aliased symbol has @public tag + const hasPublicTag = ts.getJSDocTags(aliasedDeclaration).some(tag => tag.tagName.text === 'public'); + if (hasPublicTag) { + exportedTypes.add(exportSymbol.getName()); + } + + // Also recursively collect from the source module + const sourceFile = aliasedDeclaration.getSourceFile(); + const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile); + if (moduleSymbol) { + collectExportedTypes(moduleSymbol, visited); + } + } + } + }); + }; + + // Get all exported symbols recursively + const getAllExportedSymbols = (module: ts.Symbol, visited = new Set()): ts.Symbol[] => { + if (visited.has(module)) return []; + visited.add(module); + + const exports = typeChecker.getExportsOfModule(module); + const symbols: ts.Symbol[] = []; + + exports.forEach((exportSymbol) => { + const declaration = exportSymbol.valueDeclaration || exportSymbol.declarations?.[0]; + if (!declaration) return; + + // If it's a reexport, follow it recursively + if (exportSymbol.flags & ts.SymbolFlags.Alias) { + const aliasedSymbol = typeChecker.getAliasedSymbol(exportSymbol); + const aliasedDeclaration = aliasedSymbol.valueDeclaration || aliasedSymbol.declarations?.[0]; + if (aliasedDeclaration) { + const sourceFile = aliasedDeclaration.getSourceFile(); + const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile); + if (moduleSymbol) { + symbols.push(...getAllExportedSymbols(moduleSymbol, visited)); + } + } + } + // Otherwise, add any symbol with @public tag (we'll filter by type later) + else { + const hasPublicTag = ts.getJSDocTags(declaration).some(tag => tag.tagName.text === 'public'); + if (hasPublicTag) { + symbols.push(exportSymbol); + } + } + }); + + return symbols; + }; + + // Collect all exported types from all source files + const allModuleSymbols: ts.Symbol[] = []; + sourceFiles.forEach((sourceFile) => { + const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile); + if (moduleSymbol) { + allModuleSymbols.push(moduleSymbol); + collectExportedTypes(moduleSymbol); + } + }); + + // Helper to find all potential type references in a type string + const findAllTypeReferences = (typeString: string): string[] => { + // Match PascalCase identifiers that could be type names + const matches = typeString.match(/\b[A-Z][a-zA-Z0-9_]*\b/g) || []; + return [...new Set(matches)]; // Remove duplicates + }; + + // Helper to filter references to only exported types (excluding current type) + const filterToExportedTypes = (references: string[], currentTypeName?: string): string[] => { + return references.filter(ref => exportedTypes.has(ref) && ref !== currentTypeName); + }; + + // Helper to process {@link} tags in JSDoc comments + const processLinkTags = (text: string, currentTypeName?: string): string => { + // Replace {@link TypeName} with [TypeName](./TypeName.md) if TypeName is exported + // or just TypeName if not exported + // If TypeName is the current type, just use code formatting without link + return text.replace(/\{@link\s+([^}]+)\}/g, (_, typeName) => { + const cleanTypeName = typeName.trim(); + if (cleanTypeName === currentTypeName) { + return `\`${cleanTypeName}\``; + } + if (exportedTypes.has(cleanTypeName)) { + return `[\`${cleanTypeName}\`](./${cleanTypeName}.md)`; + } + return `\`${cleanTypeName}\``; + }); + }; + + // Helper to extract linked types from {@link} tags in text + const extractLinkedTypes = (text: string): string[] => { + if (!text) return []; + const linkMatches = text.match(/\{@link\s+([^}]+)\}/g) || []; + return linkMatches.map((match) => { + const typeName = match.replace(/\{@link\s+([^}]+)\}/, '$1').trim(); + return typeName; + }); + }; + + // Helper to format references with proper "and" and period + // Optionally filters out references that were already mentioned in @link tags + const formatReferences = (references: string[], linkedTypes: string[] = []): string => { + if (references.length === 0) return ''; + + // Filter out references that were already linked in the description + const filteredReferences = references.filter(ref => !linkedTypes.includes(ref)); + + if (filteredReferences.length === 0) return ''; + + const refLinks = filteredReferences.map(ref => `[\`${ref}\`](./${ref}.md)`); + const formatter = new Intl.ListFormat('en', { style: 'long', type: 'conjunction' }); + return `\n\nSee ${formatter.format(refLinks)}.`; + }; + + // Helper to format object types with proper indentation + const formatObjectType = (typeText: string): string => { + // Remove JSDoc comments but preserve original structure + let lines = typeText.split('\n'); + const result: string[] = []; + + // First remove all JSDoc comments from the entire text + const cleanText = typeText.replace(/\/\*\*[\s\S]*?\*\//g, ''); + lines = cleanText.split('\n'); + + for (const line of lines) { + // Skip empty lines + if (line.trim() === '') continue; + + result.push(line); + } + + return result.join('\n'); + }; + + // Helper to get better type string representation + const getTypeString = (type: ts.Type): string => { + // Handle array types specially + if (typeChecker.isArrayType(type)) { + const elementType = typeChecker.getTypeArguments(type as ts.TypeReference)[0]; + if (elementType) { + return `${getTypeString(elementType)}[]`; + } + } + + // Check if it's an array-like type by checking the symbol name + const typeString = typeChecker.typeToString(type); + if (typeString === 'Array' && type.symbol && type.symbol.getName() === 'Array') { + // Try to get type arguments from the type reference + if ((type as any).typeArguments && (type as any).typeArguments.length > 0) { + const elementType = (type as any).typeArguments[0]; + return `${getTypeString(elementType)}[]`; + } + // If we can't determine the element type, try looking at the declaration + if (type.symbol.declarations && type.symbol.declarations[0]) { + const declaration = type.symbol.declarations[0]; + if ( + ts.isTypeReferenceNode(declaration) + && declaration.typeArguments + && declaration.typeArguments.length > 0 + ) { + return `${declaration.typeArguments[0]!.getText()}[]`; + } + } + } + + return typeString; + }; + + // Helper to clean up optional parameter types + const cleanOptionalType = (type: string, isOptional: boolean) => { + let cleanedType = type; + if (isOptional) { + // Remove "| undefined" from union types for optional parameters + cleanedType = cleanedType.replace(/\s*\|\s*undefined$/, '').replace(/^undefined\s*\|\s*/, ''); + } + // Convert string literals from double quotes to single quotes + cleanedType = cleanedType.replace(/"([^"]*)"/g, '\'$1\''); + + // Format long union types with line breaks if they exceed 80 characters + // Only apply to top-level unions, not unions nested within intersections, object types, or generic types + if (cleanedType.includes(' | ') && cleanedType.length > 80 && !cleanedType.includes('&') && !cleanedType.includes('{') && !cleanedType.includes('<')) { + const unionMembers = cleanedType.split(' | '); + cleanedType = '\n\t| ' + unionMembers.join('\n\t| '); + } + + return cleanedType; + }; + + // Get all exported symbols from all modules + const allSymbols: ts.Symbol[] = []; + allModuleSymbols.forEach((moduleSymbol) => { + allSymbols.push(...getAllExportedSymbols(moduleSymbol)); + }); + const indexEntries: Array<{ name: string; type: string; group: string; order: number }> = []; + + // Create a map to track the order of symbols based on their appearance in the entry file + const symbolOrderMap = new Map(); + let orderIndex = 0; + + // Walk through all source files to establish order based on declaration/export order + const establishSymbolOrder = (node: ts.Node): void => { + if (ts.isExportDeclaration(node)) { + // Handle export declarations like "export { Foo } from './foo'" + if (node.exportClause && ts.isNamedExports(node.exportClause)) { + node.exportClause.elements.forEach((element) => { + const exportName = (element.propertyName || element.name).getText(); + if (!symbolOrderMap.has(exportName)) { + symbolOrderMap.set(exportName, orderIndex++); + } + }); + } + } else if (ts.isVariableStatement(node)) { + // Handle variable statements like "export const foo = ..." + if (node.modifiers?.some(mod => mod.kind === ts.SyntaxKind.ExportKeyword)) { + node.declarationList.declarations.forEach((declaration) => { + const name = declaration.name.getText(); + if (name && !symbolOrderMap.has(name)) { + symbolOrderMap.set(name, orderIndex++); + } + }); + } + } else if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isFunctionDeclaration(node)) { + // Handle direct declarations + if (node.modifiers?.some(mod => mod.kind === ts.SyntaxKind.ExportKeyword)) { + const name = (node as any).name?.getText(); + if (name && !symbolOrderMap.has(name)) { + symbolOrderMap.set(name, orderIndex++); + } + } + } + + ts.forEachChild(node, establishSymbolOrder); + }; + + // Establish symbol order from all source files + sourceFiles.forEach((sourceFile) => { + establishSymbolOrder(sourceFile); + }); + + // Phase 1: Collect class hierarchy information + allSymbols.forEach((exportSymbol) => { + const declaration = exportSymbol.valueDeclaration || exportSymbol.declarations?.[0]; + if (!declaration) return; + + const hasPublicTag = ts.getJSDocTags(declaration).some(tag => tag.tagName.text === 'public'); + if (!hasPublicTag) return; + + // Collect inheritance information for classes + if (ts.isClassDeclaration(declaration) && declaration.heritageClauses) { + const symbolName = declaration.name?.getText(); + if (!symbolName) return; + + const extendsClauseNode = declaration.heritageClauses.find( + clause => clause.token === ts.SyntaxKind.ExtendsKeyword, + ); + if (extendsClauseNode && extendsClauseNode.types[0]) { + const superClassName = extendsClauseNode.types[0].expression.getText(); + if (!classHierarchy.has(superClassName)) { + classHierarchy.set(superClassName, []); + } + classHierarchy.get(superClassName)!.push(symbolName); + } + } + }); + + // Collect class instances + allSymbols.forEach((exportSymbol) => { + const declaration = exportSymbol.valueDeclaration || exportSymbol.declarations?.[0]; + if (!declaration) return; + + const hasPublicTag = ts.getJSDocTags(declaration).some(tag => tag.tagName.text === 'public'); + if (!hasPublicTag) return; + + if (ts.isVariableDeclaration(declaration)) { + const variableName = declaration.name.getText(); + let className: string | undefined; + // Check explicit type annotation + if (declaration.type && ts.isTypeReferenceNode(declaration.type)) { + className = declaration.type.typeName.getText(); + } + // Check initializer for constructor calls + else if (declaration.initializer && ts.isNewExpression(declaration.initializer)) { + className = declaration.initializer.expression.getText(); + } + if (className && exportedTypes.has(className)) { + if (!classInstances.has(className)) { + classInstances.set(className, []); + } + classInstances.get(className)!.push(variableName); + } + } + }); + + // Phase 2: Generate documentation for each symbol + allSymbols.forEach((exportSymbol) => { + const declaration = exportSymbol.valueDeclaration || exportSymbol.declarations?.[0]; + if (!declaration) return; + + // Check if it's a supported symbol type + const nodeKind = ts.SyntaxKind[declaration.kind]; + const symbolName = (declaration as any).name?.getText() || exportSymbol.getName(); + + // Only process symbols with @public tag + const hasPublicTag = ts.getJSDocTags(declaration).some(tag => tag.tagName.text === 'public'); + if (!hasPublicTag) return; + + // Check for @group tag (handle re-exports by looking at the original declaration) + let targetDeclaration = declaration; + if (exportSymbol.flags & ts.SymbolFlags.Alias) { + const aliasedSymbol = typeChecker.getAliasedSymbol(exportSymbol); + const aliasedDeclaration = aliasedSymbol.valueDeclaration || aliasedSymbol.declarations?.[0]; + if (aliasedDeclaration) { + targetDeclaration = aliasedDeclaration; + } + } + + const groupTag = ts.getJSDocTags(targetDeclaration).find(tag => tag.tagName.text === 'group'); + if (!groupTag || typeof groupTag.comment !== 'string') { + throw new Error(`Symbol '${symbolName}' is missing @group JSDoc tag`); + } + const groupName = groupTag.comment.trim().replace(/\\(.)/g, '$1'); + + // Validate that the group exists in the API config + if (!Object.prototype.hasOwnProperty.call(groupConfig, groupName)) { + throw new Error(`Symbol '${symbolName}' has @group '${groupName}' which is not defined in API config`); + } + + // Check if it's a supported type + if (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration) || ts.isTypeAliasDeclaration(declaration) || ts.isVariableDeclaration(declaration)) { + // Supported types - continue processing + } else { + // Unsupported type - throw error with type info + throw new Error(`Unsupported symbol type: ${nodeKind} for symbol '${symbolName}'`); + } + + if (!declaration.name) return; + + // Handle variable declarations separately + if (ts.isVariableDeclaration(declaration)) { + const variableName = declaration.name.getText(); + + // Get variable description from JSDoc + const jsDocComment = ts.getJSDocCommentsAndTags(declaration)[0]; + let description = ''; + if (jsDocComment && ts.isJSDoc(jsDocComment)) { + const commentText = jsDocComment.comment; + if (typeof commentText === 'string') { + description = processLinkTags(commentText.trim(), variableName); + } + } + + // Check if it's a function type + const variableType = typeChecker.getTypeAtLocation(declaration); + const isFunctionType = variableType.getCallSignatures().length > 0; + + // Add to index + const order = symbolOrderMap.get(variableName); + if (order === undefined) { + throw new Error(`Symbol '${variableName}' not found in entry files export order`); + } + indexEntries.push({ name: variableName, type: isFunctionType ? 'Function' : 'Constant', group: groupName, order }); + + if (isFunctionType) { + // Handle function variables like methods + const signature = variableType.getCallSignatures()[0]; + if (signature) { + const parameters = signature.getParameters(); + const params = parameters.map((param) => { + const paramDecl = param.valueDeclaration; + if (paramDecl && ts.isParameter(paramDecl)) { + const paramName = param.getName(); + const hasQuestionToken = paramDecl.questionToken !== undefined; + const hasDefault = paramDecl.initializer !== undefined; + const isRest = paramDecl.dotDotDotToken !== undefined; + const rawParamType = paramDecl.type ? paramDecl.type.getText() : typeChecker.typeToString(typeChecker.getTypeOfSymbolAtLocation(param, paramDecl)); + const paramType = cleanOptionalType(rawParamType, hasQuestionToken); + + if (hasDefault) { + const defaultValue = paramDecl.initializer.getText(); + return `\t${isRest ? '...' : ''}${paramName}: ${paramType} = ${defaultValue}`; + } else { + return `\t${isRest ? '...' : ''}${paramName}${hasQuestionToken ? '?' : ''}: ${paramType}`; + } + } + return `\t${param.getName()}: unknown`; + }); + + const returnType = getTypeString(signature.getReturnType()); + const functionSig = params.length > 0 + ? `${variableName}(\n${params.join(',\n')},\n): ${returnType};` + : `${variableName}(): ${returnType};`; + + let markdown = `\n\n\n\n# ${variableName}\n\n\`\`\`ts\n${functionSig}\n\`\`\`${description ? `\n\n${description}` : ''}`; + + // Find referenced types in all parameters and return type + const allTypeStrings = params.map(p => p.replace(/\t.*?:\s*/, '')).concat([returnType]); + const allReferences = filterToExportedTypes([...new Set(allTypeStrings.flatMap(findAllTypeReferences))], variableName); + markdown += formatReferences(allReferences); + + const outputPath = path.join(outputDir, `${variableName}.md`); + fs.writeFileSync(outputPath, markdown); + console.log(`Generated: ${outputPath}`); + } + } else { + // Handle regular variables + let markdown = `\n\n\n\n# ${variableName}\n\n${description ? `${description}\n\n` : ''}`; + const variableValue = declaration.initializer ? declaration.initializer.getText() : 'undefined'; + const variableDefinition = `const ${variableName} = ${variableValue};`; + markdown += `\`\`\`ts\n${variableDefinition}\n\`\`\``; + + // Find referenced types in the variable value + const references = filterToExportedTypes(findAllTypeReferences(variableValue), variableName); + markdown += formatReferences(references); + + const outputPath = path.join(outputDir, `${variableName}.md`); + fs.writeFileSync(outputPath, markdown); + console.log(`Generated: ${outputPath}`); + } + + return; + } + + { + const className = declaration.name.text; + const isAbstract = ts.isClassDeclaration(declaration) && declaration.modifiers?.some(mod => mod.kind === ts.SyntaxKind.AbstractKeyword); + + // Add to index + const order = symbolOrderMap.get(className); + if (order === undefined) { + throw new Error(`Symbol '${className}' not found in entry files export order`); + } + if (isAbstract) { + indexEntries.push({ name: className, type: 'Abstract class', group: groupName, order }); + } else if (ts.isClassDeclaration(declaration)) { + indexEntries.push({ name: className, type: 'Class', group: groupName, order }); + } else if (ts.isTypeAliasDeclaration(declaration)) { + indexEntries.push({ name: className, type: 'Type', group: groupName, order }); + } else if (ts.isInterfaceDeclaration(declaration)) { + indexEntries.push({ name: className, type: 'Interface', group: groupName, order }); + } + + const properties: string[] = []; + const events: string[] = []; + const methods: string[] = []; + const staticMethods: string[] = []; + let constructor: string | null = null; + let extendsClause = ''; + let typeParameters: string | null = null; + + // Get class description from JSDoc (or from superclass if none) + let description = ''; + const jsDocComment = ts.getJSDocCommentsAndTags(declaration)[0]; + + if (jsDocComment && ts.isJSDoc(jsDocComment)) { + // First try to get the comment from the parsed JSDoc + const commentText = jsDocComment.comment; + if (typeof commentText === 'string' && commentText.trim()) { + description = processLinkTags(commentText.trim(), className); + } else { + // If no comment text, extract from raw source text + const sourceFile = declaration.getSourceFile(); + const sourceText = sourceFile.getFullText(); + const start = jsDocComment.getStart(); + const end = jsDocComment.getEnd(); + const rawJsDoc = sourceText.substring(start, end); + + // Extract the content between /** and */ + const match = rawJsDoc.match(/\/\*\*(.*?)\*\//s); + if (match && match[1]) { + const content = match[1] + .split('\n') + .map(line => line.replace(/^\s*\*\s?/, '')) // Remove leading * and spaces + .join('\n') + .trim(); + + // Filter out @tags but keep the description + const lines = content.split('\n'); + const descLines = lines.filter(line => !line.trim().startsWith('@')); + const rawDesc = descLines.join('\n').trim(); + + if (rawDesc) { + description = processLinkTags(rawDesc, className); + } + } + } + } + + // If no description, check superclass (only for classes/interfaces) + if (!description && (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration)) && declaration.heritageClauses) { + const classType = typeChecker.getTypeAtLocation(declaration); + const baseTypes = classType.getBaseTypes(); + if (baseTypes && baseTypes.length > 0) { + const baseSymbol = baseTypes[0]!.getSymbol(); + if (baseSymbol && baseSymbol.valueDeclaration) { + const baseJsDoc = ts.getJSDocCommentsAndTags(baseSymbol.valueDeclaration)[0]; + if (baseJsDoc && ts.isJSDoc(baseJsDoc) && typeof baseJsDoc.comment === 'string') { + description = processLinkTags(baseJsDoc.comment.trim(), className); + } + } + } + } + + // Check for type parameters (only for classes/interfaces) + if ((ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration)) && declaration.typeParameters && declaration.typeParameters.length > 0) { + const typeParamStrings = declaration.typeParameters.map((tp) => { + const name = tp.name.text; + const constraint = tp.constraint ? ` extends ${tp.constraint.getText()}` : ''; + const defaultType = tp.default ? ` = ${tp.default.getText()}` : ''; + return `\t${name}${constraint}${defaultType}`; + }); + + const typeParamSig = `${className}<\n${typeParamStrings.join(',\n')},\n>`; + + // Get type parameter descriptions + const typeParamDocs: string[] = []; + const classJsDoc = ts.getJSDocCommentsAndTags(declaration)[0]; + if (classJsDoc && ts.isJSDoc(classJsDoc)) { + const templateTags = classJsDoc.tags?.filter((tag: any) => tag.tagName.text === 'template') || []; + templateTags.forEach((tag: any) => { + if (typeof tag.comment === 'string') { + const parts = tag.comment.trim().split(/\s+/); + const paramName = parts[0]; + const paramDesc = parts.slice(1).join(' ').replace(/^-\s*/, ''); + if (paramDesc) { + typeParamDocs.push(`- **${paramName}**: ${paramDesc}`); + } + } + }); + } + + typeParameters = `## Type parameters\n\n\`\`\`ts\n${typeParamSig}\n\`\`\``; + if (typeParamDocs.length > 0) { + typeParameters += `\n\n${typeParamDocs.join('\n')}`; + } + + // Find referenced types in type parameters for classes/interfaces + const typeParamRefs: string[] = []; + declaration.typeParameters.forEach((tp) => { + if (tp.constraint) { + typeParamRefs.push(...findAllTypeReferences(tp.constraint.getText())); + } + if (tp.default) { + typeParamRefs.push(...findAllTypeReferences(tp.default.getText())); + } + }); + const typeParamReferences = filterToExportedTypes([...new Set(typeParamRefs)], className); + const typeParamReferencesText = formatReferences(typeParamReferences); + if (typeParamReferencesText) { + typeParameters += typeParamReferencesText; + } + } + + // Check for extends clause (only for classes/interfaces) + if ((ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration)) && declaration.heritageClauses) { + const extendsClauseNode = declaration.heritageClauses.find( + clause => clause.token === ts.SyntaxKind.ExtendsKeyword, + ); + if (extendsClauseNode && extendsClauseNode.types[0]) { + const superClassName = extendsClauseNode.types[0].expression.getText(); + extendsClause = `\n\n**Extends:** [\`${superClassName}\`](./${superClassName}.md)\n`; + } + } + + // Helper to get JSDoc description with superclass fallback (recursive) + const getDescriptionWithFallback = (member: ts.ClassElement | ts.TypeElement, memberName: string): string => { + const jsDoc = ts.getJSDocCommentsAndTags(member)[0]; + if (jsDoc && ts.isJSDoc(jsDoc)) { + // First try the parsed comment + if (typeof jsDoc.comment === 'string' && jsDoc.comment.trim()) { + return processLinkTags(jsDoc.comment.trim(), className); + } else { + // If no parsed comment, extract from raw source (same logic as class descriptions) + const sourceFile = member.getSourceFile(); + const sourceText = sourceFile.getFullText(); + const start = jsDoc.getStart(); + const end = jsDoc.getEnd(); + const rawJsDoc = sourceText.substring(start, end); + + const match = rawJsDoc.match(/\/\*\*(.*?)\*\//s); + if (match && match[1]) { + const content = match[1] + .split('\n') + .map(line => line.replace(/^\s*\*\s?/, '')) // Remove leading * and spaces + .join('\n') + .trim(); + + // Filter out @tags but keep the description + const lines = content.split('\n'); + const descLines = lines.filter(line => !line.trim().startsWith('@')); + const rawDesc = descLines.join('\n').trim(); + + if (rawDesc) { + return processLinkTags(rawDesc, className); + } + } + } + } + + // Recursively check superclass hierarchy for this member's documentation + const findInHierarchy = (currentDeclaration: ts.ClassDeclaration): string => { + const currentType = typeChecker.getTypeAtLocation(currentDeclaration); + const baseTypes = currentType.getBaseTypes(); + + if (baseTypes && baseTypes.length > 0) { + const baseSymbol = baseTypes[0]!.getSymbol(); + if (baseSymbol && baseSymbol.valueDeclaration && ts.isClassDeclaration(baseSymbol.valueDeclaration)) { + const baseMember = baseSymbol.valueDeclaration.members.find(m => + m.name && m.name.getText() === memberName, + ); + if (baseMember) { + const baseJsDoc = ts.getJSDocCommentsAndTags(baseMember)[0]; + if (baseJsDoc && ts.isJSDoc(baseJsDoc) && typeof baseJsDoc.comment === 'string') { + return processLinkTags(baseJsDoc.comment.trim(), className); + } + } + // Recursively check further up the hierarchy + return findInHierarchy(baseSymbol.valueDeclaration); + } + } + return ''; + }; + + if (ts.isClassDeclaration(declaration)) { + return findInHierarchy(declaration); + } + + return ''; + }; + + // Helper to extract linked types from a member's JSDoc + const getLinkedTypesFromMember = (member: ts.ClassElement | ts.TypeElement): string[] => { + const jsDoc = ts.getJSDocCommentsAndTags(member)[0]; + if (jsDoc && ts.isJSDoc(jsDoc)) { + // First try the parsed comment + if (typeof jsDoc.comment === 'string' && jsDoc.comment.trim()) { + return extractLinkedTypes(jsDoc.comment.trim()); + } else { + // If no parsed comment, extract from raw source + const sourceFile = member.getSourceFile(); + const sourceText = sourceFile.getFullText(); + const start = jsDoc.getStart(); + const end = jsDoc.getEnd(); + const rawJsDoc = sourceText.substring(start, end); + + const match = rawJsDoc.match(/\/\*\*(.*?)\*\//s); + if (match && match[1]) { + const content = match[1] + .split('\n') + .map(line => line.replace(/^\s*\*\s?/, '')) // Remove leading * and spaces + .join('\n') + .trim(); + + // Filter out @tags but keep the description + const lines = content.split('\n'); + const descLines = lines.filter(line => !line.trim().startsWith('@')); + const rawDesc = descLines.join('\n').trim(); + + return extractLinkedTypes(rawDesc); + } + } + } + return []; + }; + + // Helper to process members (both own and inherited) + const processMember = (member: ts.ClassElement | ts.TypeElement, _isInherited = false) => { + // Check if member has @internal in JSDoc + const hasInternalTag = ts.getJSDocTags(member).some(tag => tag.tagName.text === 'internal'); + if (hasInternalTag) return; + + if (ts.isConstructorDeclaration(member) && !isAbstract) { + // Skip private constructors + const isPrivate = member.modifiers?.some(mod => mod.kind === ts.SyntaxKind.PrivateKeyword); + if (isPrivate) return; + + // Only process the first constructor we encounter to build the constructor section + // We'll collect all overloads separately + if (!constructor) { + // Collect all constructor overloads from the class + const constructorOverloads: ts.ConstructorDeclaration[] = []; + if (ts.isClassDeclaration(declaration)) { + declaration.members.forEach((m) => { + if (ts.isConstructorDeclaration(m) && !m.modifiers?.some(mod => mod.kind === ts.SyntaxKind.PrivateKeyword)) { + constructorOverloads.push(m); + } + }); + } + + // Build individual constructor blocks with their own descriptions + const constructorBlocks: string[] = []; + const allReferencedTypes: string[] = []; + + constructorOverloads.forEach((ctor) => { + // Skip the implementation (the one with a body) unless it's the only constructor + if (ctor.body && constructorOverloads.length > 1) { + // Process constructor parameters with visibility modifiers as properties (only from implementation) + ctor.parameters.forEach((param) => { + // Check if parameter has visibility modifier (public, private, protected, readonly) + const hasVisibilityModifier = param.modifiers?.some(mod => + mod.kind === ts.SyntaxKind.PublicKeyword + || mod.kind === ts.SyntaxKind.PrivateKeyword + || mod.kind === ts.SyntaxKind.ProtectedKeyword + || mod.kind === ts.SyntaxKind.ReadonlyKeyword, + ); + + if (!hasVisibilityModifier) return; + + // Skip private parameters + const isPrivate = param.modifiers?.some(mod => mod.kind === ts.SyntaxKind.PrivateKeyword); + if (isPrivate) return; + + const paramName = param.name.getText(); + const hasQuestionToken = param.questionToken !== undefined; + const isReadonly = param.modifiers?.some(mod => mod.kind === ts.SyntaxKind.ReadonlyKeyword); + const rawParamType = param.type ? param.type.getText() : typeChecker.typeToString(typeChecker.getTypeAtLocation(param)); + const paramType = cleanOptionalType(rawParamType, hasQuestionToken); + + const propertyDef = `${isReadonly ? 'readonly ' : ''}${paramName}${hasQuestionToken ? '?' : ''}: ${paramType};`; + + // Get description from JSDoc comment on the parameter + const paramJsDoc = ts.getJSDocCommentsAndTags(param)[0]; + let desc = ''; + if (paramJsDoc && ts.isJSDoc(paramJsDoc) && typeof paramJsDoc.comment === 'string') { + desc = processLinkTags(paramJsDoc.comment.trim(), className); + } + + // Find referenced types + const references = filterToExportedTypes(findAllTypeReferences(paramType), className); + const referencesText = formatReferences(references); + + const propertyContent = `### \`${paramName}\`\n\n\`\`\`ts\n${propertyDef}\n\`\`\`${desc ? `\n\n${desc}` : ''}${referencesText}`; + properties.push(propertyContent); + }); + return; + } + + // Build parameter list for this overload + const params = ctor.parameters.map((param) => { + const paramName = param.name.getText(); + const hasQuestionToken = param.questionToken !== undefined; + const hasDefault = param.initializer !== undefined; + const isRest = param.dotDotDotToken !== undefined; + const rawParamType = param.type ? param.type.getText() : typeChecker.typeToString(typeChecker.getTypeAtLocation(param)); + const paramType = cleanOptionalType(rawParamType, hasQuestionToken); + + if (hasDefault) { + const defaultValue = param.initializer.getText(); + return `\t${isRest ? '...' : ''}${paramName}: ${paramType} = ${defaultValue}`; + } else { + return `\t${isRest ? '...' : ''}${paramName}${hasQuestionToken ? '?' : ''}: ${paramType}`; + } + }); + + const constructorSig = params.length > 0 + ? `constructor(\n${params.join(',\n')},\n): ${className};` + : `constructor(): ${className};`; + + // Collect referenced types from this overload + const ctorTypeStrings = params.map(p => p.replace(/\t.*?:\s*/, '')); + allReferencedTypes.push(...ctorTypeStrings.flatMap(findAllTypeReferences)); + + // Get description for this specific overload + const constructorDesc = getDescriptionWithFallback(ctor, 'constructor'); + const linkedTypes = getLinkedTypesFromMember(ctor); + + // Get parameter descriptions for this specific overload + const paramDocs: string[] = []; + const jsDoc = ts.getJSDocCommentsAndTags(ctor)[0]; + if (jsDoc && ts.isJSDoc(jsDoc)) { + const paramTags = jsDoc.tags?.filter((tag: any) => tag.tagName.text === 'param') || []; + paramTags.forEach((tag: any) => { + if (ts.isJSDocParameterTag(tag) && tag.name && typeof tag.comment === 'string') { + const paramName = tag.name.getText(); + const paramDesc = tag.comment.trim().replace(/^-\s*/, ''); + paramDocs.push(`- **${paramName}**: ${paramDesc}`); + } + }); + } + + // Build this constructor block + let constructorBlock = `\`\`\`ts\n${constructorSig}\n\`\`\``; + if (constructorDesc) { + constructorBlock += `\n\n${constructorDesc}`; + } + if (paramDocs.length > 0) { + constructorBlock += `\n\n**Parameters:**\n\n${paramDocs.join('\n')}`; + } + + // Find referenced types for this overload + const overloadReferences = filterToExportedTypes([...new Set(ctorTypeStrings.flatMap(findAllTypeReferences))], className); + constructorBlock += formatReferences(overloadReferences, linkedTypes); + + constructorBlocks.push(constructorBlock); + }); + + // Build the constructor section + const headingText = constructorBlocks.length > 1 ? 'Constructors' : 'Constructor'; + const separator = constructorBlocks.length > 1 ? '\n\n---\n\n' : '\n\n'; + constructor = `## ${headingText}\n\n${constructorBlocks.join(separator)}`; + } + } else if ((ts.isPropertyDeclaration(member) || ts.isPropertySignature(member)) && member.name) { + const name = member.name.getText(); + const isReadonly = member.modifiers?.some(mod => mod.kind === ts.SyntaxKind.ReadonlyKeyword); + const isOptional = member.questionToken !== undefined; + // Prefer explicit type annotation if available, otherwise use type checker + let rawType: string; + if (member.type) { + rawType = member.type.getText(); + // For optional properties, check if the type is a reference to an exported type + // If so, don't apply undefined removal logic to preserve the type reference + if (isOptional && exportedTypes.has(rawType)) { + // Keep the original type reference for exported types + } else { + rawType = cleanOptionalType(rawType, isOptional); + } + } else { + const memberType = typeChecker.getTypeAtLocation(member); + const typeName = memberType.getSymbol()?.getName(); + // If the type has a symbol name and it's in our exported types, use that instead of expanding + if (typeName && exportedTypes.has(typeName)) { + rawType = typeName; + } else { + rawType = getTypeString(memberType); + rawType = cleanOptionalType(rawType, isOptional); + } + } + const type = rawType; + const propertyDef = `${isReadonly ? 'readonly ' : ''}${name}${isOptional ? '?' : ''}: ${type};`; + + // Get description from JSDoc + const desc = getDescriptionWithFallback(member, name); + const linkedTypes = getLinkedTypesFromMember(member); + + // Find referenced types + const references = filterToExportedTypes(findAllTypeReferences(type), className); + const referencesText = formatReferences(references, linkedTypes); + + // Check if this is an event handler (starts with "on" and can be a function) + const isEventHandler = name.startsWith('on') && ( + type.includes('=>') + || type.includes('Function') + || type.includes('() =>') + || (type.includes('(') && type.includes(') =>')) + ); + + const inheritedBadge = ''; + const propertyContent = `### \`${name}\`${inheritedBadge}\n\n\`\`\`ts\n${propertyDef}\n\`\`\`${desc ? `\n\n${desc}` : ''}${referencesText}`; + + if (isEventHandler) { + events.push(propertyContent); + } else { + properties.push(propertyContent); + } + } else if (ts.isGetAccessorDeclaration(member) && member.name) { + const name = member.name.getText(); + // For getters, prefer the explicit return type annotation if available + let rawType: string; + if (member.type) { + rawType = member.type.getText(); + } else { + const memberType = typeChecker.getTypeAtLocation(member); + const typeName = memberType.getSymbol()?.getName(); + // If the type has a symbol name and it's in our exported types, use that instead of expanding + if (typeName && exportedTypes.has(typeName)) { + rawType = typeName; + } else { + rawType = getTypeString(memberType); + } + } + const type = cleanOptionalType(rawType, false); + const accessorDef = `get ${name}(): ${type};`; + + // Get description from JSDoc + const desc = getDescriptionWithFallback(member, name); + const linkedTypes = getLinkedTypesFromMember(member); + + // Find referenced types + const references = filterToExportedTypes(findAllTypeReferences(type), className); + const referencesText = formatReferences(references, linkedTypes); + + const inheritedBadge = ''; + properties.push(`### \`${name}\`${inheritedBadge}\n\n\`\`\`ts\n${accessorDef}\n\`\`\`${desc ? `\n\n${desc}` : ''}${referencesText}`); + } else if (ts.isSetAccessorDeclaration(member) && member.name) { + const name = member.name.getText(); + const param = member.parameters[0]; + const isOptional = param?.questionToken !== undefined; + const rawParamType = param?.type + ? typeChecker.typeToString(typeChecker.getTypeAtLocation(param)) + : 'any'; + const paramType = cleanOptionalType(rawParamType, isOptional); + const accessorDef = `set ${name}(value: ${paramType});`; + + // Get description from JSDoc + const desc = getDescriptionWithFallback(member, name); + const linkedTypes = getLinkedTypesFromMember(member); + + // Find referenced types + const references = filterToExportedTypes(findAllTypeReferences(paramType), className); + const referencesText = formatReferences(references, linkedTypes); + + const inheritedBadge = ''; + properties.push(`### \`${name}\`${inheritedBadge}\n\n\`\`\`ts\n${accessorDef}\n\`\`\`${desc ? `\n\n${desc}` : ''}${referencesText}`); + } else if ((ts.isMethodDeclaration(member) || ts.isMethodSignature(member)) && member.name) { + const name = member.name.getText(); + const isStatic = member.modifiers?.some(mod => mod.kind === ts.SyntaxKind.StaticKeyword); + + // For method overloads, skip the implementation if it has no JSDoc + // (the overload signatures should have the documentation) + const methodJsDoc = ts.getJSDocCommentsAndTags(member)[0]; + const hasJsDoc = methodJsDoc && ts.isJSDoc(methodJsDoc) && methodJsDoc.comment; + + // If this is a method declaration (has a body) and has no JSDoc, check if there are overloads + if (ts.isMethodDeclaration(member) && member.body && !hasJsDoc && ts.isClassDeclaration(declaration)) { + // Check if there are other methods with the same name (overloads) + const sameNameMethods = declaration.members.filter(m => + (ts.isMethodDeclaration(m) || ts.isMethodSignature(m)) + && m.name && m.name.getText() === name, + ); + + // If there are multiple methods with the same name, this is likely an overloaded method + // Skip the implementation (the one with a body) if it has no JSDoc + if (sameNameMethods.length > 1) { + return; + } + } + + // Build parameter list with each on its own line + const params = member.parameters.map((param) => { + const paramName = param.name.getText(); + const hasQuestionToken = param.questionToken !== undefined; + const hasDefault = param.initializer !== undefined; + const isRest = param.dotDotDotToken !== undefined; + const rawParamType = param.type ? param.type.getText() : typeChecker.typeToString(typeChecker.getTypeAtLocation(param)); + const paramType = cleanOptionalType(rawParamType, hasQuestionToken); + + if (hasDefault) { + const defaultValue = param.initializer.getText(); + return `\t${isRest ? '...' : ''}${paramName}: ${paramType} = ${defaultValue}`; + } else { + return `\t${isRest ? '...' : ''}${paramName}${hasQuestionToken ? '?' : ''}: ${paramType}`; + } + }); + + // Get return type + const signature = typeChecker.getSignatureFromDeclaration(member); + const returnType = signature ? getTypeString(signature.getReturnType()) : 'void'; + + // Format method signature + const methodSig = params.length > 0 + ? `${isStatic ? 'static ' : ''}${name}(\n${params.join(',\n')},\n): ${returnType};` + : `${isStatic ? 'static ' : ''}${name}(): ${returnType};`; + + // Get method description from JSDoc + const desc = getDescriptionWithFallback(member, name); + const linkedTypes = getLinkedTypesFromMember(member); + + // Get parameter and return descriptions + const paramDocs: string[] = []; + let returnDoc = ''; + const jsDoc = ts.getJSDocCommentsAndTags(member)[0]; + if (jsDoc && ts.isJSDoc(jsDoc)) { + const paramTags = jsDoc.tags?.filter((tag: any) => tag.tagName.text === 'param') || []; + paramTags.forEach((tag: any) => { + if (ts.isJSDocParameterTag(tag) && tag.name && typeof tag.comment === 'string') { + const paramName = tag.name.getText(); + const paramDesc = tag.comment.trim().replace(/^-\s*/, ''); + paramDocs.push(`- **${paramName}**: ${paramDesc}`); + } + }); + + const returnTags = jsDoc.tags?.filter((tag: any) => tag.tagName.text === 'returns' || tag.tagName.text === 'return') || []; + if (returnTags.length > 0 && returnTags[0] && typeof returnTags[0].comment === 'string') { + returnDoc = returnTags[0].comment.trim().replace(/^-\s*/, ''); + } + } + + const inheritedBadge = ''; + let methodContent = `### \`${name}()\`${inheritedBadge}\n\n\`\`\`ts\n${methodSig}\n\`\`\``; + if (desc) { + methodContent += `\n\n${desc}`; + } + if (paramDocs.length > 0) { + methodContent += `\n\n**Parameters:**\n\n${paramDocs.join('\n')}`; + } + if (returnDoc) { + methodContent += `\n\n**Returns:** ${returnDoc}`; + } + + // Find referenced types in all parameters and return type + const allTypeStrings = params.map(p => p.replace(/\t.*?:\s*/, '')).concat([returnType]); + const allReferences = filterToExportedTypes([...new Set(allTypeStrings.flatMap(findAllTypeReferences))], className); + methodContent += formatReferences(allReferences, linkedTypes); + + if (isStatic) { + staticMethods.push(methodContent); + } else { + methods.push(methodContent); + } + } + }; + + // Track names of own members to avoid duplicates with inherited + const ownMemberNames = new Set(); + + // Process own members first + if (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration)) { + declaration.members.forEach((member) => { + if (member.name) { + ownMemberNames.add(member.name.getText()); + } + processMember(member); + }); + } else if (ts.isTypeAliasDeclaration(declaration)) { + // For type aliases, check if it's a simple union type (like string literals) + const resolvedType = typeChecker.getTypeAtLocation(declaration); + + // If it's a primitive type or simple union, skip property processing + const isPrimitive = !!(resolvedType.flags & (ts.TypeFlags.String | ts.TypeFlags.Number | ts.TypeFlags.Boolean | ts.TypeFlags.StringLiteral | ts.TypeFlags.NumberLiteral | ts.TypeFlags.BooleanLiteral)); + const isSimpleUnion = resolvedType.isUnion() && resolvedType.types.every(t => + t.flags & (ts.TypeFlags.StringLiteral | ts.TypeFlags.NumberLiteral | ts.TypeFlags.BooleanLiteral | ts.TypeFlags.String | ts.TypeFlags.Number | ts.TypeFlags.Boolean), + ); + + if (!isPrimitive && !isSimpleUnion) { + const typeProperties = typeChecker.getPropertiesOfType(resolvedType); + + typeProperties.forEach((prop) => { + // Create a synthetic property signature for each resolved property + const propName = prop.getName(); + const propType = typeChecker.getTypeOfSymbolAtLocation(prop, declaration); + const propTypeString = getTypeString(propType); + const isOptional = (prop.flags & ts.SymbolFlags.Optional) !== 0; + + // Get JSDoc from the original declaration + let desc = ''; + const propDeclaration = prop.valueDeclaration || prop.declarations?.[0]; + if (propDeclaration) { + const jsDoc = ts.getJSDocCommentsAndTags(propDeclaration)[0]; + if (jsDoc && ts.isJSDoc(jsDoc) && typeof jsDoc.comment === 'string') { + desc = processLinkTags(jsDoc.comment.trim(), className); + } + } + + // Check if this property's original type annotation references an exported type + let cleanedType = propTypeString; + if (propDeclaration && ts.isPropertySignature(propDeclaration) && propDeclaration.type) { + const originalType = propDeclaration.type.getText(); + if (exportedTypes.has(originalType)) { + cleanedType = originalType; + } else { + cleanedType = cleanOptionalType(propTypeString, isOptional); + } + } else { + cleanedType = cleanOptionalType(propTypeString, isOptional); + } + + const propertyDef = `${propName}${isOptional ? '?' : ''}: ${cleanedType};`; + + // Find referenced types + const references = filterToExportedTypes(findAllTypeReferences(cleanedType), className); + const referencesText = formatReferences(references); + + // Check if this is an event handler (starts with "on" and can be a function) + const isEventHandler = propName.startsWith('on') && ( + cleanedType.includes('=>') + || cleanedType.includes('Function') + || cleanedType.includes('() =>') + || (cleanedType.includes('(') && cleanedType.includes(') =>')) + ); + + // Type alias properties are never inherited + const propertyContent = `### \`${propName}\`\n\n\`\`\`ts\n${propertyDef}\n\`\`\`${desc ? `\n\n${desc}` : ''}${referencesText}`; + + if (isEventHandler) { + events.push(propertyContent); + } else { + properties.push(propertyContent); + } + }); + } + } + + // Process inherited members (skip if overridden, only for classes) + if (ts.isClassDeclaration(declaration)) { + const classType = typeChecker.getTypeAtLocation(declaration); + const baseTypes = classType.getBaseTypes(); + if (baseTypes && baseTypes.length > 0) { + baseTypes.forEach((baseType) => { + const baseSymbol = baseType.getSymbol(); + if (baseSymbol && baseSymbol.valueDeclaration && ts.isClassDeclaration(baseSymbol.valueDeclaration)) { + baseSymbol.valueDeclaration.members.forEach((member) => { + // Skip if this member is overridden in the derived class + if (member.name && ownMemberNames.has(member.name.getText())) { + return; + } + // Never process inherited constructors - always use the derived class constructor + if (ts.isConstructorDeclaration(member)) { + return; + } + processMember(member, true); + }); + } + }); + } + } + + // Sort properties and methods alphabetically (but not for type aliases - keep source order) + if (!ts.isTypeAliasDeclaration(declaration)) { + properties.sort((a, b) => { + const nameA = a.match(/### (.+)/)?.[1] || ''; + const nameB = b.match(/### (.+)/)?.[1] || ''; + return nameA.localeCompare(nameB); + }); + } + + staticMethods.sort((a, b) => { + const nameA = a.match(/### (.+)/)?.[1] || ''; + const nameB = b.match(/### (.+)/)?.[1] || ''; + return nameA.localeCompare(nameB); + }); + + let markdown = ''; + + // Add VPBadge import and badge for all types + markdown += `\n\n`; + + if (isAbstract) { + markdown += `\n\n`; + } else if (ts.isClassDeclaration(declaration)) { + markdown += `\n\n`; + } else if (ts.isTypeAliasDeclaration(declaration)) { + markdown += `\n\n`; + } else if (ts.isInterfaceDeclaration(declaration)) { + markdown += `\n\n`; + } + + markdown += `# ${className}\n\n${description ? `${description}\n` : ''}${extendsClause}`; + + // Add subclasses section for classes that have subclasses + if (ts.isClassDeclaration(declaration) && classHierarchy.has(className)) { + const subclasses = classHierarchy.get(className)!; + // Sort by definition order instead of alphabetically + subclasses.sort((a, b) => { + const orderA = symbolOrderMap.get(a); + const orderB = symbolOrderMap.get(b); + if (orderA === undefined) throw new Error(`Symbol '${a}' not found in entry files export order`); + if (orderB === undefined) throw new Error(`Symbol '${b}' not found in entry files export order`); + return orderA - orderB; + }); + markdown += `\n## Subclasses\n\n`; + subclasses.forEach((sub) => { + markdown += `- [\`${sub}\`](./${sub}.md)\n`; + }); + } + + // Add instances section for classes that have instances + if (ts.isClassDeclaration(declaration) && classInstances.has(className)) { + const instances = classInstances.get(className)!; + // Sort by definition order instead of alphabetically + instances.sort((a, b) => { + const orderA = symbolOrderMap.get(a); + const orderB = symbolOrderMap.get(b); + if (orderA === undefined) throw new Error(`Symbol '${a}' not found in entry files export order`); + if (orderB === undefined) throw new Error(`Symbol '${b}' not found in entry files export order`); + return orderA - orderB; + }); + markdown += `\n## Instances\n\n`; + instances.forEach((instance) => { + markdown += `- [\`${instance}\`](./${instance}.md)\n`; + }); + } + + // Add type definition for type aliases + if (ts.isTypeAliasDeclaration(declaration) && declaration.type) { + const resolvedType = typeChecker.getTypeAtLocation(declaration); + const isPrimitive = !!(resolvedType.flags & (ts.TypeFlags.String | ts.TypeFlags.Number | ts.TypeFlags.Boolean | ts.TypeFlags.StringLiteral | ts.TypeFlags.NumberLiteral | ts.TypeFlags.BooleanLiteral)); + const isSimpleUnion = resolvedType.isUnion() && resolvedType.types.every(t => + t.flags & (ts.TypeFlags.StringLiteral | ts.TypeFlags.NumberLiteral | ts.TypeFlags.BooleanLiteral | ts.TypeFlags.String | ts.TypeFlags.Number | ts.TypeFlags.Boolean), + ); + + // Build the type name with generic parameters if they exist + let typeName = className; + if (declaration.typeParameters && declaration.typeParameters.length > 0) { + const typeParamStrings = declaration.typeParameters.map((tp) => { + const name = tp.name.text; + const constraint = tp.constraint ? ` extends ${tp.constraint.getText()}` : ''; + const defaultType = tp.default ? ` = ${tp.default.getText()}` : ''; + return `${name}${constraint}${defaultType}`; + }); + typeName = `${className}<${typeParamStrings.join(', ')}>`; + } + + let typeText; + if (isPrimitive || isSimpleUnion) { + // For primitive types or simple unions, use resolved type string + const resolvedTypeString = typeChecker.typeToString(resolvedType); + if (resolvedTypeString === className) { + // If resolved type is just the alias name, use original text + typeText = declaration.type.getText(); + } else { + typeText = resolvedTypeString; + } + // Convert string literals from double quotes to single quotes + typeText = typeText.replace(/"([^"]*)"/g, '\'$1\''); + if (typeText.includes(' | ')) { + const unionMembers = typeText.split(' | '); + typeText = '\n\t| ' + unionMembers.join('\n\t| '); + } + } else { + // For complex types, use the original text + typeText = declaration.type.getText(); + // Format object types with proper line breaks + if (typeText.includes('{')) { + typeText = formatObjectType(typeText); + } + } + const typeDefinition = `type ${typeName} = ${typeText};`; + + // Find referenced types in the type definition and generic parameters + const allTypeRefs = findAllTypeReferences(typeText); + // Also find references in type parameters (extends clauses and default types) + if (declaration.typeParameters && declaration.typeParameters.length > 0) { + declaration.typeParameters.forEach((tp) => { + if (tp.constraint) { + allTypeRefs.push(...findAllTypeReferences(tp.constraint.getText())); + } + if (tp.default) { + allTypeRefs.push(...findAllTypeReferences(tp.default.getText())); + } + }); + } + const typeReferences = filterToExportedTypes([...new Set(allTypeRefs)], className); + const typeReferencesText = formatReferences(typeReferences); + + markdown += `\n\`\`\`ts\n${typeDefinition}\n\`\`\`${typeReferencesText}`; + } + + if (typeParameters) { + markdown += `\n${typeParameters}\n\n`; + } + + if (constructor) { + markdown += `\n${constructor}\n\n`; + } + + if (staticMethods.length > 0) { + markdown += `\n## Static methods\n\n${staticMethods.join('\n\n')}\n\n`; + } + + if (properties.length > 0) { + markdown += `\n## Properties\n\n${properties.join('\n\n')}\n\n`; + } + + if (events.length > 0) { + markdown += `\n## Events\n\n${events.join('\n\n')}\n\n`; + } + + if (methods.length > 0) { + markdown += `\n## Methods\n\n${methods.join('\n\n')}\n`; + } + const outputPath = path.join(outputDir, `${className}.md`); + fs.writeFileSync(outputPath, markdown); + console.log(`Generated: ${outputPath}`); + } + }); + + // Generate index.md with all exported symbols grouped by group + const entriesByGroup = new Map>(); + + indexEntries.forEach((entry) => { + if (!entriesByGroup.has(entry.group)) { + entriesByGroup.set(entry.group, []); + } + entriesByGroup.get(entry.group)!.push({ name: entry.name, type: entry.type, order: entry.order }); + }); + + // Sort groups according to API config order + const configGroups = Object.keys(groupConfig); + const sortedGroups = configGroups.filter(group => entriesByGroup.has(group)); + + // Check for groups in entries that aren't in config + const missingGroups = Array.from(entriesByGroup.keys()).filter(group => !configGroups.includes(group)); + if (missingGroups.length > 0) { + throw new Error(`Groups found in code but not in API config: ${missingGroups.join(', ')}`); + } + + let indexMarkdown = `# ${headingText}\n\n`; + + // Add intro text if provided + if (introText) { + indexMarkdown += `${introText}\n\n`; + } + + sortedGroups.forEach((group) => { + const entries = entriesByGroup.get(group)!; + // Sort entries by definition order + entries.sort((a, b) => a.order - b.order); + + indexMarkdown += `## ${group}\n\n`; + const groupDescription = groupConfig[group]; + if (groupDescription) { + indexMarkdown += `${groupDescription}\n\n`; + } + entries.forEach((entry) => { + indexMarkdown += `- [${entry.name}](./${entry.name}.md)\n`; + }); + indexMarkdown += '\n'; + }); + + const indexPath = path.join(outputDir, 'index.md'); + fs.writeFileSync(indexPath, indexMarkdown); + console.log(`Generated: ${indexPath}`); + + // Generate index.json with sidebar config structure + const sidebarConfig = sortedGroups.map((group) => { + const entries = entriesByGroup.get(group)!; + // Sort entries by definition order + entries.sort((a, b) => a.order - b.order); + + return { + text: group, + collapsed: true, + items: entries.map(entry => ({ + text: entry.name, + link: `/api/${entry.name}`, + })), + }; + }); + + const jsonPath = path.join(outputDir, 'index.json'); + fs.writeFileSync(jsonPath, JSON.stringify(sidebarConfig, null, 2)); + console.log(`Generated: ${jsonPath}`); +}; + +const main = () => { + const args = process.argv.slice(2); + if (args.length < 2) { + console.error('Usage: npm run generate-docs [entry-file2 ...] '); + console.error(' entry-files: One or more TypeScript entry files'); + console.error(' api-config-file: JSON config file defining groups'); + process.exit(1); + } + + // Last argument is the config file, everything else are entry files + const apiConfigFile = args[args.length - 1]!; + const entryFiles = args.slice(0, -1); + + generateDocs(entryFiles, apiConfigFile); +}; + +main(); diff --git a/src/codec.ts b/src/codec.ts index 8e4740e..119f7cb 100644 --- a/src/codec.ts +++ b/src/codec.ts @@ -28,6 +28,7 @@ import { SubtitleMetadata } from './subtitles'; /** * List of known video codecs, ordered by encoding preference. + * @group Codecs * @public */ export const VIDEO_CODECS = [ @@ -39,6 +40,7 @@ export const VIDEO_CODECS = [ ] as const; /** * List of known PCM (uncompressed) audio codecs, ordered by encoding preference. + * @group Codecs * @public */ export const PCM_AUDIO_CODECS = [ @@ -59,6 +61,7 @@ export const PCM_AUDIO_CODECS = [ ] as const; /** * List of known compressed audio codecs, ordered by encoding preference. + * @group Codecs * @public */ export const NON_PCM_AUDIO_CODECS = [ @@ -70,6 +73,7 @@ export const NON_PCM_AUDIO_CODECS = [ ] as const; /** * List of known audio codecs, ordered by encoding preference. + * @group Codecs * @public */ export const AUDIO_CODECS = [ @@ -78,6 +82,7 @@ export const AUDIO_CODECS = [ ] as const; /** * List of known subtitle codecs, ordered by encoding preference. + * @group Codecs * @public */ export const SUBTITLE_CODECS = [ @@ -86,22 +91,26 @@ export const SUBTITLE_CODECS = [ /** * Union type of known video codecs. + * @group Codecs * @public */ export type VideoCodec = typeof VIDEO_CODECS[number]; /** * Union type of known audio codecs. + * @group Codecs * @public */ export type AudioCodec = typeof AUDIO_CODECS[number]; export type PcmAudioCodec = typeof PCM_AUDIO_CODECS[number]; /** * Union type of known subtitle codecs. + * @group Codecs * @public */ export type SubtitleCodec = typeof SUBTITLE_CODECS[number]; /** * Union type of known media codecs. + * @group Codecs * @public */ export type MediaCodec = VideoCodec | AudioCodec | SubtitleCodec; @@ -726,110 +735,6 @@ export const getAudioEncoderConfigExtension = (codec: AudioCodec) => { return {}; }; -/** - * Represents a subjective media quality level. - * @public - */ -export class Quality { - /** @internal */ - _factor: number; - - /** @internal */ - constructor(factor: number) { - this._factor = factor; - } - - /** @internal */ - _toVideoBitrate(codec: VideoCodec, width: number, height: number) { - const pixels = width * height; - - const codecEfficiencyFactors = { - avc: 1.0, // H.264/AVC (baseline) - hevc: 0.6, // H.265/HEVC (~40% more efficient than AVC) - vp9: 0.6, // Similar to HEVC - av1: 0.4, // ~60% more efficient than AVC - vp8: 1.2, // Slightly less efficient than AVC - }; - - const referencePixels = 1920 * 1080; - const referenceBitrate = 3000000; - const scaleFactor = Math.pow(pixels / referencePixels, 0.95); // Slight non-linear scaling - const baseBitrate = referenceBitrate * scaleFactor; - - const codecAdjustedBitrate = baseBitrate * codecEfficiencyFactors[codec]; - const finalBitrate = codecAdjustedBitrate * this._factor; - - return Math.ceil(finalBitrate / 1000) * 1000; - } - - /** @internal */ - _toAudioBitrate(codec: AudioCodec) { - if ((PCM_AUDIO_CODECS as readonly string[]).includes(codec) || codec === 'flac') { - return undefined; - } - - const baseRates = { - aac: 128000, // 128kbps base for AAC - opus: 64000, // 64kbps base for Opus - mp3: 160000, // 160kbps base for MP3 - vorbis: 64000, // 64kbps base for Vorbis - }; - - const baseBitrate = baseRates[codec as keyof typeof baseRates]; - if (!baseBitrate) { - throw new Error(`Unhandled codec: ${codec}`); - } - - let finalBitrate = baseBitrate * this._factor; - - if (codec === 'aac') { - // AAC only works with specific bitrates, let's find the closest - const validRates = [96000, 128000, 160000, 192000]; - finalBitrate = validRates.reduce((prev, curr) => - Math.abs(curr - finalBitrate) < Math.abs(prev - finalBitrate) ? curr : prev, - ); - } else if (codec === 'opus' || codec === 'vorbis') { - finalBitrate = Math.max(6000, finalBitrate); - } else if (codec === 'mp3') { - const validRates = [ - 8000, 16000, 24000, 32000, 40000, 48000, 64000, 80000, - 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, - ]; - finalBitrate = validRates.reduce((prev, curr) => - Math.abs(curr - finalBitrate) < Math.abs(prev - finalBitrate) ? curr : prev, - ); - } - - return Math.round(finalBitrate / 1000) * 1000; - } -} - -/** - * Represents a very low media quality. - * @public - */ -export const QUALITY_VERY_LOW = new Quality(0.3); -/** - * Represents a low media quality. - * @public - */ -export const QUALITY_LOW = new Quality(0.6); -/** - * Represents a medium media quality. - * @public - */ -export const QUALITY_MEDIUM = new Quality(1); -/** - * Represents a high media quality. - * @public - */ -export const QUALITY_HIGH = new Quality(2); -/** - * Represents a very high media quality. - * @public - */ -export const QUALITY_VERY_HIGH = new Quality(4); - const VALID_VIDEO_CODEC_STRING_PREFIXES = ['avc1', 'avc3', 'hev1', 'hvc1', 'vp8', 'vp09', 'av01']; const AVC_CODEC_STRING_REGEX = /^(avc1|avc3)\.[0-9a-fA-F]{6}$/; const HEVC_CODEC_STRING_REGEX = /^(hev1|hvc1)\.(?:[ABC]?\d+)\.[0-9a-fA-F]{1,8}\.[LH]\d+(?:\.[0-9a-fA-F]{1,2}){0,6}$/; diff --git a/src/conversion.ts b/src/conversion.ts index 014a6bd..c3c1a0a 100644 --- a/src/conversion.ts +++ b/src/conversion.ts @@ -10,15 +10,14 @@ import { AUDIO_CODECS, AudioCodec, NON_PCM_AUDIO_CODECS, - Quality, - QUALITY_HIGH, VIDEO_CODECS, VideoCodec, } from './codec'; import { - AudioEncodingConfig, getEncodableAudioCodecs, getFirstEncodableVideoCodec, + Quality, + QUALITY_HIGH, VideoEncodingConfig, } from './encode'; import { Input } from './input'; @@ -51,6 +50,7 @@ import { AudioSample, VideoSample } from './sample'; /** * The options for media file conversion. + * @group Conversion * @public */ export type ConversionOptions = { @@ -62,8 +62,8 @@ export type ConversionOptions = { /** * Video-specific options. When passing an object, the same options are applied to all video tracks. When passing a * function, it will be invoked for each video track and is expected to return or resolve to the options - * for that specific track. The function is passed an instance of `InputVideoTrack` as well as a number `n`, which - * is the 1-based index of the track in the list of all video tracks. + * for that specific track. The function is passed an instance of {@link InputVideoTrack} as well as a number `n`, + * which is the 1-based index of the track in the list of all video tracks. */ video?: ConversionVideoOptions | ((track: InputVideoTrack, n: number) => MaybePromise); @@ -71,8 +71,8 @@ export type ConversionOptions = { /** * Audio-specific options. When passing an object, the same options are applied to all audio tracks. When passing a * function, it will be invoked for each audio track and is expected to return or resolve to the options - * for that specific track. The function is passed an instance of `InputAudioTrack` as well as a number `n`, which - * is the 1-based index of the track in the list of all audio tracks. + * for that specific track. The function is passed an instance of {@link InputAudioTrack} as well as a number `n`, + * which is the 1-based index of the track in the list of all audio tracks. */ audio?: ConversionAudioOptions | ((track: InputAudioTrack, n: number) => MaybePromise); @@ -88,10 +88,11 @@ export type ConversionOptions = { /** * Video-specific options. + * @group Conversion * @public */ export type ConversionVideoOptions = { - /** If true, all video tracks will be discarded and will not be present in the output. */ + /** If `true`, all video tracks will be discarded and will not be present in the output. */ discard?: boolean; /** * The desired width of the output video in pixels, defaulting to the video's natural display width. If height @@ -106,10 +107,10 @@ export type ConversionVideoOptions = { /** * The fitting algorithm in case both width and height are set, or if the input video changes its size over time. * - * - 'fill' will stretch the image to fill the entire box, potentially altering aspect ratio. - * - 'contain' will contain the entire image within the box while preserving aspect ratio. This may lead to + * - `'fill'` will stretch the image to fill the entire box, potentially altering aspect ratio. + * - `'contain'` will contain the entire image within the box while preserving aspect ratio. This may lead to * letterboxing. - * - 'cover' will scale the image until the entire box is filled, while preserving aspect ratio. + * - `'cover'` will scale the image until the entire box is filled, while preserving aspect ratio. */ fit?: 'fill' | 'contain' | 'cover'; /** @@ -125,17 +126,18 @@ export type ConversionVideoOptions = { /** The desired output video codec. */ codec?: VideoCodec; /** The desired bitrate of the output video. */ - bitrate?: VideoEncodingConfig['bitrate']; - /** When true, video will always be re-encoded instead of directly copying over the encoded samples. */ + bitrate?: number | Quality; + /** When `true`, video will always be re-encoded instead of directly copying over the encoded samples. */ forceTranscode?: boolean; }; /** * Audio-specific options. + * @group Conversion * @public */ export type ConversionAudioOptions = { - /** If true, all audio tracks will be discarded and will not be present in the output. */ + /** If `true`, all audio tracks will be discarded and will not be present in the output. */ discard?: boolean; /** The desired channel count of the output audio. */ numberOfChannels?: number; @@ -144,8 +146,8 @@ export type ConversionAudioOptions = { /** The desired output audio codec. */ codec?: AudioCodec; /** The desired bitrate of the output audio. */ - bitrate?: AudioEncodingConfig['bitrate']; - /** When true, audio will always be re-encoded instead of directly copying over the encoded samples. */ + bitrate?: number | Quality; + /** When `true`, audio will always be re-encoded instead of directly copying over the encoded samples. */ forceTranscode?: boolean; }; @@ -246,9 +248,41 @@ const validateAudioOptions = (audioOptions: ConversionAudioOptions | undefined) const FALLBACK_NUMBER_OF_CHANNELS = 2; const FALLBACK_SAMPLE_RATE = 48000; +/** + * An input track that was discarded (excluded) from a {@link Conversion} alongside the discard reason. + * @group Conversion + * @public + */ +export type DiscardedTrack = { + /** The track that was discarded. */ + track: InputTrack; + /** + * The reason for discarding the track. + * + * - `'discarded_by_user'`: You discarded this track by setting `discard: true`. + * - `'max_track_count_reached'`: The output had no more room for another track. + * - `'max_track_count_of_type_reached'`: The output had no more room for another track of this type, or the output + * doesn't support this track type at all. + * - `'unknown_source_codec'`: We don't know the codec of the input track and therefore don't know what to do + * with it. + * - `'undecodable_source_codec'`: The input track's codec is known, but we are unable to decode it. + * - `'no_encodable_target_codec'`: We can't find a codec that we are able to encode and that can be contained + * within the output format. This reason can be hit if the environment doesn't support the necessary encoders, or if + * you requested a codec that cannot be contained within the output format. + */ + reason: + | 'discarded_by_user' + | 'max_track_count_reached' + | 'max_track_count_of_type_reached' + | 'unknown_source_codec' + | 'undecodable_source_codec' + | 'no_encodable_target_codec'; +}; + /** * Represents a media file conversion process, used to convert one media file into another. In addition to conversion, * this class can be used to resize and rotate video, resample audio, drop tracks, or trim to a specific time range. + * @group Conversion * @public */ export class Conversion { @@ -298,7 +332,7 @@ export class Conversion { /** * A callback that is fired whenever the conversion progresses. Returns a number between 0 and 1, indicating the * completion of the conversion. Note that a progress of 1 doesn't necessarily mean the conversion is complete; - * the conversion is complete once `execute` resolves. + * the conversion is complete once `execute()` resolves. * * In order for progress to be computed, this property must be set before `execute` is called. */ @@ -311,18 +345,7 @@ export class Conversion { /** The list of tracks that are included in the output file. */ readonly utilizedTracks: InputTrack[] = []; /** The list of tracks from the input file that have been discarded, alongside the discard reason. */ - readonly discardedTracks: { - /** The track that was discarded. */ - track: InputTrack; - /** The reason for discarding the track. */ - reason: - | 'discarded_by_user' - | 'max_track_count_reached' - | 'max_track_count_of_type_reached' - | 'unknown_source_codec' - | 'undecodable_source_codec' - | 'no_encodable_target_codec'; - }[] = []; + readonly discardedTracks: DiscardedTrack[] = []; /** Initializes a new conversion process without starting the conversion. */ static async init(options: ConversionOptions) { @@ -332,6 +355,7 @@ export class Conversion { return conversion; } + /** Creates a new Conversion instance (duh). */ private constructor(options: ConversionOptions) { if (!options || typeof options !== 'object') { throw new TypeError('options must be an object.'); diff --git a/src/custom-coder.ts b/src/custom-coder.ts index 4c3bccf..ddc422b 100644 --- a/src/custom-coder.ts +++ b/src/custom-coder.ts @@ -7,12 +7,14 @@ */ import { AudioCodec, VideoCodec } from './codec'; +import { MaybePromise } from './misc'; import { EncodedPacket } from './packet'; import { AudioSample, VideoSample } from './sample'; /** * Base class for custom video decoders. To add your own custom video decoder, extend this class, implement the - * abstract methods and static `supports` method, and register the decoder using `registerDecoder`. + * abstract methods and static `supports` method, and register the decoder using {@link registerDecoder}. + * @group Custom coders * @public */ export abstract class CustomVideoDecoder { @@ -23,25 +25,26 @@ export abstract class CustomVideoDecoder { /** The callback to call when a decoded VideoSample is available. */ readonly onSample!: (sample: VideoSample) => unknown; - /** Returns true iff the decoder can decode the given codec configuration. */ + /** Returns true if and only if the decoder can decode the given codec configuration. */ // eslint-disable-next-line @typescript-eslint/no-unused-vars static supports(codec: VideoCodec, config: VideoDecoderConfig): boolean { return false; } /** Called after decoder creation; can be used for custom initialization logic. */ - abstract init(): Promise | void; + abstract init(): MaybePromise; /** Decodes the provided encoded packet. */ - abstract decode(packet: EncodedPacket): Promise | void; + abstract decode(packet: EncodedPacket): MaybePromise; /** Decodes all remaining packets and then resolves. */ - abstract flush(): Promise | void; + abstract flush(): MaybePromise; /** Called when the decoder is no longer needed and its resources can be freed. */ - abstract close(): Promise | void; + abstract close(): MaybePromise; } /** * Base class for custom audio decoders. To add your own custom audio decoder, extend this class, implement the - * abstract methods and static `supports` method, and register the decoder using `registerDecoder`. + * abstract methods and static `supports` method, and register the decoder using {@link registerDecoder}. + * @group Custom coders * @public */ export abstract class CustomAudioDecoder { @@ -52,25 +55,26 @@ export abstract class CustomAudioDecoder { /** The callback to call when a decoded AudioSample is available. */ readonly onSample!: (sample: AudioSample) => unknown; - /** Returns true iff the decoder can decode the given codec configuration. */ + /** Returns true if and only if the decoder can decode the given codec configuration. */ // eslint-disable-next-line @typescript-eslint/no-unused-vars static supports(codec: AudioCodec, config: AudioDecoderConfig): boolean { return false; } /** Called after decoder creation; can be used for custom initialization logic. */ - abstract init(): Promise | void; + abstract init(): MaybePromise; /** Decodes the provided encoded packet. */ - abstract decode(packet: EncodedPacket): Promise | void; + abstract decode(packet: EncodedPacket): MaybePromise; /** Decodes all remaining packets and then resolves. */ - abstract flush(): Promise | void; + abstract flush(): MaybePromise; /** Called when the decoder is no longer needed and its resources can be freed. */ - abstract close(): Promise | void; + abstract close(): MaybePromise; } /** * Base class for custom video encoders. To add your own custom video encoder, extend this class, implement the - * abstract methods and static `supports` method, and register the encoder using `registerEncoder`. + * abstract methods and static `supports` method, and register the encoder using {@link registerEncoder}. + * @group Custom coders * @public */ export abstract class CustomVideoEncoder { @@ -81,25 +85,26 @@ export abstract class CustomVideoEncoder { /** The callback to call when an EncodedPacket is available. */ readonly onPacket!: (packet: EncodedPacket, meta?: EncodedVideoChunkMetadata) => unknown; - /** Returns true iff the encoder can encode the given codec configuration. */ + /** Returns true if and only if the encoder can encode the given codec configuration. */ // eslint-disable-next-line @typescript-eslint/no-unused-vars static supports(codec: VideoCodec, config: VideoEncoderConfig): boolean { return false; } /** Called after encoder creation; can be used for custom initialization logic. */ - abstract init(): Promise | void; + abstract init(): MaybePromise; /** Encodes the provided video sample. */ - abstract encode(videoSample: VideoSample, options: VideoEncoderEncodeOptions): Promise | void; + abstract encode(videoSample: VideoSample, options: VideoEncoderEncodeOptions): MaybePromise; /** Encodes all remaining video samples and then resolves. */ - abstract flush(): Promise | void; + abstract flush(): MaybePromise; /** Called when the encoder is no longer needed and its resources can be freed. */ - abstract close(): Promise | void; + abstract close(): MaybePromise; } /** * Base class for custom audio encoders. To add your own custom audio encoder, extend this class, implement the - * abstract methods and static `supports` method, and register the encoder using `registerEncoder`. + * abstract methods and static `supports` method, and register the encoder using {@link registerEncoder}. + * @group Custom coders * @public */ export abstract class CustomAudioEncoder { @@ -110,20 +115,20 @@ export abstract class CustomAudioEncoder { /** The callback to call when an EncodedPacket is available. */ readonly onPacket!: (packet: EncodedPacket, meta?: EncodedAudioChunkMetadata) => unknown; - /** Returns true iff the encoder can encode the given codec configuration. */ + /** Returns true if and only if the encoder can encode the given codec configuration. */ // eslint-disable-next-line @typescript-eslint/no-unused-vars static supports(codec: AudioCodec, config: AudioEncoderConfig): boolean { return false; } /** Called after encoder creation; can be used for custom initialization logic. */ - abstract init(): Promise | void; + abstract init(): MaybePromise; /** Encodes the provided audio sample. */ - abstract encode(audioSample: AudioSample): Promise | void; + abstract encode(audioSample: AudioSample): MaybePromise; /** Encodes all remaining audio samples and then resolves. */ - abstract flush(): Promise | void; + abstract flush(): MaybePromise; /** Called when the encoder is no longer needed and its resources can be freed. */ - abstract close(): Promise | void; + abstract close(): MaybePromise; } export const customVideoDecoders: typeof CustomVideoDecoder[] = []; @@ -134,6 +139,7 @@ export const customAudioEncoders: typeof CustomAudioEncoder[] = []; /** * Registers a custom video or audio decoder. Registered decoders will automatically be used for decoding whenever * possible. + * @group Custom coders * @public */ export const registerDecoder = (decoder: typeof CustomVideoDecoder | typeof CustomAudioDecoder) => { @@ -163,6 +169,7 @@ export const registerDecoder = (decoder: typeof CustomVideoDecoder | typeof Cust /** * Registers a custom video or audio encoder. Registered encoders will automatically be used for encoding whenever * possible. + * @group Custom coders * @public */ export const registerEncoder = (encoder: typeof CustomVideoEncoder | typeof CustomAudioEncoder) => { diff --git a/src/encode.ts b/src/encode.ts index dd596c5..0eb26da 100644 --- a/src/encode.ts +++ b/src/encode.ts @@ -16,7 +16,6 @@ import { inferCodecFromCodecString, MediaCodec, PCM_AUDIO_CODECS, - Quality, SUBTITLE_CODECS, SubtitleCodec, VIDEO_CODECS, @@ -27,13 +26,14 @@ import { EncodedPacket } from './packet'; /** * Configuration object that controls video encoding. Can be used to set codec, quality, and more. + * @group Encoding * @public */ export type VideoEncodingConfig = { /** The video codec that should be used for encoding the video samples (frames). */ codec: VideoCodec; /** - * The target bitrate for the encoded video, in bits per second. Alternatively, a subjective Quality can + * The target bitrate for the encoded video, in bits per second. Alternatively, a subjective {@link Quality} can * be provided. */ bitrate: number | Quality; @@ -44,14 +44,14 @@ export type VideoEncodingConfig = { */ keyFrameInterval?: number; /** - * Video frames may change size overtime. This field controls the behavior in case this happens. + * Video frames may change size over time. This field controls the behavior in case this happens. * - * - 'deny' (default) will throw an error, requiring all frames to have the exact same dimensions. - * - 'passThrough' will allow the change and directly pass the frame to the encoder. - * - 'fill' will stretch the image to fill the entire original box, potentially altering aspect ratio. - * - 'contain' will contain the entire image within the originalbox while preserving aspect ratio. This may lead to - * letterboxing. - * - 'cover' will scale the image until the entire original box is filled, while preserving aspect ratio. + * - `'deny'` (default) will throw an error, requiring all frames to have the exact same dimensions. + * - `'passThrough'` will allow the change and directly pass the frame to the encoder. + * - `'fill'` will stretch the image to fill the entire original box, potentially altering aspect ratio. + * - `'contain'` will contain the entire image within the original box while preserving aspect ratio. This may lead + * to letterboxing. + * - `'cover'` will scale the image until the entire original box is filled, while preserving aspect ratio. * * The "original box" refers to the dimensions of the first encoded frame. */ @@ -59,7 +59,10 @@ export type VideoEncodingConfig = { /** Called for each successfully encoded packet. Both the packet and the encoding metadata are passed. */ onEncodedPacket?: (packet: EncodedPacket, meta: EncodedVideoChunkMetadata | undefined) => unknown; - /** Called when the internal encoder config, as used by the WebCodecs API, is created. */ + /** + * Called when the internal [encoder config](https://www.w3.org/TR/webcodecs/#video-encoder-config), as used by the + * WebCodecs API, is created. + */ onEncoderConfig?: (config: VideoEncoderConfig) => unknown; } & VideoEncodingAdditionalOptions; @@ -100,30 +103,33 @@ export const validateVideoEncodingConfig = (config: VideoEncodingConfig) => { /** * Additional options that control audio encoding. + * @group Encoding * @public */ export type VideoEncodingAdditionalOptions = { /** Configures the bitrate mode. */ bitrateMode?: 'constant' | 'variable'; /** The latency mode used by the encoder; controls the performance-quality tradeoff. */ - latencyMode?: VideoEncoderConfig['latencyMode']; + latencyMode?: 'quality' | 'realtime'; /** * The full codec string as specified in the WebCodecs Codec Registry. This string must match the codec * specified in `codec`. When not set, a fitting codec string will be constructed automatically by the library. */ fullCodecString?: string; - /** A hint that configures the hardware acceleration method of this codec. This is best left on 'no-preference'. */ - hardwareAcceleration?: VideoEncoderConfig['hardwareAcceleration']; + /** + * A hint that configures the hardware acceleration method of this codec. This is best left on `'no-preference'`. + */ + hardwareAcceleration?: 'no-preference' | 'prefer-hardware' | 'prefer-software'; /** * An encoding scalability mode identifier as defined by * [WebRTC-SVC](https://w3c.github.io/webrtc-svc/#scalabilitymodes*). */ - scalabilityMode?: VideoEncoderConfig['scalabilityMode']; + scalabilityMode?: string; /** * An encoding video content hint as defined by * [mst-content-hint](https://w3c.github.io/mst-content-hint/#video-content-hints). */ - contentHint?: VideoEncoderConfig['contentHint']; + contentHint?: string; }; export const validateVideoEncodingAdditionalOptions = (codec: VideoCodec, options: VideoEncodingAdditionalOptions) => { @@ -194,20 +200,24 @@ export const buildVideoEncoderConfig = (options: { /** * Configuration object that controls audio encoding. Can be used to set codec, quality, and more. + * @group Encoding * @public */ export type AudioEncodingConfig = { /** The audio codec that should be used for encoding the audio samples. */ codec: AudioCodec; /** - * The target bitrate for the encoded audio, in bits per second. Alternatively, a subjective Quality can + * The target bitrate for the encoded audio, in bits per second. Alternatively, a subjective {@link Quality} can * be provided. Required for compressed audio codecs, unused for PCM codecs. */ bitrate?: number | Quality; /** Called for each successfully encoded packet. Both the packet and the encoding metadata are passed. */ onEncodedPacket?: (packet: EncodedPacket, meta: EncodedAudioChunkMetadata | undefined) => unknown; - /** Called when the internal encoder config, as used by the WebCodecs API, is created. */ + /** + * Called when the internal [encoder config](https://www.w3.org/TR/webcodecs/#audio-encoder-config), as used by the + * WebCodecs API, is created. + */ onEncoderConfig?: (config: AudioEncoderConfig) => unknown; } & AudioEncodingAdditionalOptions; @@ -243,6 +253,7 @@ export const validateAudioEncodingConfig = (config: AudioEncodingConfig) => { /** * Additional options that control audio encoding. + * @group Encoding * @public */ export type AudioEncodingAdditionalOptions = { @@ -296,8 +307,119 @@ export const buildAudioEncoderConfig = (options: { }; }; +/** + * Represents a subjective media quality level. + * @group Encoding + * @public + */ +export class Quality { + /** @internal */ + _factor: number; + + /** @internal */ + constructor(factor: number) { + this._factor = factor; + } + + /** @internal */ + _toVideoBitrate(codec: VideoCodec, width: number, height: number) { + const pixels = width * height; + + const codecEfficiencyFactors = { + avc: 1.0, // H.264/AVC (baseline) + hevc: 0.6, // H.265/HEVC (~40% more efficient than AVC) + vp9: 0.6, // Similar to HEVC + av1: 0.4, // ~60% more efficient than AVC + vp8: 1.2, // Slightly less efficient than AVC + }; + + const referencePixels = 1920 * 1080; + const referenceBitrate = 3000000; + const scaleFactor = Math.pow(pixels / referencePixels, 0.95); // Slight non-linear scaling + const baseBitrate = referenceBitrate * scaleFactor; + + const codecAdjustedBitrate = baseBitrate * codecEfficiencyFactors[codec]; + const finalBitrate = codecAdjustedBitrate * this._factor; + + return Math.ceil(finalBitrate / 1000) * 1000; + } + + /** @internal */ + _toAudioBitrate(codec: AudioCodec) { + if ((PCM_AUDIO_CODECS as readonly string[]).includes(codec) || codec === 'flac') { + return undefined; + } + + const baseRates = { + aac: 128000, // 128kbps base for AAC + opus: 64000, // 64kbps base for Opus + mp3: 160000, // 160kbps base for MP3 + vorbis: 64000, // 64kbps base for Vorbis + }; + + const baseBitrate = baseRates[codec as keyof typeof baseRates]; + if (!baseBitrate) { + throw new Error(`Unhandled codec: ${codec}`); + } + + let finalBitrate = baseBitrate * this._factor; + + if (codec === 'aac') { + // AAC only works with specific bitrates, let's find the closest + const validRates = [96000, 128000, 160000, 192000]; + finalBitrate = validRates.reduce((prev, curr) => + Math.abs(curr - finalBitrate) < Math.abs(prev - finalBitrate) ? curr : prev, + ); + } else if (codec === 'opus' || codec === 'vorbis') { + finalBitrate = Math.max(6000, finalBitrate); + } else if (codec === 'mp3') { + const validRates = [ + 8000, 16000, 24000, 32000, 40000, 48000, 64000, 80000, + 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, + ]; + finalBitrate = validRates.reduce((prev, curr) => + Math.abs(curr - finalBitrate) < Math.abs(prev - finalBitrate) ? curr : prev, + ); + } + + return Math.round(finalBitrate / 1000) * 1000; + } +} + +/** + * Represents a very low media quality. + * @group Encoding + * @public + */ +export const QUALITY_VERY_LOW = new Quality(0.3); +/** + * Represents a low media quality. + * @group Encoding + * @public + */ +export const QUALITY_LOW = new Quality(0.6); +/** + * Represents a medium media quality. + * @group Encoding + * @public + */ +export const QUALITY_MEDIUM = new Quality(1); +/** + * Represents a high media quality. + * @group Encoding + * @public + */ +export const QUALITY_HIGH = new Quality(2); +/** + * Represents a very high media quality. + * @group Encoding + * @public + */ +export const QUALITY_VERY_HIGH = new Quality(4); + /** * Checks if the browser is able to encode the given codec. + * @group Encoding * @public */ export const canEncode = (codec: MediaCodec) => { @@ -314,18 +436,24 @@ export const canEncode = (codec: MediaCodec) => { /** * Checks if the browser is able to encode the given video codec with the given parameters. + * @group Encoding * @public */ -export const canEncodeVideo = async (codec: VideoCodec, { - width = 1280, - height = 720, - bitrate = 1e6, - ...restOptions -}: { - width?: number; - height?: number; - bitrate?: number | Quality; -} & VideoEncodingAdditionalOptions = {}) => { +export const canEncodeVideo = async ( + codec: VideoCodec, + options: { + width?: number; + height?: number; + bitrate?: number | Quality; + } & VideoEncodingAdditionalOptions = {}, +) => { + const { + width = 1280, + height = 720, + bitrate = 1e6, + ...restOptions + } = options; + if (!VIDEO_CODECS.includes(codec)) { return false; } @@ -377,18 +505,24 @@ export const canEncodeVideo = async (codec: VideoCodec, { /** * Checks if the browser is able to encode the given audio codec with the given parameters. + * @group Encoding * @public */ -export const canEncodeAudio = async (codec: AudioCodec, { - numberOfChannels = 2, - sampleRate = 48000, - bitrate = 128e3, - ...restOptions -}: { - numberOfChannels?: number; - sampleRate?: number; - bitrate?: number | Quality; -} & AudioEncodingAdditionalOptions = {}) => { +export const canEncodeAudio = async ( + codec: AudioCodec, + options: { + numberOfChannels?: number; + sampleRate?: number; + bitrate?: number | Quality; + } & AudioEncodingAdditionalOptions = {}, +) => { + const { + numberOfChannels = 2, + sampleRate = 48000, + bitrate = 128e3, + ...restOptions + } = options; + if (!AUDIO_CODECS.includes(codec)) { return false; } @@ -442,6 +576,7 @@ export const canEncodeAudio = async (codec: AudioCodec, { /** * Checks if the browser is able to encode the given subtitle codec. + * @group Encoding * @public */ export const canEncodeSubtitles = async (codec: SubtitleCodec) => { @@ -454,6 +589,7 @@ export const canEncodeSubtitles = async (codec: SubtitleCodec) => { /** * Returns the list of all media codecs that can be encoded by the browser. + * @group Encoding * @public */ export const getEncodableCodecs = async (): Promise => { @@ -468,10 +604,11 @@ export const getEncodableCodecs = async (): Promise => { /** * Returns the list of all video codecs that can be encoded by the browser. + * @group Encoding * @public */ export const getEncodableVideoCodecs = async ( - checkedCodecs = VIDEO_CODECS as unknown as VideoCodec[], + checkedCodecs: VideoCodec[] = VIDEO_CODECS as unknown as VideoCodec[], options?: { width?: number; height?: number; @@ -484,10 +621,11 @@ export const getEncodableVideoCodecs = async ( /** * Returns the list of all audio codecs that can be encoded by the browser. + * @group Encoding * @public */ export const getEncodableAudioCodecs = async ( - checkedCodecs = AUDIO_CODECS as unknown as AudioCodec[], + checkedCodecs: AudioCodec[] = AUDIO_CODECS as unknown as AudioCodec[], options?: { numberOfChannels?: number; sampleRate?: number; @@ -500,10 +638,11 @@ export const getEncodableAudioCodecs = async ( /** * Returns the list of all subtitle codecs that can be encoded by the browser. + * @group Encoding * @public */ export const getEncodableSubtitleCodecs = async ( - checkedCodecs = SUBTITLE_CODECS as unknown as SubtitleCodec[], + checkedCodecs: SubtitleCodec[] = SUBTITLE_CODECS as unknown as SubtitleCodec[], ): Promise => { const bools = await Promise.all(checkedCodecs.map(canEncodeSubtitles)); return checkedCodecs.filter((_, i) => bools[i]); @@ -511,6 +650,7 @@ export const getEncodableSubtitleCodecs = async ( /** * Returns the first video codec from the given list that can be encoded by the browser. + * @group Encoding * @public */ export const getFirstEncodableVideoCodec = async ( @@ -532,6 +672,7 @@ export const getFirstEncodableVideoCodec = async ( /** * Returns the first audio codec from the given list that can be encoded by the browser. + * @group Encoding * @public */ export const getFirstEncodableAudioCodec = async ( @@ -553,6 +694,7 @@ export const getFirstEncodableAudioCodec = async ( /** * Returns the first subtitle codec from the given list that can be encoded by the browser. + * @group Encoding * @public */ export const getFirstEncodableSubtitleCodec = async ( diff --git a/src/index.ts b/src/index.ts index da1f4ce..9bd1991 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,61 +16,53 @@ export { VideoTrackMetadata, AudioTrackMetadata, SubtitleTrackMetadata, - TrackType, - ALL_TRACK_TYPES, } from './output'; export { OutputFormat, + AdtsOutputFormat, + AdtsOutputFormatOptions, IsobmffOutputFormat, - Mp4OutputFormat, - MovOutputFormat, IsobmffOutputFormatOptions, MkvOutputFormat, MkvOutputFormatOptions, - WebMOutputFormat, - WebMOutputFormatOptions, + MovOutputFormat, Mp3OutputFormat, Mp3OutputFormatOptions, - WavOutputFormat, - WavOutputFormatOptions, + Mp4OutputFormat, OggOutputFormat, OggOutputFormatOptions, - AdtsOutputFormat, - AdtsOutputFormatOptions, - TrackCountLimits, + WavOutputFormat, + WavOutputFormatOptions, + WebMOutputFormat, + WebMOutputFormatOptions, InclusiveIntegerRange, + TrackCountLimits, } from './output-format'; export { MediaSource, VideoSource, - EncodedVideoPacketSource, - VideoSampleSource, - CanvasSource, - MediaStreamVideoTrackSource, AudioSource, - EncodedAudioPacketSource, - AudioSampleSource, - AudioBufferSource, - MediaStreamAudioTrackSource, SubtitleSource, + AudioBufferSource, + AudioSampleSource, + CanvasSource, + EncodedAudioPacketSource, + EncodedVideoPacketSource, + MediaStreamAudioTrackSource, + MediaStreamVideoTrackSource, TextSubtitleSource, + VideoSampleSource, } from './media-source'; export { - VIDEO_CODECS, + MediaCodec, VideoCodec, + AudioCodec, + SubtitleCodec, + VIDEO_CODECS, + AUDIO_CODECS, PCM_AUDIO_CODECS, NON_PCM_AUDIO_CODECS, - AUDIO_CODECS, - AudioCodec, SUBTITLE_CODECS, - SubtitleCodec, - MediaCodec, - Quality, - QUALITY_VERY_LOW, - QUALITY_LOW, - QUALITY_MEDIUM, - QUALITY_HIGH, - QUALITY_VERY_HIGH, } from './codec'; export { VideoEncodingConfig, @@ -88,69 +80,110 @@ export { getFirstEncodableVideoCodec, getFirstEncodableAudioCodec, getFirstEncodableSubtitleCodec, + Quality, + QUALITY_VERY_LOW, + QUALITY_LOW, + QUALITY_MEDIUM, + QUALITY_HIGH, + QUALITY_VERY_HIGH, } from './encode'; -export { Target, BufferTarget, StreamTarget, StreamTargetChunk, StreamTargetOptions, NullTarget } from './target'; -export { Rotation, AnyIterable, SetRequired, MaybePromise } from './misc'; +export { + Target, + BufferTarget, + NullTarget, + StreamTarget, + StreamTargetOptions, + StreamTargetChunk, +} from './target'; +export { + AnyIterable, + MaybePromise, + Rotation, + SetRequired, +} from './misc'; +export { + TrackType, + ALL_TRACK_TYPES, +} from './output'; export { Source, - BufferSource, BlobSource, BlobSourceOptions, - UrlSource, - UrlSourceOptions, + BufferSource, FilePathSource, FilePathSourceOptions, StreamSource, StreamSourceOptions, ReadableStreamSource, ReadableStreamSourceOptions, + UrlSource, + UrlSourceOptions, } from './source'; export { InputFormat, + AdtsInputFormat, IsobmffInputFormat, - Mp4InputFormat, - QuickTimeInputFormat, MatroskaInputFormat, - WebMInputFormat, Mp3InputFormat, - WaveInputFormat, + Mp4InputFormat, OggInputFormat, + QuickTimeInputFormat, + WaveInputFormat, + WebMInputFormat, ALL_FORMATS, - MP4, - QTFF, + ADTS, MATROSKA, - WEBM, MP3, - WAVE, + MP4, OGG, + QTFF, + WAVE, + WEBM, } from './input-format'; -export { Input, InputOptions } from './input'; -export { InputTrack, InputVideoTrack, InputAudioTrack, PacketStats } from './input-track'; -export { EncodedPacket, PacketType } from './packet'; export { - VideoSample, - VideoSampleInit, + Input, + InputOptions, +} from './input'; +export { + InputTrack, + InputVideoTrack, + InputAudioTrack, + PacketStats, +} from './input-track'; +export { + EncodedPacket, + PacketType, +} from './packet'; +export { AudioSample, AudioSampleInit, AudioSampleCopyToOptions, + VideoSample, + VideoSampleInit, } from './sample'; export { - PacketRetrievalOptions, - EncodedPacketSink, - BaseMediaSampleSink, - VideoSampleSink, - CanvasSinkOptions, - CanvasSink, - WrappedCanvas, - AudioSampleSink, AudioBufferSink, + AudioSampleSink, + BaseMediaSampleSink, + CanvasSink, + CanvasSinkOptions, + EncodedPacketSink, + PacketRetrievalOptions, + VideoSampleSink, WrappedAudioBuffer, + WrappedCanvas, } from './media-sink'; -export { Conversion, ConversionOptions, ConversionVideoOptions, ConversionAudioOptions } from './conversion'; +export { + Conversion, + ConversionOptions, + ConversionVideoOptions, + ConversionAudioOptions, + DiscardedTrack, +} from './conversion'; export { CustomVideoDecoder, - CustomAudioDecoder, CustomVideoEncoder, + CustomAudioDecoder, CustomAudioEncoder, registerDecoder, registerEncoder, diff --git a/src/input-format.ts b/src/input-format.ts index 1d4f17b..72f2ffc 100644 --- a/src/input-format.ts +++ b/src/input-format.ts @@ -31,6 +31,7 @@ import { readAscii } from './reader'; /** * Base class representing an input media file format. + * @group Input formats * @public */ export abstract class InputFormat { @@ -48,6 +49,7 @@ export abstract class InputFormat { /** * Format representing files compatible with the ISO base media file format (ISOBMFF), like MP4 or MOV files. + * @group Input formats * @public */ export abstract class IsobmffInputFormat extends InputFormat { @@ -75,6 +77,10 @@ export abstract class IsobmffInputFormat extends InputFormat { /** * MPEG-4 Part 14 (MP4) file format. + * + * Do not instantiate this class; use the {@link MP4} singleton instead. + * + * @group Input formats * @public */ export class Mp4InputFormat extends IsobmffInputFormat { @@ -95,6 +101,10 @@ export class Mp4InputFormat extends IsobmffInputFormat { /** * QuickTime File Format (QTFF), often called MOV. + * + * Do not instantiate this class; use the {@link QTFF} singleton instead. + * + * @group Input formats * @public */ export class QuickTimeInputFormat extends IsobmffInputFormat { @@ -115,6 +125,10 @@ export class QuickTimeInputFormat extends IsobmffInputFormat { /** * Matroska file format. + * + * Do not instantiate this class; use the {@link MATROSKA} singleton instead. + * + * @group Input formats * @public */ export class MatroskaInputFormat extends InputFormat { @@ -211,6 +225,10 @@ export class MatroskaInputFormat extends InputFormat { /** * WebM file format, based on Matroska. + * + * Do not instantiate this class; use the {@link WEBM} singleton instead. + * + * @group Input formats * @public */ export class WebMInputFormat extends MatroskaInputFormat { @@ -230,6 +248,10 @@ export class WebMInputFormat extends MatroskaInputFormat { /** * MP3 file format. + * + * Do not instantiate this class; use the {@link MP3} singleton instead. + * + * @group Input formats * @public */ export class Mp3InputFormat extends InputFormat { @@ -294,6 +316,10 @@ export class Mp3InputFormat extends InputFormat { /** * WAVE file format, based on RIFF. + * + * Do not instantiate this class; use the {@link WAVE} singleton instead. + * + * @group Input formats * @public */ export class WaveInputFormat extends InputFormat { @@ -330,6 +356,10 @@ export class WaveInputFormat extends InputFormat { /** * Ogg file format. + * + * Do not instantiate this class; use the {@link OGG} singleton instead. + * + * @group Input formats * @public */ export class OggInputFormat extends InputFormat { @@ -358,6 +388,10 @@ export class OggInputFormat extends InputFormat { /** * ADTS file format. + * + * Do not instantiate this class; use the {@link ADTS} singleton instead. + * + * @group Input formats * @public */ export class AdtsInputFormat extends InputFormat { @@ -402,41 +436,49 @@ export class AdtsInputFormat extends InputFormat { /** * MP4 input format singleton. + * @group Input formats * @public */ export const MP4 = new Mp4InputFormat(); /** * QuickTime File Format input format singleton. + * @group Input formats * @public */ export const QTFF = new QuickTimeInputFormat(); /** * Matroska input format singleton. + * @group Input formats * @public */ export const MATROSKA = new MatroskaInputFormat(); /** * WebM input format singleton. + * @group Input formats * @public */ export const WEBM = new WebMInputFormat(); /** * MP3 input format singleton. + * @group Input formats * @public */ export const MP3 = new Mp3InputFormat(); /** * WAVE input format singleton. + * @group Input formats * @public */ export const WAVE = new WaveInputFormat(); /** * Ogg input format singleton. + * @group Input formats * @public */ export const OGG = new OggInputFormat(); /** * ADTS input format singleton. + * @group Input formats * @public */ export const ADTS = new AdtsInputFormat(); @@ -444,6 +486,7 @@ export const ADTS = new AdtsInputFormat(); /** * List of all input format singletons. If you don't need to support all input formats, you should specify the * formats individually for better tree shaking. + * @group Input formats * @public */ export const ALL_FORMATS: InputFormat[] = [MP4, QTFF, MATROSKA, WEBM, WAVE, OGG, MP3, ADTS]; diff --git a/src/input-track.ts b/src/input-track.ts index bb5ec42..b093b0d 100644 --- a/src/input-track.ts +++ b/src/input-track.ts @@ -16,6 +16,7 @@ import { EncodedPacket, PacketType } from './packet'; /** * Contains aggregate statistics about the encoded packets of a track. + * @group Input files & tracks * @public */ export type PacketStats = { @@ -46,6 +47,7 @@ export interface InputTrackBacking { /** * Represents a media track in an input file. + * @group Input files & tracks * @public */ export abstract class InputTrack { @@ -71,12 +73,12 @@ export abstract class InputTrack { */ abstract determinePacketType(packet: EncodedPacket): Promise; - /** Returns true iff this track is a video track. */ + /** Returns true if and only if this track is a video track. */ isVideoTrack(): this is InputVideoTrack { return this instanceof InputVideoTrack; } - /** Returns true iff this track is an audio track. */ + /** Returns true if and only if this track is an audio track. */ isAudioTrack(): this is InputAudioTrack { return this instanceof InputAudioTrack; } @@ -92,17 +94,19 @@ export abstract class InputTrack { * * This field can be used to determine the codec of a track in case Mediabunny doesn't know that codec. * - * - For ISOBMFF files, this field returns the name of the Sample Description Box (e.g. 'avc1'). - * - For Matroska files, this field returns the value of the CodecID element. - * - For WAVE files, this field returns the value of the format tag in the 'fmt ' chunk. - * - For ADTS files, this field contains the MPEG-4 Audio Object Type. + * - For ISOBMFF files, this field returns the name of the Sample Description Box (e.g. `'avc1'`). + * - For Matroska files, this field returns the value of the `CodecID` element. + * - For WAVE files, this field returns the value of the format tag in the `'fmt '` chunk. + * - For ADTS files, this field contains the `MPEG-4 Audio Object Type`. * - In all other cases, this field is `null`. */ get internalCodecId() { return this._backing.getInternalCodecId(); } - /** The ISO 639-2/T language code for this track. If the language is unknown, this field is 'und' (undetermined). */ + /** + * The ISO 639-2/T language code for this track. If the language is unknown, this field is `'und'` (undetermined). + */ get languageCode() { return this._backing.getLanguageCode(); } @@ -189,6 +193,7 @@ export interface InputVideoTrackBacking extends InputTrackBacking { /** * Represents a video track in an input file. + * @group Input files & tracks * @public */ export class InputVideoTrack extends InputTrack { @@ -206,7 +211,7 @@ export class InputVideoTrack extends InputTrack { return 'video'; } - get codec() { + get codec(): VideoCodec | null { return this._backing.getCodec(); } @@ -252,8 +257,8 @@ export class InputVideoTrack extends InputTrack { } /** - * Returns the decoder configuration for decoding the track's packets using a VideoDecoder. Returns null if the - * track's codec is unknown. + * Returns the [decoder configuration](https://www.w3.org/TR/webcodecs/#video-decoder-config) for decoding the + * track's packets using a VideoDecoder. Returns null if the track's codec is unknown. */ getDecoderConfig() { return this._backing.getDecoderConfig(); @@ -315,6 +320,7 @@ export interface InputAudioTrackBacking extends InputTrackBacking { /** * Represents an audio track in an input file. + * @group Input files & tracks * @public */ export class InputAudioTrack extends InputTrack { @@ -347,8 +353,8 @@ export class InputAudioTrack extends InputTrack { } /** - * Returns the decoder configuration for decoding the track's packets using an AudioDecoder. Returns null if the - * track's codec is unknown. + * Returns the [decoder configuration](https://www.w3.org/TR/webcodecs/#audio-decoder-config) for decoding the + * track's packets using an AudioDecoder. Returns null if the track's codec is unknown. */ getDecoderConfig() { return this._backing.getDecoderConfig(); diff --git a/src/input.ts b/src/input.ts index 1136214..4d815e5 100644 --- a/src/input.ts +++ b/src/input.ts @@ -14,6 +14,7 @@ import { Source } from './source'; /** * The options for creating an Input object. + * @group Input files & tracks * @public */ export type InputOptions = { @@ -25,6 +26,7 @@ export type InputOptions = { /** * Represents an input media file. This is the root object from which all media read operations start. + * @group Input files & tracks * @public */ export class Input { @@ -39,6 +41,10 @@ export class Input { /** @internal */ _reader: Reader; + /** + * Creates a new input file from the specified options. No reading operations will be performed until methods are + * called on this instance. + */ constructor(options: InputOptions) { if (!options || typeof options !== 'object') { throw new TypeError('options must be an object.'); @@ -81,9 +87,9 @@ export class Input { } /** - * Returns the format of the input file. You can compare this result directly to the InputFormat singletons or use - * `instanceof` checks for subset-aware logic (for example, `format instanceof MatroskaInputFormat` is true for - * both MKV and WebM). + * Returns the format of the input file. You can compare this result directly to the {@link InputFormat} singletons + * or use `instanceof` checks for subset-aware logic (for example, `format instanceof MatroskaInputFormat` is true + * for both MKV and WebM). */ async getFormat() { await this._getDemuxer(); @@ -112,18 +118,18 @@ export class Input { return tracks.filter(x => x.isVideoTrack()); } - /** Returns the primary video track of this input file, or null if there are no video tracks. */ - async getPrimaryVideoTrack() { - const tracks = await this.getTracks(); - return tracks.find(x => x.isVideoTrack()) ?? null; - } - /** Returns the list of all audio tracks of this input file. */ async getAudioTracks() { const tracks = await this.getTracks(); return tracks.filter(x => x.isAudioTrack()); } + /** Returns the primary video track of this input file, or null if there are no video tracks. */ + async getPrimaryVideoTrack() { + const tracks = await this.getTracks(); + return tracks.find(x => x.isVideoTrack()) ?? null; + } + /** Returns the primary audio track of this input file, or null if there are no audio tracks. */ async getPrimaryAudioTrack() { const tracks = await this.getTracks(); diff --git a/src/media-sink.ts b/src/media-sink.ts index b419e46..643f259 100644 --- a/src/media-sink.ts +++ b/src/media-sink.ts @@ -32,11 +32,12 @@ import { AudioSample, VideoSample } from './sample'; /** * Additional options for controlling packet retrieval. + * @group Media sinks * @public */ export type PacketRetrievalOptions = { /** - * When set to true, only packet metadata (like timestamp) will be retrieved - the actual packet data will not + * When set to `true`, only packet metadata (like timestamp) will be retrieved - the actual packet data will not * be loaded. */ metadataOnly?: boolean; @@ -97,12 +98,14 @@ const maybeFixPacketType = ( /** * Sink for retrieving encoded packets from an input track. + * @group Media sinks * @public */ export class EncodedPacketSink { /** @internal */ _track: InputTrack; + /** Creates a new {@link EncodedPacketSink} for the given {@link InputTrack}. */ constructor(track: InputTrack) { if (!(track instanceof InputTrack)) { throw new TypeError('track must be an InputTrack.'); @@ -342,6 +345,7 @@ abstract class DecoderWrapper< /** * Base class for decoded media sample sinks. + * @group Media sinks * @public */ export abstract class BaseMediaSampleSink< @@ -894,12 +898,14 @@ class VideoDecoderWrapper extends DecoderWrapper { /** * A sink that retrieves decoded video samples (video frames) from a video track. + * @group Media sinks * @public */ export class VideoSampleSink extends BaseMediaSampleSink { /** @internal */ _videoTrack: InputVideoTrack; + /** Creates a new {@link VideoSampleSink} for the given {@link InputVideoTrack}. */ constructor(videoTrack: InputVideoTrack) { if (!(videoTrack instanceof InputVideoTrack)) { throw new TypeError('videoTrack must be an InputVideoTrack.'); @@ -978,6 +984,7 @@ export class VideoSampleSink extends BaseMediaSampleSink { /** * A canvas with additional timing information (timestamp & duration). + * @group Media sinks * @public */ export type WrappedCanvas = { @@ -991,6 +998,7 @@ export type WrappedCanvas = { /** * Options for constructing a CanvasSink. + * @group Media sinks * @public */ export type CanvasSinkOptions = { @@ -1007,10 +1015,10 @@ export type CanvasSinkOptions = { /** * The fitting algorithm in case both width and height are set. * - * - 'fill' will stretch the image to fill the entire box, potentially altering aspect ratio. - * - 'contain' will contain the entire image within the box while preserving aspect ratio. This may lead to + * - `'fill'` will stretch the image to fill the entire box, potentially altering aspect ratio. + * - `'contain'` will contain the entire image within the box while preserving aspect ratio. This may lead to * letterboxing. - * - 'cover' will scale the image until the entire box is filled, while preserving aspect ratio. + * - `'cover'` will scale the image until the entire box is filled, while preserving aspect ratio. */ fit?: 'fill' | 'contain' | 'cover'; /** @@ -1032,7 +1040,9 @@ export type CanvasSinkOptions = { * directly retrieving frames, as it comes with common preprocessing steps such as resizing or applying rotation * metadata. * - * This sink will yield HTMLCanvasElements when in a DOM context, and OffscreenCanvases otherwise. + * This sink will yield `HTMLCanvasElement`s when in a DOM context, and `OffscreenCanvas`es otherwise. + * + * @group Media sinks * @public */ export class CanvasSink { @@ -1053,6 +1063,7 @@ export class CanvasSink { /** @internal */ _nextCanvasIndex = 0; + /** Creates a new {@link CanvasSink} for the given {@link InputVideoTrack}. */ constructor(videoTrack: InputVideoTrack, options: CanvasSinkOptions = {}) { if (!(videoTrack instanceof InputVideoTrack)) { throw new TypeError('videoTrack must be an InputVideoTrack.'); @@ -1509,12 +1520,14 @@ class PcmAudioDecoderWrapper extends DecoderWrapper { /** * Sink for retrieving decoded audio samples from an audio track. + * @group Media sinks * @public */ export class AudioSampleSink extends BaseMediaSampleSink { /** @internal */ _audioTrack: InputAudioTrack; + /** Creates a new {@link AudioSampleSink} for the given {@link InputAudioTrack}. */ constructor(audioTrack: InputAudioTrack) { if (!(audioTrack instanceof InputAudioTrack)) { throw new TypeError('audioTrack must be an InputAudioTrack.'); @@ -1595,6 +1608,7 @@ export class AudioSampleSink extends BaseMediaSampleSink { /** * An AudioBuffer with additional timing information (timestamp & duration). + * @group Media sinks * @public */ export type WrappedAudioBuffer = { @@ -1607,14 +1621,17 @@ export type WrappedAudioBuffer = { }; /** - * A sink that retrieves decoded audio samples from an audio track and converts them to AudioBuffers. This is often - * more useful than directly retrieving audio samples, as AudioBuffers can be directly used with the Web Audio API. + * A sink that retrieves decoded audio samples from an audio track and converts them to `AudioBuffer` instances. This is + * often more useful than directly retrieving audio samples, as audio buffers can be directly used with the + * Web Audio API. + * @group Media sinks * @public */ export class AudioBufferSink { /** @internal */ _audioSampleSink: AudioSampleSink; + /** Creates a new {@link AudioBufferSink} for the given {@link InputAudioTrack}. */ constructor(audioTrack: InputAudioTrack) { if (!(audioTrack instanceof InputAudioTrack)) { throw new TypeError('audioTrack must be an InputAudioTrack.'); diff --git a/src/media-source.ts b/src/media-source.ts index d8730e1..89b5485 100644 --- a/src/media-source.ts +++ b/src/media-source.ts @@ -41,6 +41,7 @@ import { /** * Base class for media sources. Media sources are used to add media samples to an output file. + * @group Media sources * @public */ export abstract class MediaSource { @@ -131,6 +132,7 @@ export abstract class MediaSource { /** * Base class for video sources - sources for video tracks. + * @group Media sources * @public */ export abstract class VideoSource extends MediaSource { @@ -139,6 +141,7 @@ export abstract class VideoSource extends MediaSource { /** @internal */ _codec: VideoCodec; + /** Internal constructor. */ constructor(codec: VideoCodec) { super(); @@ -152,9 +155,11 @@ export abstract class VideoSource extends MediaSource { /** * The most basic video source; can be used to directly pipe encoded packets into the output file. + * @group Media sources * @public */ export class EncodedVideoPacketSource extends VideoSource { + /** Creates a new {@link EncodedVideoPacketSource} whose packets are encoded using `codec`. */ constructor(codec: VideoCodec) { super(codec); } @@ -458,12 +463,17 @@ class VideoEncoderWrapper { /** * This source can be used to add raw, unencoded video samples (frames) to an output video track. These frames will * automatically be encoded and then piped into the output. + * @group Media sources * @public */ export class VideoSampleSource extends VideoSource { /** @internal */ private _encoder: VideoEncoderWrapper; + /** + * Creates a new {@link VideoSampleSource} whose samples are encoded according to the specified + * {@link VideoEncodingConfig}. + */ constructor(encodingConfig: VideoEncodingConfig) { validateVideoEncodingConfig(encodingConfig); @@ -493,7 +503,8 @@ export class VideoSampleSource extends VideoSource { /** * This source can be used to add video frames to the output track from a fixed canvas element. Since canvases are often - * used for rendering, this source provides a convenient wrapper around VideoSampleSource. + * used for rendering, this source provides a convenient wrapper around {@link VideoSampleSource}. + * @group Media sources * @public */ export class CanvasSource extends VideoSource { @@ -502,6 +513,10 @@ export class CanvasSource extends VideoSource { /** @internal */ private _canvas: HTMLCanvasElement | OffscreenCanvas; + /** + * Creates a new {@link CanvasSource} from a canvas element or `OffscreenCanvas` whose samples are encoded + * according to the specified {@link VideoEncodingConfig}. + */ constructor(canvas: HTMLCanvasElement | OffscreenCanvas, encodingConfig: VideoEncodingConfig) { if ( !(typeof HTMLCanvasElement !== 'undefined' && canvas instanceof HTMLCanvasElement) @@ -544,10 +559,12 @@ export class CanvasSource extends VideoSource { } /** - * Video source that encodes the frames of a MediaStreamVideoTrack and pipes them into the output. This is useful for - * capturing live or real-time data such as webcams or screen captures. Frames will automatically start being captured - * once the connected Output is started, and will keep being captured until the Output is finalized or this source - * is closed. + * Video source that encodes the frames of a + * [`MediaStreamVideoTrack`](https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack) and pipes them into the + * output. This is useful for capturing live or real-time data such as webcams or screen captures. Frames will + * automatically start being captured once the connected {@link Output} is started, and will keep being captured until + * the {@link Output} is finalized or this source is closed. + * @group Media sources * @public */ export class MediaStreamVideoTrackSource extends VideoSource { @@ -572,6 +589,11 @@ export class MediaStreamVideoTrackSource extends VideoSource { return this._promiseWithResolvers.promise; } + /** + * Creates a new {@link MediaStreamVideoTrackSource} from a + * [`MediaStreamVideoTrack`](https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack), which will pull + * video samples from the stream in real time and encode them according to {@link VideoEncodingConfig}. + */ constructor(track: MediaStreamVideoTrack, encodingConfig: VideoEncodingConfig) { if (!(track instanceof MediaStreamTrack) || track.kind !== 'video') { throw new TypeError('track must be a video MediaStreamTrack.'); @@ -726,6 +748,7 @@ export class MediaStreamVideoTrackSource extends VideoSource { /** * Base class for audio sources - sources for audio tracks. + * @group Media sources * @public */ export abstract class AudioSource extends MediaSource { @@ -734,6 +757,7 @@ export abstract class AudioSource extends MediaSource { /** @internal */ _codec: AudioCodec; + /** Internal constructor. */ constructor(codec: AudioCodec) { super(); @@ -747,9 +771,11 @@ export abstract class AudioSource extends MediaSource { /** * The most basic audio source; can be used to directly pipe encoded packets into the output file. + * @group Media sources * @public */ export class EncodedAudioPacketSource extends AudioSource { + /** Creates a new {@link EncodedAudioPacketSource} whose packets are encoded using `codec`. */ constructor(codec: AudioCodec) { super(codec); } @@ -1171,12 +1197,17 @@ class AudioEncoderWrapper { /** * This source can be used to add raw, unencoded audio samples to an output audio track. These samples will * automatically be encoded and then piped into the output. + * @group Media sources * @public */ export class AudioSampleSource extends AudioSource { /** @internal */ private _encoder: AudioEncoderWrapper; + /** + * Creates a new {@link AudioSampleSource} whose samples are encoded according to the specified + * {@link AudioEncodingConfig}. + */ constructor(encodingConfig: AudioEncodingConfig) { validateAudioEncodingConfig(encodingConfig); @@ -1207,6 +1238,7 @@ export class AudioSampleSource extends AudioSource { /** * This source can be used to add audio data from an AudioBuffer to the output track. This is useful when working with * the Web Audio API. + * @group Media sources * @public */ export class AudioBufferSource extends AudioSource { @@ -1215,6 +1247,10 @@ export class AudioBufferSource extends AudioSource { /** @internal */ private _accumulatedTime = 0; + /** + * Creates a new {@link AudioBufferSource} whose `AudioBuffer` instances are encoded according to the specified + * {@link AudioEncodingConfig}. + */ constructor(encodingConfig: AudioEncodingConfig) { validateAudioEncodingConfig(encodingConfig); @@ -1250,10 +1286,12 @@ export class AudioBufferSource extends AudioSource { } /** - * Audio source that encodes the data of a MediaStreamAudioTrack and pipes it into the output. This is useful for - * capturing live or real-time audio such as microphones or audio from other media elements. Audio will automatically - * start being captured once the connected Output is started, and will keep being captured until the Output is - * finalized or this source is closed. + * Audio source that encodes the data of a + * [`MediaStreamAudioTrack`](https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack) and pipes it into the + * output. This is useful for capturing live or real-time audio such as microphones or audio from other media elements. + * Audio will automatically start being captured once the connected {@link Output} is started, and will keep being + * captured until the {@link Output} is finalized or this source is closed. + * @group Media sources * @public */ export class MediaStreamAudioTrackSource extends AudioSource { @@ -1278,6 +1316,10 @@ export class MediaStreamAudioTrackSource extends AudioSource { return this._promiseWithResolvers.promise; } + /** + * Creates a new {@link MediaStreamAudioTrackSource} from a `MediaStreamAudioTrack`, which will pull audio samples + * from the stream in real time and encode them according to {@link AudioEncodingConfig}. + */ constructor(track: MediaStreamAudioTrack, encodingConfig: AudioEncodingConfig) { if (!(track instanceof MediaStreamTrack) || track.kind !== 'audio') { throw new TypeError('track must be an audio MediaStreamTrack.'); @@ -1572,6 +1614,7 @@ const sendMessageToMediaStreamTrackProcessorWorker = ( /** * Base class for subtitle sources - sources for subtitle tracks. + * @group Media sources * @public */ export abstract class SubtitleSource extends MediaSource { @@ -1580,6 +1623,7 @@ export abstract class SubtitleSource extends MediaSource { /** @internal */ _codec: SubtitleCodec; + /** Internal constructor. */ constructor(codec: SubtitleCodec) { super(); @@ -1593,12 +1637,14 @@ export abstract class SubtitleSource extends MediaSource { /** * This source can be used to add subtitles from a subtitle text file. + * @group Media sources * @public */ export class TextSubtitleSource extends SubtitleSource { /** @internal */ private _parser: SubtitleParser; + /** Creates a new {@link TextSubtitleSource} where added text chunks are in the specified `codec`. */ constructor(codec: SubtitleCodec) { super(codec); diff --git a/src/misc.ts b/src/misc.ts index 7d2b1c8..99aac7a 100644 --- a/src/misc.ts +++ b/src/misc.ts @@ -14,6 +14,7 @@ export function assert(x: unknown): asserts x { /** * Represents a clockwise rotation in degrees. + * @group Miscellaneous * @public */ export type Rotation = 0 | 90 | 180 | 270; @@ -355,6 +356,7 @@ export const findLastIndex = (arr: T[], predicate: (x: T) => boolean) => { /** * Sync or async iterable. + * @group Miscellaneous * @public */ export type AnyIterable = @@ -501,6 +503,7 @@ export const SECOND_TO_MICROSECOND_FACTOR = 1e6 * (1 + Number.EPSILON); /** * Sets all keys K of T to be required. + * @group Miscellaneous * @public */ export type SetRequired = T & Required>; @@ -630,6 +633,7 @@ export const isSafari = () => { /** * T or a promise that resolves to T. + * @group Miscellaneous * @public */ export type MaybePromise = T | Promise; diff --git a/src/output-format.ts b/src/output-format.ts index 0f5e228..c1a1a17 100644 --- a/src/output-format.ts +++ b/src/output-format.ts @@ -29,6 +29,7 @@ import { WaveMuxer } from './wave/wave-muxer'; /** * Specifies an inclusive range of integers. + * @group Miscellaneous * @public */ export type InclusiveIntegerRange = { @@ -40,6 +41,7 @@ export type InclusiveIntegerRange = { /** * Specifies the number of tracks (for each track type and in total) that an output format supports. + * @group Output formats * @public */ export type TrackCountLimits = { @@ -51,6 +53,7 @@ export type TrackCountLimits = { /** * Base class representing an output media file format. + * @group Output formats * @public */ export abstract class OutputFormat { @@ -97,6 +100,7 @@ export abstract class OutputFormat { /** * ISOBMFF-specific output options. + * @group Output formats * @public */ export type IsobmffOutputFormatOptions = { @@ -168,12 +172,14 @@ export type IsobmffOutputFormatOptions = { /** * Format representing files compatible with the ISO base media file format (ISOBMFF), like MP4 or MOV files. + * @group Output formats * @public */ export abstract class IsobmffOutputFormat extends OutputFormat { /** @internal */ _options: IsobmffOutputFormatOptions; + /** Internal constructor. */ constructor(options: IsobmffOutputFormatOptions = {}) { if (!options || typeof options !== 'object') { throw new TypeError('options must be an object.'); @@ -225,10 +231,16 @@ export abstract class IsobmffOutputFormat extends OutputFormat { } /** - * MPEG-4 Part 14 (MP4) file format. Supports all codecs except PCM audio codecs. + * MPEG-4 Part 14 (MP4) file format. Supports most codecs. + * @group Output formats * @public */ export class Mp4OutputFormat extends IsobmffOutputFormat { + /** Creates a new {@link Mp4OutputFormat} configured with the specified `options`. */ + constructor(options?: IsobmffOutputFormatOptions) { + super(options); + } + /** @internal */ get _name() { return 'MP4'; @@ -273,9 +285,15 @@ export class Mp4OutputFormat extends IsobmffOutputFormat { /** * QuickTime File Format (QTFF), often called MOV. Supports all video and audio codecs, but not subtitle codecs. + * @group Output formats * @public */ export class MovOutputFormat extends IsobmffOutputFormat { + /** Creates a new {@link MovOutputFormat} configured with the specified `options`. */ + constructor(options?: IsobmffOutputFormatOptions) { + super(options); + } + /** @internal */ get _name() { return 'MOV'; @@ -308,6 +326,7 @@ export class MovOutputFormat extends IsobmffOutputFormat { /** * Matroska-specific output options. + * @group Output formats * @public */ export type MkvOutputFormatOptions = { @@ -353,12 +372,14 @@ export type MkvOutputFormatOptions = { /** * Matroska file format. + * @group Output formats * @public */ export class MkvOutputFormat extends OutputFormat { /** @internal */ _options: MkvOutputFormatOptions; + /** Creates a new {@link MkvOutputFormat} configured with the specified `options`. */ constructor(options: MkvOutputFormatOptions = {}) { if (!options || typeof options !== 'object') { throw new TypeError('options must be an object.'); @@ -431,15 +452,22 @@ export class MkvOutputFormat extends OutputFormat { /** * WebM-specific output options. + * @group Output formats * @public */ export type WebMOutputFormatOptions = MkvOutputFormatOptions; /** * WebM file format, based on Matroska. + * @group Output formats * @public */ export class WebMOutputFormat extends MkvOutputFormat { + /** Creates a new {@link WebMOutputFormat} configured with the specified `options`. */ + constructor(options?: MkvOutputFormatOptions) { + super(options); + } + override getSupportedCodecs(): MediaCodec[] { return [ ...VIDEO_CODECS.filter(codec => ['vp8', 'vp9', 'av1'].includes(codec)), @@ -473,12 +501,13 @@ export class WebMOutputFormat extends MkvOutputFormat { /** * MP3-specific output options. + * @group Output formats * @public */ export type Mp3OutputFormatOptions = { /** * Controls whether the Xing header, which contains additional metadata as well as an index, is written to the start - * of the MP3 file. When disabled, the writing process becomes append-only. Defaults to true. + * of the MP3 file. When disabled, the writing process becomes append-only. Defaults to `true`. */ xingHeader?: boolean; @@ -493,12 +522,14 @@ export type Mp3OutputFormatOptions = { /** * MP3 file format. + * @group Output formats * @public */ export class Mp3OutputFormat extends OutputFormat { /** @internal */ _options: Mp3OutputFormatOptions; + /** Creates a new {@link Mp3OutputFormat} configured with the specified `options`. */ constructor(options: Mp3OutputFormatOptions = {}) { if (!options || typeof options !== 'object') { throw new TypeError('options must be an object.'); @@ -553,12 +584,13 @@ export class Mp3OutputFormat extends OutputFormat { /** * WAVE-specific output options. + * @group Output formats * @public */ export type WavOutputFormatOptions = { /** - * When enabled, an RF64 file be written, allowing for file sizes to exceed 4 GiB, which is otherwise not possible - * for regular WAVE files. + * When enabled, an RF64 file will be written, allowing for file sizes to exceed 4 GiB, which is otherwise not + * possible for regular WAVE files. */ large?: boolean; @@ -571,12 +603,14 @@ export type WavOutputFormatOptions = { /** * WAVE file format, based on RIFF. + * @group Output formats * @public */ export class WavOutputFormat extends OutputFormat { /** @internal */ _options: WavOutputFormatOptions; + /** Creates a new {@link WavOutputFormat} configured with the specified `options`. */ constructor(options: WavOutputFormatOptions = {}) { if (!options || typeof options !== 'object') { throw new TypeError('options must be an object.'); @@ -635,6 +669,7 @@ export class WavOutputFormat extends OutputFormat { /** * Ogg-specific output options. + * @group Output formats * @public */ export type OggOutputFormatOptions = { @@ -643,19 +678,21 @@ export type OggOutputFormatOptions = { * * @param data - The raw bytes. * @param position - The byte offset of the data in the file. - * @param source - The media source backing the page's logical bitstream (track). + * @param source - The {@link MediaSource} backing the page's logical bitstream (track). */ onPage?: (data: Uint8Array, position: number, source: MediaSource) => unknown; }; /** * Ogg file format. + * @group Output formats * @public */ export class OggOutputFormat extends OutputFormat { /** @internal */ _options: OggOutputFormatOptions; + /** Creates a new {@link OggOutputFormat} configured with the specified `options`. */ constructor(options: OggOutputFormatOptions = {}) { if (!options || typeof options !== 'object') { throw new TypeError('options must be an object.'); @@ -709,6 +746,7 @@ export class OggOutputFormat extends OutputFormat { /** * ADTS-specific output options. + * @group Output formats * @public */ export type AdtsOutputFormatOptions = { @@ -723,12 +761,14 @@ export type AdtsOutputFormatOptions = { /** * ADTS file format. + * @group Output formats * @public */ export class AdtsOutputFormat extends OutputFormat { /** @internal */ _options: AdtsOutputFormatOptions; + /** Creates a new {@link AdtsOutputFormat} configured with the specified `options`. */ constructor(options: AdtsOutputFormatOptions = {}) { if (!options || typeof options !== 'object') { throw new TypeError('options must be an object.'); diff --git a/src/output.ts b/src/output.ts index 92c0d0f..24e71d7 100644 --- a/src/output.ts +++ b/src/output.ts @@ -15,6 +15,7 @@ import { Writer } from './writer'; /** * The options for creating an Output object. + * @group Output files * @public */ export type OutputOptions< @@ -29,11 +30,13 @@ export type OutputOptions< /** * List of all track types. + * @group Miscellaneous * @public */ export const ALL_TRACK_TYPES = ['video', 'audio', 'subtitle'] as const; /** * Union type of all track types. + * @group Miscellaneous * @public */ export type TrackType = typeof ALL_TRACK_TYPES[number]; @@ -62,6 +65,7 @@ export type OutputSubtitleTrack = OutputTrack & { type: 'subtitle' }; /** * Base track metadata, applicable to all tracks. + * @group Output files * @public */ export type BaseTrackMetadata = { @@ -73,6 +77,7 @@ export type BaseTrackMetadata = { /** * Additional metadata for video tracks. + * @group Output files * @public */ export type VideoTrackMetadata = BaseTrackMetadata & { @@ -87,11 +92,13 @@ export type VideoTrackMetadata = BaseTrackMetadata & { }; /** * Additional metadata for audio tracks. + * @group Output files * @public */ export type AudioTrackMetadata = BaseTrackMetadata & {}; /** * Additional metadata for subtitle tracks. + * @group Output files * @public */ export type SubtitleTrackMetadata = BaseTrackMetadata & {}; @@ -110,6 +117,7 @@ const validateBaseTrackMetadata = (metadata: BaseTrackMetadata) => { /** * Main class orchestrating the creation of a new media file. + * @group Output files * @public */ export class Output< @@ -138,6 +146,10 @@ export class Output< /** @internal */ _mutex = new AsyncMutex(); + /** + * Creates a new instance of {@link Output} which can then be used to create a new media file according to the + * specified {@link OutputOptions}. + */ constructor(options: OutputOptions) { if (!options || typeof options !== 'object') { throw new TypeError('options must be an object.'); diff --git a/src/packet.ts b/src/packet.ts index ddbdc56..5221267 100644 --- a/src/packet.ts +++ b/src/packet.ts @@ -13,13 +13,17 @@ export const PLACEHOLDER_DATA = new Uint8Array(0); /** * The type of a packet. Key packets can be decoded without previous packets, while delta packets depend on previous * packets. + * @group Packets * @public */ export type PacketType = 'key' | 'delta'; /** - * Represents an encoded chunk of media. Mainly used as an expressive wrapper around WebCodecs API's EncodedVideoChunk - * and EncodedAudioChunk, but can also be used standalone. + * Represents an encoded chunk of media. Mainly used as an expressive wrapper around WebCodecs API's + * [`EncodedVideoChunk`](https://developer.mozilla.org/en-US/docs/Web/API/EncodedVideoChunk) and + * [`EncodedAudioChunk`](https://developer.mozilla.org/en-US/docs/Web/API/EncodedAudioChunk), but can also be used + * standalone. + * @group Packets * @public */ export class EncodedPacket { @@ -29,6 +33,7 @@ export class EncodedPacket { */ readonly byteLength: number; + /** Creates a new {@link EncodedPacket} from raw bytes and timing information. */ constructor( /** The encoded data of this packet. */ public readonly data: Uint8Array, diff --git a/src/sample.ts b/src/sample.ts index b59b332..5f407cd 100644 --- a/src/sample.ts +++ b/src/sample.ts @@ -19,10 +19,14 @@ import { /** * Metadata used for VideoSample initialization. + * @group Samples * @public */ export type VideoSampleInit = { - /** The internal pixel format in which the frame is stored. */ + /** + * The internal pixel format in which the frame is stored. + * [See pixel formats](https://developer.mozilla.org/en-US/docs/Web/API/VideoFrame/format) + */ format?: VideoPixelFormat; /** The width of the frame in pixels. */ codedWidth?: number; @@ -40,7 +44,8 @@ export type VideoSampleInit = { /** * Represents a raw, unencoded video sample (frame). Mainly used as an expressive wrapper around WebCodecs API's - * VideoFrame, but can also be used standalone. + * [`VideoFrame`](https://developer.mozilla.org/en-US/docs/Web/API/VideoFrame), but can also be used standalone. + * @group Samples * @public */ export class VideoSample { @@ -49,7 +54,10 @@ export class VideoSample { /** @internal */ _closed: boolean = false; - /** The internal pixel format in which the frame is stored. */ + /** + * The internal pixel format in which the frame is stored. + * [See pixel formats](https://developer.mozilla.org/en-US/docs/Web/API/VideoFrame/format) + */ readonly format!: VideoPixelFormat | null; /** The width of the frame in pixels. */ readonly codedWidth!: number; @@ -87,8 +95,24 @@ export class VideoSample { return Math.trunc(SECOND_TO_MICROSECOND_FACTOR * this.duration); } + /** + * Creates a new {@link VideoSample} from a + * [`VideoFrame`](https://developer.mozilla.org/en-US/docs/Web/API/VideoFrame). This is essentially a near zero-cost + * wrapper around `VideoFrame`. The sample's metadata is optionally refined using the data specified in `init`. + */ constructor(data: VideoFrame, init?: VideoSampleInit); + /** + * Creates a new {@link VideoSample} from a + * [`CanvasImageSource`](https://udn.realityripple.com/docs/Web/API/CanvasImageSource), similar to the + * [`VideoFrame`](https://developer.mozilla.org/en-US/docs/Web/API/VideoFrame) constructor. When `VideoFrame` is + * available, this is simply a wrapper around its constructor. If not, it will copy the source's image data to an + * internal canvas for later use. + */ constructor(data: CanvasImageSource, init: SetRequired); + /** + * Creates a new {@link VideoSample} from raw pixel data specified in `data`. Additional metadata must be provided + * in `init`. + */ constructor( data: BufferSource, init: SetRequired @@ -544,10 +568,10 @@ export class VideoSample { /** * Controls the fitting algorithm. * - * - 'fill' will stretch the image to fill the entire box, potentially altering aspect ratio. - * - 'contain' will contain the entire image within the box while preserving aspect ratio. This may lead to + * - `'fill'` will stretch the image to fill the entire box, potentially altering aspect ratio. + * - `'contain'` will contain the entire image within the box while preserving aspect ratio. This may lead to * letterboxing. - * - 'cover' will scale the image until the entire box is filled, while preserving aspect ratio. + * - `'cover'` will scale the image until the entire box is filled, while preserving aspect ratio. */ fit: 'fill' | 'contain' | 'cover'; /** A way to override rotation. Defaults to the rotation of the sample. */ @@ -596,7 +620,8 @@ export class VideoSample { } /** - * Converts this video sample to a CanvasImageSource for drawing to a canvas. + * Converts this video sample to a + * [`CanvasImageSource`](https://udn.realityripple.com/docs/Web/API/CanvasImageSource) for drawing to a canvas. * * You must use the value returned by this method immediately, as any VideoFrame created internally will * automatically be closed in the next microtask. @@ -660,12 +685,15 @@ const AUDIO_SAMPLE_FORMATS = new Set( /** * Metadata used for AudioSample initialization. + * @group Samples * @public */ export type AudioSampleInit = { /** The audio data for this sample. */ data: AllowSharedBufferSource; - /** The audio sample format. */ + /** + * The audio sample format. [See sample formats](https://developer.mozilla.org/en-US/docs/Web/API/AudioData/format) + */ format: AudioSampleFormat; /** The number of audio channels. */ numberOfChannels: number; @@ -677,6 +705,7 @@ export type AudioSampleInit = { /** * Options used for copying audio sample data. + * @group Samples * @public */ export type AudioSampleCopyToOptions = { @@ -684,7 +713,10 @@ export type AudioSampleCopyToOptions = { * The index identifying the plane to copy from. This must be 0 if using a non-planar (interleaved) output format. */ planeIndex: number; - /** The output format for the destination data. Defaults to the AudioSample's format. */ + /** + * The output format for the destination data. Defaults to the AudioSample's format. + * [See sample formats](https://developer.mozilla.org/en-US/docs/Web/API/AudioData/format) + */ format?: AudioSampleFormat; /** An offset into the source plane data indicating which frame to begin copying from. Defaults to 0. */ frameOffset?: number; @@ -696,8 +728,9 @@ export type AudioSampleCopyToOptions = { }; /** - * Represents a raw, unencoded audio sample. Mainly used as an expressive wrapper around WebCodecs API's AudioData, - * but can also be used standalone. + * Represents a raw, unencoded audio sample. Mainly used as an expressive wrapper around WebCodecs API's + * [`AudioData`](https://developer.mozilla.org/en-US/docs/Web/API/AudioData), but can also be used standalone. + * @group Samples * @public */ export class AudioSample { @@ -706,7 +739,10 @@ export class AudioSample { /** @internal */ _closed: boolean = false; - /** The audio sample format. */ + /** + * The audio sample format. + * [See sample formats](https://developer.mozilla.org/en-US/docs/Web/API/AudioData/format) + */ readonly format: AudioSampleFormat; /** The audio sample rate in hertz. */ readonly sampleRate: number; @@ -716,7 +752,7 @@ export class AudioSample { readonly numberOfFrames: number; /** The number of audio channels. */ readonly numberOfChannels: number; - /** The timestamp of the sample in seconds. */ + /** The duration of the sample in seconds. */ readonly duration: number; /** * The presentation timestamp of the sample in seconds. May be negative. Samples with negative end timestamps should @@ -734,6 +770,11 @@ export class AudioSample { return Math.trunc(SECOND_TO_MICROSECOND_FACTOR * this.duration); } + /** + * Creates a new {@link AudioSample}, either from an existing + * [`AudioData`](https://developer.mozilla.org/en-US/docs/Web/API/AudioData) or from raw bytes specified in + * {@link AudioSampleInit}. + */ constructor(init: AudioData | AudioSampleInit) { if (isAudioData(init)) { if (init.format === null) { diff --git a/src/source.ts b/src/source.ts index 1b3d41a..ae24aaf 100644 --- a/src/source.ts +++ b/src/source.ts @@ -16,6 +16,7 @@ import { promiseWithResolvers, retriedFetch, toDataView, + toUint8Array, } from './misc'; export type ReadResult = { @@ -27,6 +28,7 @@ export type ReadResult = { /** * The source base class, representing a resource from which bytes can be read. + * @group Input sources * @public */ export abstract class Source { @@ -69,6 +71,7 @@ export abstract class Source { /** * A source backed by an ArrayBuffer or ArrayBufferView, with the entire file held in memory. + * @group Input sources * @public */ export class BufferSource extends Source { @@ -79,15 +82,16 @@ export class BufferSource extends Source { /** @internal */ _onreadCalled = false; - constructor(buffer: ArrayBuffer | Uint8Array) { - if (!(buffer instanceof ArrayBuffer) && !(buffer instanceof Uint8Array)) { - throw new TypeError('buffer must be an ArrayBuffer or Uint8Array.'); + /** Creates a new {@link BufferSource} backed the specified `ArrayBuffer` or `ArrayBufferView`. */ + constructor(buffer: ArrayBuffer | ArrayBufferView) { + if (!(buffer instanceof ArrayBuffer) && !ArrayBuffer.isView(buffer)) { + throw new TypeError('buffer must be an ArrayBuffer or ArrayBufferView.'); } super(); - this._bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer); - this._view = toDataView(this._bytes); + this._bytes = toUint8Array(buffer); + this._view = toDataView(buffer); } /** @internal */ @@ -112,7 +116,8 @@ export class BufferSource extends Source { } /** - * Options for BlobSource. + * Options for {@link BlobSource}. + * @group Input sources * @public */ export type BlobSourceOptions = { @@ -121,7 +126,10 @@ export type BlobSourceOptions = { }; /** - * A source backed by a Blob. Since Files are also Blobs, this is the source to use when reading files off the disk. + * A source backed by a [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob). Since a + * [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File) is also a `Blob`, this is the source to use when + * reading files off the disk. + * @group Input sources * @public */ export class BlobSource extends Source { @@ -130,6 +138,10 @@ export class BlobSource extends Source { /** @internal */ _orchestrator: ReadOrchestrator; + /** + * Creates a new {@link BlobSource} backed by the specified + * [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob). + */ constructor(blob: Blob, options: BlobSourceOptions = {}) { if (!(blob instanceof Blob)) { throw new TypeError('blob must be a Blob.'); @@ -203,13 +215,14 @@ export class BlobSource extends Source { const URL_SOURCE_MIN_LOAD_AMOUNT = 0.5 * 2 ** 20; // 0.5 MiB /** - * Options for UrlSource. + * Options for {@link UrlSource}. + * @group Input sources * @public */ export type UrlSourceOptions = { /** - * The RequestInit used by the Fetch API. Can be used to further control the requests, such as setting - * custom headers. + * The [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) used by the Fetch API. Can be + * used to further control the requests, such as setting custom headers. */ requestInit?: RequestInit; @@ -228,6 +241,7 @@ export type UrlSourceOptions = { /** * A source backed by a URL. This is useful for reading data from the network. Requests will be made using an optimized * reading and prefetching pattern to minimize request count and latency. + * @group Input sources * @public */ export class UrlSource extends Source { @@ -245,6 +259,7 @@ export class UrlSource extends Source { abortController: AbortController; }>(); + /** Creates a new {@link UrlSource} backed by the resource at the specified URL. */ constructor( url: string | URL, options: UrlSourceOptions = {}, @@ -476,7 +491,8 @@ export class UrlSource extends Source { } /** - * Options for FilePathSource. + * Options for {@link FilePathSource}. + * @group Input sources * @public */ export type FilePathSourceOptions = { @@ -486,12 +502,14 @@ export type FilePathSourceOptions = { /** * A source backed by a path to a file. Intended for server-side usage in Node, Bun, or Deno. + * @group Input sources * @public */ export class FilePathSource extends Source { /** @internal */ _streamSource: StreamSource; + /** Creates a new {@link FilePathSource} backed by the file at the specified file path. */ constructor(filePath: string, options: BlobSourceOptions = {}) { if (typeof filePath !== 'string') { throw new TypeError('filePath must be a string.'); @@ -544,7 +562,8 @@ export class FilePathSource extends Source { } /** - * Options for defining a StreamSource. + * Options for defining a {@link StreamSource}. + * @group Input sources * @public */ export type StreamSourceOptions = { @@ -564,7 +583,7 @@ export type StreamSourceOptions = { maxCacheSize?: number; /** - * Specifies the prefetch profile that the reader should use with this source. A prefetch propfile specifies the + * Specifies the prefetch profile that the reader should use with this source. A prefetch profile specifies the * pattern with which bytes outside of the requested range are preloaded to reduce latency for future reads. * * - `'none'` (default): No prefetching; only the data needed in the moment is requested. @@ -579,6 +598,7 @@ export type StreamSourceOptions = { /** * A general-purpose, callback-driven source that can get its data from anywhere. + * @group Input sources * @public */ export class StreamSource extends Source { @@ -587,6 +607,7 @@ export class StreamSource extends Source { /** @internal */ _orchestrator: ReadOrchestrator; + /** Creates a new {@link StreamSource} whose behavior is specified by `options`. */ constructor(options: StreamSourceOptions) { if (!options || typeof options !== 'object') { throw new TypeError('options must be an object.'); @@ -718,7 +739,8 @@ type ReadableStreamSourcePendingSlice = { }; /** - * Options for ReadableStreamSource. + * Options for {@link ReadableStreamSource}. + * @group Input sources * @public */ export type ReadableStreamSourceOptions = { @@ -727,16 +749,17 @@ export type ReadableStreamSourceOptions = { }; /** - * A source backed by a `ReadableStream` of `Uint8Array`, representing an append-only byte stream of unknown - * length. This is the source to use for incrementally streaming in input files that are still being constructed and - * whose size we don't yet know, like for example the output chunks of - * [MediaRecorder](https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder). + * A source backed by a [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) of + * `Uint8Array`, representing an append-only byte stream of unknown length. This is the source to use for incrementally + * streaming in input files that are still being constructed and whose size we don't yet know, like for example the + * output chunks of [MediaRecorder](https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder). * * This source is *unsized*, meaning calls to `.getSize()` will throw and readers are more limited due to the * lack of random file access. You should only use this source with sequential access patterns, such as reading all * packets from start to end. This source does not work well with random access patterns unless you increase its * max cache size. * + * @group Input sources * @public */ export class ReadableStreamSource extends Source { @@ -761,6 +784,7 @@ export class ReadableStreamSource extends Source { /** @internal */ _pulling = false; + /** Creates a new {@link ReadableStreamSource} backed by the specified `ReadableStream`. */ constructor(stream: ReadableStream, options: ReadableStreamSourceOptions = {}) { if (!(stream instanceof ReadableStream)) { throw new TypeError('stream must be a ReadableStream.'); diff --git a/src/target.ts b/src/target.ts index 03366bd..3893be7 100644 --- a/src/target.ts +++ b/src/target.ts @@ -11,6 +11,7 @@ import { Output } from './output'; /** * Base class for targets, specifying where output files are written. + * @group Output targets * @public */ export abstract class Target { @@ -32,10 +33,11 @@ export abstract class Target { /** * A target that writes data directly into an ArrayBuffer in memory. Great for performance, but not suitable for very * large files. The buffer will be available once the output has been finalized. + * @group Output targets * @public */ export class BufferTarget extends Target { - /** Stores the final output buffer. Until the output is finalized, this will be null. */ + /** Stores the final output buffer. Until the output is finalized, this will be `null`. */ buffer: ArrayBuffer | null = null; /** @internal */ @@ -45,7 +47,8 @@ export class BufferTarget extends Target { } /** - * A data chunk for StreamTarget. + * A data chunk for {@link StreamTarget}. + * @group Output targets * @public */ export type StreamTargetChunk = { @@ -58,7 +61,8 @@ export type StreamTargetChunk = { }; /** - * Options for StreamTarget. + * Options for {@link StreamTarget}. + * @group Output targets * @public */ export type StreamTargetOptions = { @@ -73,9 +77,12 @@ export type StreamTargetOptions = { }; /** - * This target writes data to a WritableStream, making it a general-purpose target for writing data anywhere. It is - * also compatible with FileSystemWritableFileStream for use with the File System Access API. The WritableStream can - * also apply backpressure, which will propagate to the output and throttle the encoders. + * This target writes data to a [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream), + * making it a general-purpose target for writing data anywhere. It is also compatible with + * [`FileSystemWritableFileStream`](https://developer.mozilla.org/en-US/docs/Web/API/FileSystemWritableFileStream) for + * use with the [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API). The + * `WritableStream` can also apply backpressure, which will propagate to the output and throttle the encoders. + * @group Output targets * @public */ export class StreamTarget extends Target { @@ -84,6 +91,7 @@ export class StreamTarget extends Target { /** @internal */ _options: StreamTargetOptions; + /** Creates a new {@link StreamTarget} which writes to the specified `writable`. */ constructor( writable: WritableStream, options: StreamTargetOptions = {}, @@ -114,8 +122,9 @@ export class StreamTarget extends Target { } /** - * This target just discards all incoming data. It is useful for when you need an `Output` but extract data from it - * differently, for example through format-specific callbacks (`onMoof`, `onMdat`, ...) or encoder events. + * This target just discards all incoming data. It is useful for when you need an {@link Output} but extract data from + * it differently, for example through format-specific callbacks (`onMoof`, `onMdat`, ...) or encoder events. + * @group Output targets * @public */ export class NullTarget extends Target { diff --git a/test/browser/url-source-short-file.test.ts b/test/browser/url-source-short-file.test.ts new file mode 100644 index 0000000..d9d003a --- /dev/null +++ b/test/browser/url-source-short-file.test.ts @@ -0,0 +1,19 @@ +import { expect, test } from 'vitest'; +import { UrlSource } from '../../src/source.js'; +import { ALL_FORMATS } from '../../src/input-format.js'; +import { Input } from '../../src/input.js'; + +test('Should be able to load a very small video file via URL (<512 kB)', async () => { + const source = new UrlSource('/frames.webm'); + const input = new Input({ + source, + formats: ALL_FORMATS, + }); + const primaryVideoTrack = await input.getPrimaryVideoTrack(); + if (!primaryVideoTrack) { + throw new Error('No video track found'); + }; + + const duration = await primaryVideoTrack.computeDuration(); + expect(duration).toBeCloseTo(3.33333); +}); diff --git a/test/read-mp4.test.ts b/test/node/read-mp4.test.ts similarity index 84% rename from test/read-mp4.test.ts rename to test/node/read-mp4.test.ts index b5d0407..1332cc5 100644 --- a/test/read-mp4.test.ts +++ b/test/node/read-mp4.test.ts @@ -1,11 +1,13 @@ import { expect, test } from 'vitest'; -import { ALL_FORMATS, MP4, EncodedPacketSink, Input, FilePathSource } from '../src/index.js'; +import path from 'node:path'; +import { ALL_FORMATS, MP4, EncodedPacketSink, Input, FilePathSource } from '../../src/index.js'; const __dirname = new URL('.', import.meta.url).pathname; test('Should be able to get packets from a .MP4 file', async () => { + const filePath = path.join(__dirname, '..', 'public/video.mp4'); const input = new Input({ - source: new FilePathSource(`${__dirname}/files/video.mp4`), + source: new FilePathSource(filePath), formats: ALL_FORMATS, }); diff --git a/test/public/frames.webm b/test/public/frames.webm new file mode 100644 index 0000000..7b7c009 Binary files /dev/null and b/test/public/frames.webm differ diff --git a/test/files/video.mp4 b/test/public/video.mp4 similarity index 100% rename from test/files/video.mp4 rename to test/public/video.mp4 diff --git a/test/vitest.config.ts b/test/vitest.config.ts new file mode 100644 index 0000000..90db06d --- /dev/null +++ b/test/vitest.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + browser: { + provider: 'webdriverio', + instances: [{ browser: 'chrome' }], + }, + }, +}); diff --git a/tsdoc.json b/tsdoc.json new file mode 100644 index 0000000..e0118bc --- /dev/null +++ b/tsdoc.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json", + "extends": ["@microsoft/api-extractor/extends/tsdoc-base.json"], + "tagDefinitions": [ + { + "tagName": "@group", + "syntaxKind": "block" + } + ], + "supportForTags": { + "@group": true + } +} \ No newline at end of file