diff --git a/.gitignore b/.gitignore index 972f3b82..71ec4924 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ node_modules dist .pnpm-debug.log __diff_output__ +__video_output__ .eslintcache coverage diff --git a/package.json b/package.json index 588a5604..2d9d4455 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,9 @@ "*": { "wasm": [ "./dist/index.d.ts" + ], + "video": [ + "./dist/video/index.d.ts" ] } }, @@ -53,6 +56,11 @@ "types": "./dist/jsx/jsx-runtime.d.ts", "import": "./dist/jsx/jsx-runtime.js", "require": "./dist/jsx/jsx-runtime.cjs" + }, + "./video": { + "types": "./dist/video/index.d.ts", + "import": "./dist/video/index.js", + "require": "./dist/video/index.cjs" } }, "scripts": { @@ -123,6 +131,14 @@ "typescript": "^5", "vitest": "^0.32.0" }, + "peerDependencies": { + "sharp": "*" + }, + "peerDependenciesMeta": { + "sharp": { + "optional": true + } + }, "dependencies": { "@shuding/opentype.js": "1.4.0-beta.0", "css-background-parser": "^0.1.0", @@ -131,6 +147,7 @@ "css-to-react-native": "^3.0.0", "emoji-regex-xs": "^2.0.1", "escape-html": "^1.0.3", + "h264-mp4-encoder": "^1.0.12", "linebreak": "^1.1.0", "parse-css-color": "^0.2.1", "postcss-value-parser": "^4.2.0", diff --git a/playground/next-env.d.ts b/playground/next-env.d.ts index 4f11a03d..83311bdf 100644 --- a/playground/next-env.d.ts +++ b/playground/next-env.d.ts @@ -1,5 +1,6 @@ /// /// +import './.next/dev/types/routes.d.ts' // NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. +// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information. diff --git a/playground/package.json b/playground/package.json index 8e00b3ad..949f0a28 100644 --- a/playground/package.json +++ b/playground/package.json @@ -17,14 +17,15 @@ "fflate": "^0.7.3", "intl-segmenter-polyfill": "^0.4.4", "js-base64": "^3.7.2", - "next": "^12.2.5", + "next": "^16.2.6", "pdfkit": "^0.13.0", - "react": "^17.0.2", - "react-dom": "^17.0.2", + "react": "0.0.0-experimental-d5736f09-20260507", + "react-dom": "0.0.0-experimental-d5736f09-20260507", "react-hot-toast": "^2.3.0", "react-live": "^2.4.1", "react-resizable-panels": "^0.0.30", "satori": "workspace:*", + "sharp": "^0.34.3", "svg-to-pdfkit": "^0.1.8" }, "devDependencies": { diff --git a/playground/pages/api/video.tsx b/playground/pages/api/video.tsx new file mode 100644 index 00000000..cdb0ba29 --- /dev/null +++ b/playground/pages/api/video.tsx @@ -0,0 +1,306 @@ +import { readFile } from 'node:fs/promises' +import { join } from 'node:path' +import type { NextApiRequest, NextApiResponse } from 'next' +import React from 'react' +import satori from 'satori' +import { video } from 'satori/video' + +// Record module-evaluation timing so a stage probe can report cold-start cost. +const MODULE_LOADED_AT = Date.now() + +const TAGLINE = 'ENLIGHTENED JSX, NOW IN MOTION' +const W = 960 +const H = 540 +const FPS = 30 +const DURATION_MS = 3000 + +const clamp01 = (t: number) => (t < 0 ? 0 : t > 1 ? 1 : t) +const range = (t: number, a: number, b: number) => clamp01((t - a) / (b - a)) +const easeOutCubic = (t: number) => 1 - Math.pow(1 - t, 3) +const easeOutQuint = (t: number) => 1 - Math.pow(1 - t, 5) + +type LoadedFonts = Array<{ + name: string + data: Buffer + weight: 400 | 700 + style: 'normal' +}> + +let fontsPromise: Promise | null = null +function loadFonts(): Promise { + if (fontsPromise) return fontsPromise + const promise: Promise = (async () => { + const [regular, bold] = await Promise.all([ + readFile(join(process.cwd(), 'public/inter-latin-ext-400-normal.woff')), + readFile(join(process.cwd(), 'public/inter-latin-ext-700-normal.woff')), + ]) + return [ + { name: 'Inter', data: regular, weight: 400, style: 'normal' }, + { name: 'Inter', data: bold, weight: 700, style: 'normal' }, + ] + })() + promise.catch(() => { + if (fontsPromise === promise) fontsPromise = null + }) + fontsPromise = promise + return promise +} + +function sanitizeText(raw: unknown): string { + if (typeof raw !== 'string') return 'satori' + // Strip control chars, collapse, clip to 10 grapheme-ish chars. + // (Codepoint-clip is good enough for a demo.) + const cleaned = Array.from(raw) + .filter((c) => c >= ' ' && c !== '\x7f') + .slice(0, 10) + .join('') + .trim() + return cleaned.length ? cleaned : 'satori' +} + +function renderTitleCard(text: string, progress: number): React.ReactElement { + const dot = easeOutQuint(range(progress, 0, 0.4)) + const tagline = easeOutCubic(range(progress, 0.7, 1)) + const pulse = 1 + 0.18 * Math.sin(range(progress, 0.8, 1) * Math.PI) + + const hueShift = progress * 30 + const bgInner = `hsl(${250 + hueShift}, 45%, 16%)` + const bgOuter = `hsl(${230 + hueShift}, 40%, 6%)` + + // Scale font size down as text grows so 10 chars still fit. + const fontSize = Math.max(72, Math.min(168, 720 / Math.max(text.length, 4))) + const stagger = Math.min(0.08, 0.45 / Math.max(text.length, 1)) + + return ( +
+
+ +
+ {Array.from(text).map((ch, i) => { + const start = 0.22 + i * stagger + const t = easeOutCubic(range(progress, start, start + 0.5)) + const baseStyle = { + display: 'flex', + fontSize, + fontWeight: 700, + letterSpacing: -fontSize * 0.04, + opacity: t, + transform: `translateY(${(1 - t) * 70}px)`, + padding: '0 2px', + lineHeight: 1.1, + } as const + if (ch === ' ') { + return ( +
+ ) + } + return ( +
+ {ch} +
+ ) + })} +
+ +
+ {TAGLINE} +
+
+ ) +} + +export const config = { + api: { + // Encoded MP4 may be a few MB; leave room. + responseLimit: '16mb', + }, +} + +const SOLID_FRAME = { + type: 'div', + props: { + style: { + width: '100%', + height: '100%', + backgroundColor: 'red', + }, + }, +} as const + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const stage = String(req.query.stage ?? 'full') + const handlerStart = Date.now() + const log = (event: string, extra: Record = {}) => { + console.log( + `[video] stage=${stage} ${event} ms=${Date.now() - handlerStart}`, + extra + ) + } + + try { + // Stage 0: did module evaluation complete? If you hit a 120s timeout + // *before* this handler runs, none of the stage probes will respond — the + // hang is in one of the top-level imports (satori/video, sharp, h264). + if (stage === 'module') { + return res.status(200).json({ + ok: true, + moduleLoadAt: MODULE_LOADED_AT, + handlerStart, + sinceModuleLoad: handlerStart - MODULE_LOADED_AT, + }) + } + + // Stage 1: just import the video entry. With static imports it's already + // done; this probe confirms the module is reachable. + if (stage === 'import') { + log('import-only') + return res.status(200).json({ + ok: true, + videoFn: typeof video, + satoriFn: typeof satori, + ms: Date.now() - handlerStart, + }) + } + + // Stage 2: read fonts from disk. Will fail with ENOENT if `public/` isn't + // bundled into the serverless function. + if (stage === 'fonts') { + const fonts = await loadFonts() + log('fonts-loaded', { count: fonts.length }) + return res.status(200).json({ + ok: true, + fonts: fonts.map((f) => ({ + name: f.name, + weight: f.weight, + bytes: f.data.byteLength, + })), + ms: Date.now() - handlerStart, + }) + } + + // Stage 3: one Satori call → SVG string. Exercises yoga + text shaping, + // but neither sharp nor the encoder. + if (stage === 'satori') { + const fonts = await loadFonts() + log('fonts-loaded') + const svg = await satori(renderTitleCard('hi', 0.5), { + width: 320, + height: 180, + fonts: fonts as any, + }) + log('satori-done', { svgBytes: svg.length }) + return res.status(200).json({ + ok: true, + svgBytes: svg.length, + ms: Date.now() - handlerStart, + }) + } + + // Stage 4: full video pipeline but trivial — 1 frame, 64×64, solid color + // (no text shaping). If everything else passed and this hangs, the + // h264-mp4-encoder WASM is the suspect. + if (stage === 'encode-one') { + const fonts = await loadFonts() + log('fonts-loaded') + const mp4 = await video(() => SOLID_FRAME as any, { + width: 64, + height: 64, + duration: 33, + fps: 30, + fonts: fonts as any, + }) + log('encode-done', { bytes: mp4.byteLength }) + return res.status(200).json({ + ok: true, + bytes: mp4.byteLength, + ms: Date.now() - handlerStart, + }) + } + + // Stage full (default): the real thing, with per-frame timing. + const text = sanitizeText(req.query.text) + const fonts = await loadFonts() + log('fonts-loaded') + + let frameCount = 0 + const sampleTimings: Array<{ frame: number; ms: number }> = [] + let lastFrameEnd = Date.now() + + const mp4 = await video( + ({ progress, frame }: { progress: number; frame: number }) => { + if (frame % 10 === 0) { + const now = Date.now() + sampleTimings.push({ frame, ms: now - lastFrameEnd }) + lastFrameEnd = now + } + frameCount++ + return renderTitleCard(text, progress) + }, + { + width: W, + height: H, + duration: DURATION_MS, + fps: FPS, + fonts: fonts as any, + quality: 22, + } + ) + + log('full-done', { + frameCount, + bytes: mp4.byteLength, + sampleTimings, + }) + + res.setHeader('Content-Type', 'video/mp4') + res.setHeader('Content-Length', String(mp4.byteLength)) + res.setHeader( + 'Cache-Control', + 'public, max-age=60, s-maxage=300, stale-while-revalidate=600' + ) + res.status(200).send(Buffer.from(mp4)) + } catch (err) { + console.error('[/api/video] failed at stage=', stage, err) + res.status(500).json({ + stage, + error: (err as Error).message ?? 'video render failed', + }) + } +} diff --git a/playground/tsconfig.json b/playground/tsconfig.json index 928ce22a..04d9e1d3 100644 --- a/playground/tsconfig.json +++ b/playground/tsconfig.json @@ -1,7 +1,11 @@ { "compilerOptions": { "target": "es2015", - "lib": ["dom", "dom.iterable", "esnext"], + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], "allowJs": true, "skipLibCheck": true, "strict": true, @@ -10,11 +14,17 @@ "incremental": true, "esModuleInterop": true, "module": "esnext", - "moduleResolution": "node", + "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, - "jsx": "preserve" + "jsx": "react-jsx" }, - "include": ["decs.d.ts", "**/*.ts", "**/*.tsx"], - "exclude": ["node_modules"] + "include": [ + "decs.d.ts", + "**/*.ts", + "**/*.tsx" + ], + "exclude": [ + "node_modules" + ] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d31aa9b0..f5315e58 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -34,6 +34,9 @@ importers: escape-html: specifier: ^1.0.3 version: 1.0.3 + h264-mp4-encoder: + specifier: ^1.0.12 + version: 1.0.12 linebreak: specifier: ^1.1.0 version: 1.1.0 @@ -109,13 +112,13 @@ importers: version: 0.34.3 tsup: specifier: ^7.1.0 - version: 7.1.0(postcss@8.4.21)(typescript@5.1.3) + version: 7.1.0(postcss@8.4.31)(typescript@5.1.3) turbo: specifier: ^1.6.3 version: 1.6.3 twrnc: specifier: ^3.4.0 - version: 3.4.0(postcss@8.4.21)(react-native@0.72.4) + version: 3.4.0(postcss@8.4.31)(react-native@0.72.4) typescript: specifier: ^5 version: 5.1.3 @@ -130,7 +133,7 @@ importers: version: 7.22.3 '@monaco-editor/react': specifier: ^4.4.5 - version: 4.5.1(monaco-editor@0.43.0)(react-dom@17.0.2)(react@17.0.2) + version: 4.5.1(monaco-editor@0.43.0)(react-dom@0.0.0-experimental-d5736f09-20260507)(react@0.0.0-experimental-d5736f09-20260507) '@resvg/resvg-wasm': specifier: ^2.3.1 version: 2.4.1 @@ -150,29 +153,32 @@ importers: specifier: ^3.7.2 version: 3.7.5 next: - specifier: ^12.2.5 - version: 12.3.4(@babel/core@7.22.20)(react-dom@17.0.2)(react@17.0.2) + specifier: ^16.2.6 + version: 16.2.6(@babel/core@7.22.20)(react-dom@0.0.0-experimental-d5736f09-20260507)(react@0.0.0-experimental-d5736f09-20260507) pdfkit: specifier: ^0.13.0 version: 0.13.0 react: - specifier: ^17.0.2 - version: 17.0.2 + specifier: 0.0.0-experimental-d5736f09-20260507 + version: 0.0.0-experimental-d5736f09-20260507 react-dom: - specifier: ^17.0.2 - version: 17.0.2(react@17.0.2) + specifier: 0.0.0-experimental-d5736f09-20260507 + version: 0.0.0-experimental-d5736f09-20260507(react@0.0.0-experimental-d5736f09-20260507) react-hot-toast: specifier: ^2.3.0 - version: 2.4.1(csstype@3.1.2)(react-dom@17.0.2)(react@17.0.2) + version: 2.4.1(csstype@3.1.2)(react-dom@0.0.0-experimental-d5736f09-20260507)(react@0.0.0-experimental-d5736f09-20260507) react-live: specifier: ^2.4.1 - version: 2.4.1(react-dom@17.0.2)(react@17.0.2) + version: 2.4.1(react-dom@0.0.0-experimental-d5736f09-20260507)(react@0.0.0-experimental-d5736f09-20260507) react-resizable-panels: specifier: ^0.0.30 - version: 0.0.30(react-dom@17.0.2)(react@17.0.2) + version: 0.0.30(react-dom@0.0.0-experimental-d5736f09-20260507)(react@0.0.0-experimental-d5736f09-20260507) satori: specifier: workspace:* version: link:.. + sharp: + specifier: ^0.34.3 + version: 0.34.3 svg-to-pdfkit: specifier: ^0.1.8 version: 0.1.8 @@ -1613,12 +1619,11 @@ packages: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} dev: true - /@emnapi/runtime@1.5.0: - resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==} + /@emnapi/runtime@1.10.0: + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} requiresBuild: true dependencies: - tslib: 2.4.0 - dev: true + tslib: 2.8.1 optional: true /@esbuild/android-arm64@0.16.17: @@ -2064,6 +2069,13 @@ packages: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} dev: true + /@img/colour@1.1.0: + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + requiresBuild: true + dev: false + optional: true + /@img/sharp-darwin-arm64@0.34.3: resolution: {integrity: sha512-ryFMfvxxpQRsgZJqBd4wsttYQbCxsJksrv9Lw/v798JcQ8+w84mBWuXwl+TT0WJ/WrYOLaYpwQXi3sA9nTIaIg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -2072,7 +2084,17 @@ packages: requiresBuild: true optionalDependencies: '@img/sharp-libvips-darwin-arm64': 1.2.0 - dev: true + optional: true + + /@img/sharp-darwin-arm64@0.34.5: + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + dev: false optional: true /@img/sharp-darwin-x64@0.34.3: @@ -2083,7 +2105,17 @@ packages: requiresBuild: true optionalDependencies: '@img/sharp-libvips-darwin-x64': 1.2.0 - dev: true + optional: true + + /@img/sharp-darwin-x64@0.34.5: + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + dev: false optional: true /@img/sharp-libvips-darwin-arm64@1.2.0: @@ -2091,7 +2123,14 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true - dev: true + optional: true + + /@img/sharp-libvips-darwin-arm64@1.2.4: + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false optional: true /@img/sharp-libvips-darwin-x64@1.2.0: @@ -2099,7 +2138,14 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true - dev: true + optional: true + + /@img/sharp-libvips-darwin-x64@1.2.4: + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false optional: true /@img/sharp-libvips-linux-arm64@1.2.0: @@ -2107,7 +2153,14 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true - dev: true + optional: true + + /@img/sharp-libvips-linux-arm64@1.2.4: + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false optional: true /@img/sharp-libvips-linux-arm@1.2.0: @@ -2115,7 +2168,14 @@ packages: cpu: [arm] os: [linux] requiresBuild: true - dev: true + optional: true + + /@img/sharp-libvips-linux-arm@1.2.4: + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: false optional: true /@img/sharp-libvips-linux-ppc64@1.2.0: @@ -2123,7 +2183,22 @@ packages: cpu: [ppc64] os: [linux] requiresBuild: true - dev: true + optional: true + + /@img/sharp-libvips-linux-ppc64@1.2.4: + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linux-riscv64@1.2.4: + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: false optional: true /@img/sharp-libvips-linux-s390x@1.2.0: @@ -2131,7 +2206,14 @@ packages: cpu: [s390x] os: [linux] requiresBuild: true - dev: true + optional: true + + /@img/sharp-libvips-linux-s390x@1.2.4: + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: false optional: true /@img/sharp-libvips-linux-x64@1.2.0: @@ -2139,7 +2221,14 @@ packages: cpu: [x64] os: [linux] requiresBuild: true - dev: true + optional: true + + /@img/sharp-libvips-linux-x64@1.2.4: + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false optional: true /@img/sharp-libvips-linuxmusl-arm64@1.2.0: @@ -2147,7 +2236,14 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true - dev: true + optional: true + + /@img/sharp-libvips-linuxmusl-arm64@1.2.4: + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false optional: true /@img/sharp-libvips-linuxmusl-x64@1.2.0: @@ -2155,7 +2251,14 @@ packages: cpu: [x64] os: [linux] requiresBuild: true - dev: true + optional: true + + /@img/sharp-libvips-linuxmusl-x64@1.2.4: + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false optional: true /@img/sharp-linux-arm64@0.34.3: @@ -2166,7 +2269,17 @@ packages: requiresBuild: true optionalDependencies: '@img/sharp-libvips-linux-arm64': 1.2.0 - dev: true + optional: true + + /@img/sharp-linux-arm64@0.34.5: + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + dev: false optional: true /@img/sharp-linux-arm@0.34.3: @@ -2177,7 +2290,17 @@ packages: requiresBuild: true optionalDependencies: '@img/sharp-libvips-linux-arm': 1.2.0 - dev: true + optional: true + + /@img/sharp-linux-arm@0.34.5: + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + dev: false optional: true /@img/sharp-linux-ppc64@0.34.3: @@ -2188,7 +2311,28 @@ packages: requiresBuild: true optionalDependencies: '@img/sharp-libvips-linux-ppc64': 1.2.0 - dev: true + optional: true + + /@img/sharp-linux-ppc64@0.34.5: + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + dev: false + optional: true + + /@img/sharp-linux-riscv64@0.34.5: + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + dev: false optional: true /@img/sharp-linux-s390x@0.34.3: @@ -2199,7 +2343,17 @@ packages: requiresBuild: true optionalDependencies: '@img/sharp-libvips-linux-s390x': 1.2.0 - dev: true + optional: true + + /@img/sharp-linux-s390x@0.34.5: + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + dev: false optional: true /@img/sharp-linux-x64@0.34.3: @@ -2210,7 +2364,17 @@ packages: requiresBuild: true optionalDependencies: '@img/sharp-libvips-linux-x64': 1.2.0 - dev: true + optional: true + + /@img/sharp-linux-x64@0.34.5: + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + dev: false optional: true /@img/sharp-linuxmusl-arm64@0.34.3: @@ -2221,7 +2385,17 @@ packages: requiresBuild: true optionalDependencies: '@img/sharp-libvips-linuxmusl-arm64': 1.2.0 - dev: true + optional: true + + /@img/sharp-linuxmusl-arm64@0.34.5: + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + dev: false optional: true /@img/sharp-linuxmusl-x64@0.34.3: @@ -2232,7 +2406,17 @@ packages: requiresBuild: true optionalDependencies: '@img/sharp-libvips-linuxmusl-x64': 1.2.0 - dev: true + optional: true + + /@img/sharp-linuxmusl-x64@0.34.5: + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + dev: false optional: true /@img/sharp-wasm32@0.34.3: @@ -2241,8 +2425,17 @@ packages: cpu: [wasm32] requiresBuild: true dependencies: - '@emnapi/runtime': 1.5.0 - dev: true + '@emnapi/runtime': 1.10.0 + optional: true + + /@img/sharp-wasm32@0.34.5: + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + requiresBuild: true + dependencies: + '@emnapi/runtime': 1.10.0 + dev: false optional: true /@img/sharp-win32-arm64@0.34.3: @@ -2251,7 +2444,15 @@ packages: cpu: [arm64] os: [win32] requiresBuild: true - dev: true + optional: true + + /@img/sharp-win32-arm64@0.34.5: + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: false optional: true /@img/sharp-win32-ia32@0.34.3: @@ -2260,7 +2461,15 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true - dev: true + optional: true + + /@img/sharp-win32-ia32@0.34.5: + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: false optional: true /@img/sharp-win32-x64@0.34.3: @@ -2269,7 +2478,15 @@ packages: cpu: [x64] os: [win32] requiresBuild: true - dev: true + optional: true + + /@img/sharp-win32-x64@0.34.5: + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false optional: true /@istanbuljs/load-nyc-config@1.1.0: @@ -2575,7 +2792,7 @@ packages: state-local: 1.0.7 dev: false - /@monaco-editor/react@4.5.1(monaco-editor@0.43.0)(react-dom@17.0.2)(react@17.0.2): + /@monaco-editor/react@4.5.1(monaco-editor@0.43.0)(react-dom@0.0.0-experimental-d5736f09-20260507)(react@0.0.0-experimental-d5736f09-20260507): resolution: {integrity: sha512-NNDFdP+2HojtNhCkRfE6/D6ro6pBNihaOzMbGK84lNWzRu+CfBjwzGt4jmnqimLuqp5yE5viHS2vi+QOAnD5FQ==} peerDependencies: monaco-editor: '>= 0.25.0 < 1' @@ -2584,34 +2801,16 @@ packages: dependencies: '@monaco-editor/loader': 1.3.3(monaco-editor@0.43.0) monaco-editor: 0.43.0 - react: 17.0.2 - react-dom: 17.0.2(react@17.0.2) - dev: false - - /@next/env@12.3.4: - resolution: {integrity: sha512-H/69Lc5Q02dq3o+dxxy5O/oNxFsZpdL6WREtOOtOM1B/weonIwDXkekr1KV5DPVPr12IHFPrMrcJQ6bgPMfn7A==} - dev: false - - /@next/swc-android-arm-eabi@12.3.4: - resolution: {integrity: sha512-cM42Cw6V4Bz/2+j/xIzO8nK/Q3Ly+VSlZJTa1vHzsocJRYz8KT6MrreXaci2++SIZCF1rVRCDgAg5PpqRibdIA==} - engines: {node: '>= 10'} - cpu: [arm] - os: [android] - requiresBuild: true + react: 0.0.0-experimental-d5736f09-20260507 + react-dom: 0.0.0-experimental-d5736f09-20260507(react@0.0.0-experimental-d5736f09-20260507) dev: false - optional: true - /@next/swc-android-arm64@12.3.4: - resolution: {integrity: sha512-5jf0dTBjL+rabWjGj3eghpLUxCukRhBcEJgwLedewEA/LJk2HyqCvGIwj5rH+iwmq1llCWbOky2dO3pVljrapg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [android] - requiresBuild: true + /@next/env@16.2.6: + resolution: {integrity: sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw==} dev: false - optional: true - /@next/swc-darwin-arm64@12.3.4: - resolution: {integrity: sha512-DqsSTd3FRjQUR6ao0E1e2OlOcrF5br+uegcEGPVonKYJpcr0MJrtYmPxd4v5T6UCJZ+XzydF7eQo5wdGvSZAyA==} + /@next/swc-darwin-arm64@16.2.6: + resolution: {integrity: sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] @@ -2619,8 +2818,8 @@ packages: dev: false optional: true - /@next/swc-darwin-x64@12.3.4: - resolution: {integrity: sha512-PPF7tbWD4k0dJ2EcUSnOsaOJ5rhT3rlEt/3LhZUGiYNL8KvoqczFrETlUx0cUYaXe11dRA3F80Hpt727QIwByQ==} + /@next/swc-darwin-x64@16.2.6: + resolution: {integrity: sha512-v/YLBHIY132Ced3puBJ7YJKw1lqsCrgcNo2aRJlCEyQrrCeRJlvGlnmxhPxNQI3KE3N1DN5r9TPNPvka3nq5RQ==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] @@ -2628,66 +2827,44 @@ packages: dev: false optional: true - /@next/swc-freebsd-x64@12.3.4: - resolution: {integrity: sha512-KM9JXRXi/U2PUM928z7l4tnfQ9u8bTco/jb939pdFUHqc28V43Ohd31MmZD1QzEK4aFlMRaIBQOWQZh4D/E5lQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [freebsd] - requiresBuild: true - dev: false - optional: true - - /@next/swc-linux-arm-gnueabihf@12.3.4: - resolution: {integrity: sha512-3zqD3pO+z5CZyxtKDTnOJ2XgFFRUBciOox6EWkoZvJfc9zcidNAQxuwonUeNts6Xbm8Wtm5YGIRC0x+12YH7kw==} - engines: {node: '>= 10'} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@next/swc-linux-arm64-gnu@12.3.4: - resolution: {integrity: sha512-kiX0vgJGMZVv+oo1QuObaYulXNvdH/IINmvdZnVzMO/jic/B8EEIGlZ8Bgvw8LCjH3zNVPO3mGrdMvnEEPEhKA==} + /@next/swc-linux-arm64-gnu@16.2.6: + resolution: {integrity: sha512-RPOvqlYBbcQjkz9VQQDZ2T2bARIjXZV1KFlt+V2Mr6SW/e4I9fcKsaA0hdyf2FHoTlsV2xnBd5Y912rP/1Ce6w==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] requiresBuild: true dev: false optional: true - /@next/swc-linux-arm64-musl@12.3.4: - resolution: {integrity: sha512-EETZPa1juczrKLWk5okoW2hv7D7WvonU+Cf2CgsSoxgsYbUCZ1voOpL4JZTOb6IbKMDo6ja+SbY0vzXZBUMvkQ==} + /@next/swc-linux-arm64-musl@16.2.6: + resolution: {integrity: sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] requiresBuild: true dev: false optional: true - /@next/swc-linux-x64-gnu@12.3.4: - resolution: {integrity: sha512-4csPbRbfZbuWOk3ATyWcvVFdD9/Rsdq5YHKvRuEni68OCLkfy4f+4I9OBpyK1SKJ00Cih16NJbHE+k+ljPPpag==} + /@next/swc-linux-x64-gnu@16.2.6: + resolution: {integrity: sha512-DOj182mPV8G3UkrayLoREM5YEYI+Dk5wv7Ox9xl1fFibAELEsFD0lDPfHIeILlutMMfdyhlzYPELG3peuKaurw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] requiresBuild: true dev: false optional: true - /@next/swc-linux-x64-musl@12.3.4: - resolution: {integrity: sha512-YeBmI+63Ro75SUiL/QXEVXQ19T++58aI/IINOyhpsRL1LKdyfK/35iilraZEFz9bLQrwy1LYAR5lK200A9Gjbg==} + /@next/swc-linux-x64-musl@16.2.6: + resolution: {integrity: sha512-HKQ5SP/V/ub73UvF7n/zeJlxk2kLmtL7Wzrg4WfmkjmNos5onJ2tKu7yZOPdL18A6Svfn3max29ym+ry7NkK4g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] requiresBuild: true dev: false optional: true - /@next/swc-win32-arm64-msvc@12.3.4: - resolution: {integrity: sha512-Sd0qFUJv8Tj0PukAYbCCDbmXcMkbIuhnTeHm9m4ZGjCf6kt7E/RMs55Pd3R5ePjOkN7dJEuxYBehawTR/aPDSQ==} + /@next/swc-win32-arm64-msvc@16.2.6: + resolution: {integrity: sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] @@ -2695,17 +2872,8 @@ packages: dev: false optional: true - /@next/swc-win32-ia32-msvc@12.3.4: - resolution: {integrity: sha512-rt/vv/vg/ZGGkrkKcuJ0LyliRdbskQU+91bje+PgoYmxTZf/tYs6IfbmgudBJk6gH3QnjHWbkphDdRQrseRefQ==} - engines: {node: '>= 10'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: false - optional: true - - /@next/swc-win32-x64-msvc@12.3.4: - resolution: {integrity: sha512-DQ20JEfTBZAgF8QCjYfJhv2/279M6onxFjdG/+5B0Cyj00/EdBxiWb2eGGFgQhrBbNv/lsvzFbbi0Ptf8Vw/bg==} + /@next/swc-win32-x64-msvc@16.2.6: + resolution: {integrity: sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -2786,7 +2954,7 @@ packages: node-stream-zip: 1.15.0 ora: 5.4.1 prompts: 2.4.2 - semver: 7.7.2 + semver: 7.8.0 strip-ansi: 5.2.0 sudo-prompt: 9.2.1 wcwidth: 1.0.1 @@ -2883,7 +3051,7 @@ packages: node-fetch: 2.6.11 open: 6.4.0 ora: 5.4.1 - semver: 7.7.2 + semver: 7.8.0 shell-quote: 1.7.3 transitivePeerDependencies: - encoding @@ -2916,7 +3084,7 @@ packages: fs-extra: 8.1.0 graceful-fs: 4.2.11 prompts: 2.4.2 - semver: 7.7.2 + semver: 7.8.0 transitivePeerDependencies: - '@babel/core' - bufferutil @@ -3142,13 +3310,13 @@ packages: /@swc/helpers@0.3.17: resolution: {integrity: sha512-tb7Iu+oZ+zWJZ3HJqwx8oNwSDIU440hmVMDPhpACWQWnrZHK99Bxs70gT1L2dnr5Hg50ZRWEFkQCAnOVVV0z1Q==} dependencies: - tslib: 2.4.0 + tslib: 2.8.1 dev: false - /@swc/helpers@0.4.11: - resolution: {integrity: sha512-rEUrBSGIoSFuYxwBYtlUFMlE2CwGhmW+w9355/5oduSw8e5h2+Tj4UrAGNNgP9915++wj5vkQo0UuOBqOAq4nw==} + /@swc/helpers@0.5.15: + resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} dependencies: - tslib: 2.4.0 + tslib: 2.8.1 dev: false /@types/babel__core@7.20.2: @@ -3706,7 +3874,7 @@ packages: resolution: {integrity: sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==} engines: {node: '>=4'} dependencies: - tslib: 2.4.0 + tslib: 2.8.1 dev: true /astral-regex@1.0.0: @@ -3907,6 +4075,12 @@ packages: /base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + /baseline-browser-mapping@2.10.29: + resolution: {integrity: sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==} + engines: {node: '>=6.0.0'} + hasBin: true + dev: false + /binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} @@ -3959,7 +4133,7 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001538 + caniuse-lite: 1.0.30001792 electron-to-chromium: 1.4.525 node-releases: 2.0.13 update-browserslist-db: 1.0.11(browserslist@4.21.10) @@ -4062,12 +4236,8 @@ packages: resolution: {integrity: sha512-W2lPwkBkMZwFlPCXhIlYgxu+7gC/NUlCtdK652DAJ1JdgV0sTrvuPFshNPrFa1TY2JOkLhgdeEBplB4ezEa+xg==} dev: false - /caniuse-lite@1.0.30001495: - resolution: {integrity: sha512-F6x5IEuigtUfU5ZMQK2jsy5JqUUlEFRVZq8bO2a+ysq5K7jD6PPc9YXZj78xDNS3uNchesp1Jw47YXEqr+Viyg==} - dev: false - - /caniuse-lite@1.0.30001538: - resolution: {integrity: sha512-HWJnhnID+0YMtGlzcp3T9drmBJUVDchPJ08tpUGFLs9CYlwWPH2uLgpHn8fND5pCgXVtnGS3H4QR9XLMHVNkHw==} + /caniuse-lite@1.0.30001792: + resolution: {integrity: sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==} /chai@4.3.7: resolution: {integrity: sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==} @@ -4168,6 +4338,10 @@ packages: string-width: 5.1.2 dev: true + /client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + dev: false + /cliui@6.0.0: resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} dependencies: @@ -4231,7 +4405,6 @@ packages: engines: {node: '>=7.0.0'} dependencies: color-name: 1.1.4 - dev: true /color-name@1.1.3: resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} @@ -4244,7 +4417,6 @@ packages: dependencies: color-name: 1.1.4 simple-swizzle: 0.2.2 - dev: true /color@4.2.3: resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} @@ -4252,7 +4424,6 @@ packages: dependencies: color-convert: 2.0.1 color-string: 1.9.1 - dev: true /colorette@1.4.0: resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} @@ -4610,7 +4781,13 @@ packages: /detect-libc@2.0.4: resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} engines: {node: '>=8'} - dev: true + + /detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + requiresBuild: true + dev: false + optional: true /detect-newline@3.1.0: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} @@ -5426,6 +5603,10 @@ packages: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} dev: true + /h264-mp4-encoder@1.0.12: + resolution: {integrity: sha512-xih3J+Go0o1RqGjhOt6TwXLWWGqLONRPyS8yoMu/RoS/S8WyEv4HuHp1KBsDDl8srZQ3gw9f95JYkCSjCuZbHQ==} + dev: false + /has-bigints@1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} @@ -5635,7 +5816,6 @@ packages: /is-arrayish@0.3.2: resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} - dev: true /is-bigint@1.0.4: resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} @@ -5865,7 +6045,7 @@ packages: '@babel/parser': 7.23.0 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.0 - semver: 7.7.2 + semver: 7.8.0 transitivePeerDependencies: - supports-color dev: true @@ -6257,7 +6437,7 @@ packages: jest-util: 29.7.0 natural-compare: 1.4.0 pretty-format: 29.7.0 - semver: 7.7.2 + semver: 7.8.0 transitivePeerDependencies: - supports-color dev: true @@ -7167,10 +7347,16 @@ packages: thenify-all: 1.6.0 dev: true + /nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + /nanoid@3.3.4: resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + dev: true /natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -7185,46 +7371,45 @@ packages: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} dev: true - /next@12.3.4(@babel/core@7.22.20)(react-dom@17.0.2)(react@17.0.2): - resolution: {integrity: sha512-VcyMJUtLZBGzLKo3oMxrEF0stxh8HwuW976pAzlHhI3t8qJ4SROjCrSh1T24bhrbjw55wfZXAbXPGwPt5FLRfQ==} - engines: {node: '>=12.22.0'} + /next@16.2.6(@babel/core@7.22.20)(react-dom@0.0.0-experimental-d5736f09-20260507)(react@0.0.0-experimental-d5736f09-20260507): + resolution: {integrity: sha512-qOVgKJg1+At15NpeUP+eJgCHvTCgXsogweq87Ri/Ix7PkqQHg4sdaXmSFqKlgaIXE4kW0g25LE68W87UANlHtw==} + engines: {node: '>=20.9.0'} hasBin: true peerDependencies: - fibers: '>= 3.1.0' - node-sass: ^6.0.0 || ^7.0.0 - react: ^17.0.2 || ^18.0.0-0 - react-dom: ^17.0.2 || ^18.0.0-0 + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.51.1 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 sass: ^1.3.0 peerDependenciesMeta: - fibers: + '@opentelemetry/api': optional: true - node-sass: + '@playwright/test': + optional: true + babel-plugin-react-compiler: optional: true sass: optional: true dependencies: - '@next/env': 12.3.4 - '@swc/helpers': 0.4.11 - caniuse-lite: 1.0.30001495 - postcss: 8.4.14 - react: 17.0.2 - react-dom: 17.0.2(react@17.0.2) - styled-jsx: 5.0.7(@babel/core@7.22.20)(react@17.0.2) - use-sync-external-store: 1.2.0(react@17.0.2) + '@next/env': 16.2.6 + '@swc/helpers': 0.5.15 + baseline-browser-mapping: 2.10.29 + caniuse-lite: 1.0.30001792 + postcss: 8.4.31 + react: 0.0.0-experimental-d5736f09-20260507 + react-dom: 0.0.0-experimental-d5736f09-20260507(react@0.0.0-experimental-d5736f09-20260507) + styled-jsx: 5.1.6(@babel/core@7.22.20)(react@0.0.0-experimental-d5736f09-20260507) optionalDependencies: - '@next/swc-android-arm-eabi': 12.3.4 - '@next/swc-android-arm64': 12.3.4 - '@next/swc-darwin-arm64': 12.3.4 - '@next/swc-darwin-x64': 12.3.4 - '@next/swc-freebsd-x64': 12.3.4 - '@next/swc-linux-arm-gnueabihf': 12.3.4 - '@next/swc-linux-arm64-gnu': 12.3.4 - '@next/swc-linux-arm64-musl': 12.3.4 - '@next/swc-linux-x64-gnu': 12.3.4 - '@next/swc-linux-x64-musl': 12.3.4 - '@next/swc-win32-arm64-msvc': 12.3.4 - '@next/swc-win32-ia32-msvc': 12.3.4 - '@next/swc-win32-x64-msvc': 12.3.4 + '@next/swc-darwin-arm64': 16.2.6 + '@next/swc-darwin-x64': 16.2.6 + '@next/swc-linux-arm64-gnu': 16.2.6 + '@next/swc-linux-arm64-musl': 16.2.6 + '@next/swc-linux-x64-gnu': 16.2.6 + '@next/swc-linux-x64-musl': 16.2.6 + '@next/swc-win32-arm64-msvc': 16.2.6 + '@next/swc-win32-x64-msvc': 16.2.6 + sharp: 0.34.5 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros @@ -7666,29 +7851,29 @@ packages: engines: {node: '>=12.13.0'} dev: true - /postcss-import@14.1.0(postcss@8.4.21): + /postcss-import@14.1.0(postcss@8.4.31): resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==} engines: {node: '>=10.0.0'} peerDependencies: postcss: ^8.0.0 dependencies: - postcss: 8.4.21 + postcss: 8.4.31 postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.1 dev: true - /postcss-js@4.0.0(postcss@8.4.21): + /postcss-js@4.0.0(postcss@8.4.31): resolution: {integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==} engines: {node: ^12 || ^14 || >= 16} peerDependencies: postcss: ^8.3.3 dependencies: camelcase-css: 2.0.1 - postcss: 8.4.21 + postcss: 8.4.31 dev: true - /postcss-load-config@3.1.4(postcss@8.4.21): + /postcss-load-config@3.1.4(postcss@8.4.31): resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} engines: {node: '>= 10'} peerDependencies: @@ -7701,11 +7886,11 @@ packages: optional: true dependencies: lilconfig: 2.0.6 - postcss: 8.4.21 + postcss: 8.4.31 yaml: 1.10.2 dev: true - /postcss-load-config@4.0.1(postcss@8.4.21): + /postcss-load-config@4.0.1(postcss@8.4.31): resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==} engines: {node: '>= 14'} peerDependencies: @@ -7718,17 +7903,17 @@ packages: optional: true dependencies: lilconfig: 2.0.6 - postcss: 8.4.21 + postcss: 8.4.31 yaml: 2.2.1 dev: true - /postcss-nested@5.0.6(postcss@8.4.21): + /postcss-nested@5.0.6(postcss@8.4.31): resolution: {integrity: sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==} engines: {node: '>=12.0'} peerDependencies: postcss: ^8.2.14 dependencies: - postcss: 8.4.21 + postcss: 8.4.31 postcss-selector-parser: 6.0.10 dev: true @@ -7743,23 +7928,22 @@ packages: /postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - /postcss@8.4.14: - resolution: {integrity: sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==} + /postcss@8.4.21: + resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==} engines: {node: ^10 || ^12 || >=14} dependencies: nanoid: 3.3.4 picocolors: 1.0.0 source-map-js: 1.0.2 - dev: false + dev: true - /postcss@8.4.21: - resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==} + /postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} dependencies: - nanoid: 3.3.4 + nanoid: 3.3.12 picocolors: 1.0.0 source-map-js: 1.0.2 - dev: true /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} @@ -7800,12 +7984,12 @@ packages: react-is: 18.2.0 dev: true - /prism-react-renderer@1.3.5(react@17.0.2): + /prism-react-renderer@1.3.5(react@0.0.0-experimental-d5736f09-20260507): resolution: {integrity: sha512-IJ+MSwBWKG+SM3b2SUfdrhC+gu01QkV2KmRQgREThBfSQRoufqRfxfHUxpG1WcaFjP+kojcFyO9Qqtpgt3qLCg==} peerDependencies: react: '>=0.14.9' dependencies: - react: 17.0.2 + react: 0.0.0-experimental-d5736f09-20260507 dev: false /process-nextick-args@2.0.1: @@ -7872,18 +8056,16 @@ packages: - utf-8-validate dev: true - /react-dom@17.0.2(react@17.0.2): - resolution: {integrity: sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==} + /react-dom@0.0.0-experimental-d5736f09-20260507(react@0.0.0-experimental-d5736f09-20260507): + resolution: {integrity: sha512-KOHRpa2sHl+wA7KGfX96sAtlSF0bbEHcHmMvA2ahIMMvlFtlcBblY6yV4i53E9nQD+wJ5OUG8jMII4fRTYTL4Q==} peerDependencies: - react: 17.0.2 + react: 0.0.0-experimental-d5736f09-20260507 dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - react: 17.0.2 - scheduler: 0.20.2 + react: 0.0.0-experimental-d5736f09-20260507 + scheduler: 0.0.0-experimental-d5736f09-20260507 dev: false - /react-hot-toast@2.4.1(csstype@3.1.2)(react-dom@17.0.2)(react@17.0.2): + /react-hot-toast@2.4.1(csstype@3.1.2)(react-dom@0.0.0-experimental-d5736f09-20260507)(react@0.0.0-experimental-d5736f09-20260507): resolution: {integrity: sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==} engines: {node: '>=10'} peerDependencies: @@ -7891,8 +8073,8 @@ packages: react-dom: '>=16' dependencies: goober: 2.1.13(csstype@3.1.2) - react: 17.0.2 - react-dom: 17.0.2(react@17.0.2) + react: 0.0.0-experimental-d5736f09-20260507 + react-dom: 0.0.0-experimental-d5736f09-20260507(react@0.0.0-experimental-d5736f09-20260507) transitivePeerDependencies: - csstype dev: false @@ -7908,7 +8090,7 @@ packages: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} dev: true - /react-live@2.4.1(react-dom@17.0.2)(react@17.0.2): + /react-live@2.4.1(react-dom@0.0.0-experimental-d5736f09-20260507)(react@0.0.0-experimental-d5736f09-20260507): resolution: {integrity: sha512-r+32f7oV/kBs3QZBRvaT+9vOkQW47UZrDpgwUe5FiIMOl7sdo5pmISgb7Zpj5PGHgY6XQaiXs3FEh+IWw3KbRg==} engines: {node: '>= 0.12.0', npm: '>= 2.0.0'} peerDependencies: @@ -7919,11 +8101,11 @@ packages: buble: 0.19.6 core-js: 3.30.2 dom-iterator: 1.0.0 - prism-react-renderer: 1.3.5(react@17.0.2) + prism-react-renderer: 1.3.5(react@0.0.0-experimental-d5736f09-20260507) prop-types: 15.8.1 - react: 17.0.2 - react-dom: 17.0.2(react@17.0.2) - react-simple-code-editor: 0.11.3(react-dom@17.0.2)(react@17.0.2) + react: 0.0.0-experimental-d5736f09-20260507 + react-dom: 0.0.0-experimental-d5736f09-20260507(react@0.0.0-experimental-d5736f09-20260507) + react-simple-code-editor: 0.11.3(react-dom@0.0.0-experimental-d5736f09-20260507)(react@0.0.0-experimental-d5736f09-20260507) unescape: 1.0.1 dev: false @@ -7985,14 +8167,14 @@ packages: engines: {node: '>=0.10.0'} dev: true - /react-resizable-panels@0.0.30(react-dom@17.0.2)(react@17.0.2): + /react-resizable-panels@0.0.30(react-dom@0.0.0-experimental-d5736f09-20260507)(react@0.0.0-experimental-d5736f09-20260507): resolution: {integrity: sha512-VvyPqYJ/+pDkWLUf+XdLlvSyV4p1NFVXB3PP7oZ9l5bUOE6Voe0nADzIKOuSdeTvQ15Fnsv7AKGmRA8WJw+AuA==} peerDependencies: react: ^16.14.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.14.0 || ^17.0.0 || ^18.0.0 dependencies: - react: 17.0.2 - react-dom: 17.0.2(react@17.0.2) + react: 0.0.0-experimental-d5736f09-20260507 + react-dom: 0.0.0-experimental-d5736f09-20260507(react@0.0.0-experimental-d5736f09-20260507) dev: false /react-shallow-renderer@16.15.0(react@17.0.2): @@ -8005,14 +8187,19 @@ packages: react-is: 18.2.0 dev: true - /react-simple-code-editor@0.11.3(react-dom@17.0.2)(react@17.0.2): + /react-simple-code-editor@0.11.3(react-dom@0.0.0-experimental-d5736f09-20260507)(react@0.0.0-experimental-d5736f09-20260507): resolution: {integrity: sha512-7bVI4Yd1aNCeuldErXUt8ksaAG5Fi+GZ6vp3mtFBnckKdzsQtrgkDvdwMFXIhwTGG+mUYmk5ZpMo0axSW9JBzA==} peerDependencies: react: '*' react-dom: '*' dependencies: - react: 17.0.2 - react-dom: 17.0.2(react@17.0.2) + react: 0.0.0-experimental-d5736f09-20260507 + react-dom: 0.0.0-experimental-d5736f09-20260507(react@0.0.0-experimental-d5736f09-20260507) + dev: false + + /react@0.0.0-experimental-d5736f09-20260507: + resolution: {integrity: sha512-5dDIwqjDBbcFq8XT+X/i645ApcYnXPaeX4YhLtMHTCSV28xCSq3G4rfjIMqWZtlkcrblz6oBq0T5DXHqlDe0NQ==} + engines: {node: '>=0.10.0'} dev: false /react@17.0.2: @@ -8021,6 +8208,7 @@ packages: dependencies: loose-envify: 1.4.0 object-assign: 4.1.1 + dev: true /read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} @@ -8067,7 +8255,7 @@ packages: ast-types: 0.15.2 esprima: 4.0.1 source-map: 0.6.1 - tslib: 2.4.0 + tslib: 2.8.1 dev: true /regenerate-unicode-properties@10.1.1: @@ -8275,13 +8463,13 @@ packages: /rxjs@7.5.6: resolution: {integrity: sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==} dependencies: - tslib: 2.4.0 + tslib: 2.8.1 dev: true /rxjs@7.8.0: resolution: {integrity: sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==} dependencies: - tslib: 2.4.0 + tslib: 2.8.1 dev: true /safe-buffer@5.1.2: @@ -8292,11 +8480,8 @@ packages: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} dev: true - /scheduler@0.20.2: - resolution: {integrity: sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==} - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 + /scheduler@0.0.0-experimental-d5736f09-20260507: + resolution: {integrity: sha512-3aVfrUiV8iEjthtyzXWQfx2Mg7kSWP7oT16XoblwySl6Vhc6aqMIOqQhPMF+W5EGugD7KJALjwBpwU64z8MGFQ==} dev: false /scheduler@0.24.0-canary-efb381bbf-20230505: @@ -8331,7 +8516,12 @@ packages: resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} engines: {node: '>=10'} hasBin: true - dev: true + + /semver@7.8.0: + resolution: {integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==} + engines: {node: '>=10'} + hasBin: true + requiresBuild: true /send@0.18.0: resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} @@ -8417,7 +8607,42 @@ packages: '@img/sharp-win32-arm64': 0.34.3 '@img/sharp-win32-ia32': 0.34.3 '@img/sharp-win32-x64': 0.34.3 - dev: true + + /sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + requiresBuild: true + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.8.0 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + dev: false + optional: true /shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} @@ -8454,7 +8679,6 @@ packages: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} dependencies: is-arrayish: 0.3.2 - dev: true /sirv@2.0.3: resolution: {integrity: sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==} @@ -8739,13 +8963,13 @@ packages: resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} dev: true - /styled-jsx@5.0.7(@babel/core@7.22.20)(react@17.0.2): - resolution: {integrity: sha512-b3sUzamS086YLRuvnaDigdAewz1/EFYlHpYBP5mZovKEdQQOIIYq8lApylub3HHZ6xFjV051kkGU7cudJmrXEA==} + /styled-jsx@5.1.6(@babel/core@7.22.20)(react@0.0.0-experimental-d5736f09-20260507): + resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} engines: {node: '>= 12.0.0'} peerDependencies: '@babel/core': '*' babel-plugin-macros: '*' - react: '>= 16.8.0 || 17.x.x || ^18.0.0-0' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' peerDependenciesMeta: '@babel/core': optional: true @@ -8753,7 +8977,8 @@ packages: optional: true dependencies: '@babel/core': 7.22.20 - react: 17.0.2 + client-only: 0.0.1 + react: 0.0.0-experimental-d5736f09-20260507 dev: false /sucrase@3.27.0: @@ -8805,7 +9030,7 @@ packages: pdfkit: 0.13.0 dev: false - /tailwindcss@3.1.8(postcss@8.4.21): + /tailwindcss@3.1.8(postcss@8.4.31): resolution: {integrity: sha512-YSneUCZSFDYMwk+TGq8qYFdCA3yfBRdBlS7txSq0LUmzyeqRe3a8fBQzbz9M3WS/iFT4BNf/nmw9mEzrnSaC0g==} engines: {node: '>=12.13.0'} hasBin: true @@ -8825,11 +9050,11 @@ packages: normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.0.0 - postcss: 8.4.21 - postcss-import: 14.1.0(postcss@8.4.21) - postcss-js: 4.0.0(postcss@8.4.21) - postcss-load-config: 3.1.4(postcss@8.4.21) - postcss-nested: 5.0.6(postcss@8.4.21) + postcss: 8.4.31 + postcss-import: 14.1.0(postcss@8.4.31) + postcss-js: 4.0.0(postcss@8.4.31) + postcss-load-config: 3.1.4(postcss@8.4.31) + postcss-nested: 5.0.6(postcss@8.4.31) postcss-selector-parser: 6.0.10 postcss-value-parser: 4.2.0 quick-lru: 5.1.1 @@ -8972,10 +9197,10 @@ packages: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} dev: true - /tslib@2.4.0: - resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} + /tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - /tsup@7.1.0(postcss@8.4.21)(typescript@5.1.3): + /tsup@7.1.0(postcss@8.4.31)(typescript@5.1.3): resolution: {integrity: sha512-mazl/GRAk70j8S43/AbSYXGgvRP54oQeX8Un4iZxzATHt0roW0t6HYDVZIXMw0ZQIpvr1nFMniIVnN5186lW7w==} engines: {node: '>=16.14'} hasBin: true @@ -8999,8 +9224,8 @@ packages: execa: 5.1.1 globby: 11.1.0 joycon: 3.1.1 - postcss: 8.4.21 - postcss-load-config: 4.0.1(postcss@8.4.21) + postcss: 8.4.31 + postcss-load-config: 4.0.1(postcss@8.4.31) resolve-from: 5.0.0 rollup: 3.12.1 source-map: 0.8.0-beta.0 @@ -9083,13 +9308,13 @@ packages: turbo-windows-arm64: 1.6.3 dev: true - /twrnc@3.4.0(postcss@8.4.21)(react-native@0.72.4): + /twrnc@3.4.0(postcss@8.4.31)(react-native@0.72.4): resolution: {integrity: sha512-zJzueyC+LUphbrUEyeVmbR7VfAFObq2mr6ALfMytusHxzHJnOMCdxsw2GCTD67nThP1V22QFp9CZ/DulcZMl1g==} peerDependencies: react-native: '>=0.63.0' dependencies: react-native: 0.72.4(@babel/core@7.22.20)(@babel/preset-env@7.22.20)(react@17.0.2) - tailwindcss: 3.1.8(postcss@8.4.21) + tailwindcss: 3.1.8(postcss@8.4.31) transitivePeerDependencies: - postcss - ts-node @@ -9223,6 +9448,7 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: react: 17.0.2 + dev: true /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} diff --git a/src/video/index.ts b/src/video/index.ts new file mode 100644 index 00000000..f2e6a2c4 --- /dev/null +++ b/src/video/index.ts @@ -0,0 +1,167 @@ +import type { ReactNode } from 'react' +import * as sharpNs from 'sharp' +import * as hmeNs from 'h264-mp4-encoder' +import type { SatoriOptions } from '../satori.js' +import satori from '../satori.js' + +export type VideoOptions = Omit & { + width: number + height: number + duration: number + fps?: number + bitrate?: number + quality?: number + groupOfPictures?: number + /** + * Number of frames whose Satori + sharp work can be in flight at once. The + * H.264 encoder still consumes frames strictly in order — concurrency lets + * upcoming frames render while the current one is being encoded, and lets + * sharp use its libuv threadpool for multiple frames in parallel. + * + * Default: 4 (matches the default libuv UV_THREADPOOL_SIZE). + */ + concurrency?: number +} + +export type FrameContext = { + frame: number + progress: number + time: number +} + +export type VideoRenderer = ( + ctx: FrameContext +) => ReactNode | Promise + +type SharpFactory = typeof import('sharp') +type HMEModule = typeof import('h264-mp4-encoder') + +// Resolve both backends at module evaluation. On serverless platforms this +// front-loads the native binding and WASM compilation into cold-start rather +// than the first request. +const sharp: SharpFactory = ((sharpNs as any).default ?? + sharpNs) as SharpFactory +const hme: HMEModule = ((hmeNs as any).default ?? hmeNs) as HMEModule +const { createH264MP4Encoder } = hme + +function computeTotalFrames(durationMs: number, fps: number): number { + return Math.max(1, Math.round((durationMs / 1000) * fps)) +} + +async function rasterize( + svg: string, + width: number, + height: number +): Promise { + return sharp(Buffer.from(svg), { density: 96 }) + .resize(width, height) + .ensureAlpha() + .raw() + .toBuffer() +} + +async function renderFrame( + renderer: VideoRenderer, + satoriOptions: SatoriOptions, + width: number, + height: number, + frame: number, + totalFrames: number, + fps: number +): Promise { + const progress = totalFrames <= 1 ? 0 : frame / (totalFrames - 1) + const time = (frame / fps) * 1000 + const element = await renderer({ frame, progress, time }) + const svg = await satori(element, satoriOptions) + return rasterize(svg, width, height) +} + +export async function video( + renderer: VideoRenderer, + options: VideoOptions +): Promise { + const { + width, + height, + duration, + fps = 30, + bitrate, + quality, + groupOfPictures, + concurrency = 4, + ...rest + } = options + + if (width % 2 !== 0 || height % 2 !== 0) { + throw new Error( + 'satori/video: width and height must be even (H.264 requirement)' + ) + } + if (duration <= 0) { + throw new Error('satori/video: duration must be > 0') + } + if (fps <= 0) { + throw new Error('satori/video: fps must be > 0') + } + if (!Number.isInteger(concurrency) || concurrency < 1) { + throw new Error('satori/video: concurrency must be a positive integer') + } + + const satoriOptions = { ...rest, width, height } as SatoriOptions + const totalFrames = computeTotalFrames(duration, fps) + const windowSize = Math.min(concurrency, totalFrames) + + const encoder = await createH264MP4Encoder() + encoder.width = width + encoder.height = height + encoder.frameRate = fps + if (bitrate != null) encoder.kbps = bitrate + if (quality != null) encoder.quantizationParameter = quality + if (groupOfPictures != null) encoder.groupOfPictures = groupOfPictures + encoder.outputFilename = `satori-${Date.now()}-${Math.random() + .toString(36) + .slice(2)}.mp4` + encoder.initialize() + + // Sliding window of in-flight render promises. Producers run ahead of the + // encoder so Satori + sharp can overlap with the synchronous WASM encode. + const inFlight = new Map>() + const startFrame = (i: number) => { + const p = renderFrame( + renderer, + satoriOptions, + width, + height, + i, + totalFrames, + fps + ) + // Suppress unhandled-rejection warnings for frames we may never await + // (e.g. an earlier frame throws and we bail out of the loop). + p.catch(() => undefined) + inFlight.set(i, p) + } + + let nextStart = 0 + while (nextStart < windowSize) startFrame(nextStart++) + + try { + for (let i = 0; i < totalFrames; i++) { + const rgba = await inFlight.get(i)! + inFlight.delete(i) + if (nextStart < totalFrames) startFrame(nextStart++) + encoder.addFrameRgba(rgba) + } + encoder.finalize() + return encoder.FS.readFile(encoder.outputFilename) + } finally { + try { + encoder.FS.unlink(encoder.outputFilename) + } catch { + // Encoder may have failed before producing the file; nothing to clean up. + } + encoder.delete() + } +} + +export default video diff --git a/test/video.test.tsx b/test/video.test.tsx new file mode 100644 index 00000000..e129116f --- /dev/null +++ b/test/video.test.tsx @@ -0,0 +1,136 @@ +import { beforeAll, describe, expect, it } from 'vitest' +import { mkdir, readFile, writeFile } from 'node:fs/promises' +import { join } from 'node:path' + +import { video } from '../src/video/index.js' +import type { SatoriOptions } from '../src/index.js' + +const W = 960 +const H = 540 +const FPS = 30 +const DURATION_MS = 3000 + +const clamp01 = (t: number) => (t < 0 ? 0 : t > 1 ? 1 : t) +const range = (t: number, a: number, b: number) => clamp01((t - a) / (b - a)) +const easeOutCubic = (t: number) => 1 - Math.pow(1 - t, 3) +const easeOutQuint = (t: number) => 1 - Math.pow(1 - t, 5) + +const TITLE = 'hello world' +const STAGGER = 0.05 + +describe('Video', () => { + let fonts: SatoriOptions['fonts'] + + beforeAll(async () => { + const [bold, regular] = await Promise.all([ + readFile(join(process.cwd(), 'test/assets/Roboto-Bold.ttf')), + readFile(join(process.cwd(), 'test/assets/Roboto-Regular.ttf')), + ]) + fonts = [ + { name: 'Roboto', data: bold, weight: 700, style: 'normal' }, + { name: 'Roboto', data: regular, weight: 400, style: 'normal' }, + ] + }) + + it('renders a kinetic title card to MP4', async () => { + const mp4 = await video( + ({ progress }) => { + const dot = easeOutQuint(range(progress, 0, 0.4)) + const tagline = easeOutCubic(range(progress, 0.7, 1)) + const pulse = 1 + 0.18 * Math.sin(range(progress, 0.8, 1) * Math.PI) + + const hueShift = progress * 30 + const bgInner = `hsl(${250 + hueShift}, 45%, 16%)` + const bgOuter = `hsl(${230 + hueShift}, 40%, 6%)` + + return ( +
+
+ +
+ {Array.from(TITLE).map((ch, i) => { + const start = 0.22 + i * STAGGER + const t = easeOutCubic(range(progress, start, start + 0.5)) + const baseStyle = { + display: 'flex', + fontSize: 96, + fontWeight: 700, + letterSpacing: -4, + opacity: t, + transform: `translateY(${(1 - t) * 70}px)`, + padding: '0 2px', + lineHeight: 1.1, + } as const + if (ch === ' ') { + return
+ } + return ( +
+ {ch} +
+ ) + })} +
+ +
+ enlightened jsx, now in motion +
+
+ ) + }, + { + width: W, + height: H, + duration: DURATION_MS, + fps: FPS, + fonts, + quality: 22, + concurrency: 4, + } + ) + + expect(mp4).toBeInstanceOf(Uint8Array) + expect(mp4.byteLength).toBeGreaterThan(1000) + // ISO base media file: bytes 4..8 spell 'ftyp' + expect(Buffer.from(mp4.slice(4, 8)).toString('ascii')).toBe('ftyp') + + const outDir = join(process.cwd(), 'test', '__video_output__') + await mkdir(outDir, { recursive: true }) + await writeFile(join(outDir, 'title-card.mp4'), mp4) + }, 60_000) +}) diff --git a/tsup.config.ts b/tsup.config.ts index ffa5a696..449fc3ab 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -9,6 +9,7 @@ export default defineConfig({ [isStandaloneBuild ? 'standalone' : 'index']: 'src/index.ts', 'jsx/index': 'src/jsx/index.ts', 'jsx/jsx-runtime': 'src/jsx/jsx-runtime.ts', + 'video/index': 'src/video/index.ts', }, splitting: false, sourcemap: true,