From a174aafb5bc71829c5f7e66a6594fcdee67c67e8 Mon Sep 17 00:00:00 2001 From: sairamg Date: Wed, 19 Nov 2025 11:18:46 +0530 Subject: [PATCH 01/23] test(router): add missing test coverage for keepAlive, reuseComponent, and hooks --- src/router/router.test.js | 293 +++++++++++++++++++++++++++++++++++++- 1 file changed, 292 insertions(+), 1 deletion(-) diff --git a/src/router/router.test.js b/src/router/router.test.js index e37428f8..68944ff4 100644 --- a/src/router/router.test.js +++ b/src/router/router.test.js @@ -16,7 +16,7 @@ */ import test from 'tape' -import { matchHash, getHash, to, navigate, state } from './router.js' +import { matchHash, getHash, to, navigate, state, back } from './router.js' import { stage } from '../launch.js' import Component from '../component.js' import symbols from '../lib/symbols.js' @@ -682,3 +682,294 @@ test('Route meta data is accessible in route object', async (assert) => { ) assert.end() }) + +test('keepAlive: view is cached and can be restored', async (assert) => { + const originalElement = stage.element + let initCallCount = 0 + + stage.element = ({ parent }) => ({ + populate() {}, + set(prop, value) { + if (value && typeof value === 'object' && value.transition && value.transition.end) { + setTimeout(() => value.transition.end(), 0) + } + }, + destroy() {}, + parent, + }) + + const TestComponent1 = Component('TestComponent1', { + template: '', + code: { + render: () => ({ + elms: [ + { + [symbols.holder]: { destroy: () => {} }, + node: {}, + }, + ], + cleanup: () => {}, + }), + effects: [], + init() { + initCallCount++ + }, + }, + }) + + const TestComponent2 = Component('TestComponent2', { + template: '', + code: { + render: () => ({ + elms: [ + { + [symbols.holder]: { destroy: () => {} }, + node: {}, + }, + ], + cleanup: () => {}, + }), + effects: [], + }, + }) + + const host = { + parent: { + [symbols.routes]: [ + { + path: '/page1', + component: TestComponent1, + options: { keepAlive: true, inHistory: true, passFocus: false }, + }, + { + path: '/page2', + component: TestComponent2, + options: { inHistory: true, passFocus: false }, + }, + ], + }, + [symbols.children]: [{}], + [symbols.props]: {}, + } + + to('/page1') + await navigate.call(host) + const page1View = host[symbols.children][host[symbols.children].length - 1] + const initialInitCount = initCallCount + + to('/page2') + await navigate.call(host) + + back.call(host) + await navigate.call(host) + + const restoredView = host[symbols.children][host[symbols.children].length - 1] + assert.equal(restoredView, page1View, 'Should restore the same cached view instance') + assert.equal( + initCallCount, + initialInitCount, + 'Component init should not be called again when restored from cache' + ) + + stage.element = originalElement + assert.end() +}) + +test('reuseComponent: same component instance is reused when navigating to same route', async (assert) => { + const originalElement = stage.element + + stage.element = ({ parent }) => ({ + populate() {}, + set(prop, value) { + if (value && typeof value === 'object' && value.transition && value.transition.end) { + setTimeout(() => value.transition.end(), 0) + } + }, + destroy() {}, + parent, + }) + + const TestComponent = Component('TestComponent', { + template: '', + code: { + render: () => ({ + elms: [ + { + [symbols.holder]: { destroy: () => {} }, + node: {}, + }, + ], + cleanup: () => {}, + }), + effects: [], + }, + }) + + const TestComponent2 = Component('TestComponent2', { + template: '', + code: { + render: () => ({ + elms: [ + { + [symbols.holder]: { destroy: () => {} }, + node: {}, + }, + ], + cleanup: () => {}, + }), + effects: [], + }, + }) + + const host = { + parent: { + [symbols.routes]: [ + { + path: '/page1', + component: TestComponent, + options: { reuseComponent: true, keepAlive: false, inHistory: true, passFocus: false }, + }, + { + path: '/page2', + component: TestComponent2, + options: { inHistory: true, passFocus: false }, + }, + ], + }, + [symbols.children]: [{}], + [symbols.props]: {}, + } + + to('/page1') + await navigate.call(host) + const firstView = host[symbols.children][host[symbols.children].length - 1] + + to('/page2') + await navigate.call(host) + + to('/page1') + await navigate.call(host) + const secondView = host[symbols.children][host[symbols.children].length - 1] + + assert.equal( + firstView, + secondView, + 'Should reuse the same component instance when reuseComponent is true' + ) + + stage.element = originalElement + assert.end() +}) + +test('Edge case: async hook failure with empty history should not cause endless loop', async (assert) => { + const originalElement = stage.element + stage.element = ({ parent }) => ({ + populate() {}, + set() {}, + destroy() {}, + parent, + }) + + const TestComponent = Component('TestComponent', { + template: '', + code: { + render: () => ({ + elms: [ + { + [symbols.holder]: { destroy: () => {} }, + node: {}, + }, + ], + cleanup: () => {}, + }), + effects: [], + }, + }) + + const host = { + parent: { + [symbols.routes]: [ + { + path: '/page1', + component: TestComponent, + hooks: { + async before() { + throw new Error('Hook failed') + }, + }, + }, + ], + [symbols.routerHooks]: { + async beforeEach() { + throw new Error('BeforeEach hook failed') + }, + }, + }, + [symbols.children]: [{}], + [symbols.props]: {}, + } + + to('/page1') + + try { + await navigate.call(host) + } catch { + // Expected to fail + } + + // Wait a bit to ensure no endless loop + await new Promise((resolve) => setTimeout(resolve, 50)) + + // Verify navigation state is properly reset + assert.equal(state.navigating, false, 'Navigation state should be reset after hook failure') + assert.ok(true, 'Should not cause endless loop when hook fails with empty history') + + stage.element = originalElement + assert.end() +}) + +test('Router hooks: beforeEach and before returning false cancel navigation', async (assert) => { + const originalElement = stage.element + stage.element = ({ parent }) => ({ populate() {}, set() {}, parent }) + + const TestComponent = Component('TestComponent', { + template: '', + code: { render: () => ({ elms: [], cleanup: () => {} }), effects: [] }, + }) + + const host = { + parent: { + [symbols.routes]: [ + { path: '/page1', component: TestComponent }, + { + path: '/page2', + component: TestComponent, + hooks: { + before() { + return false + }, + }, + }, + ], + [symbols.routerHooks]: { + beforeEach(to) { + if (to.path === '/page2') { + return false + } + }, + }, + }, + [symbols.children]: [{}], + [symbols.props]: {}, + } + + to('/page1') + await navigate.call(host) + + to('/page2') + await navigate.call(host) + assert.equal(state.path, '/page1', 'Navigation should be cancelled when hooks return false') + + stage.element = originalElement + assert.end() +}) From 689b56e20b05dde514659a2c3db06b3fa91a04a8 Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Thu, 8 Oct 2026 09:16:08 +0200 Subject: [PATCH 02/23] Added profiler utility for internal use and use in app code. --- eslint.config.cjs | 1 + index.d.ts | 59 ++++++++++++++ package.json | 4 + src/launch.js | 12 +++ src/lib/profiler.d.ts | 40 ++++++++++ src/lib/profiler.js | 167 +++++++++++++++++++++++++++++++++++++++ src/lib/profiler.test.js | 119 ++++++++++++++++++++++++++++ 7 files changed, 402 insertions(+) create mode 100644 src/lib/profiler.d.ts create mode 100644 src/lib/profiler.js create mode 100644 src/lib/profiler.test.js diff --git a/eslint.config.cjs b/eslint.config.cjs index f434e01e..4c7ab5cb 100644 --- a/eslint.config.cjs +++ b/eslint.config.cjs @@ -37,6 +37,7 @@ module.exports = defineConfig([ ...globals.browser, ...globals.node, globalThis: false, + __DEV__: false, }, parser: babelParser, diff --git a/index.d.ts b/index.d.ts index ddbcfee2..94193f3e 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1111,6 +1111,15 @@ declare module '@lightningjs/blits' { * the Blits App */ export interface Settings { + /** + * Start the performance profiler when the App launches + * + * The profiler is also available on `globalThis.__BLITS_PROFILER__`, so it + * can be started and stopped from the devtools console + * + * @default false + */ + profiler?: boolean, /** * Width of the Application */ @@ -1477,6 +1486,56 @@ declare module '@lightningjs/blits' { } + export interface ProfilerEntry { + /** + * Number of times the measurement was recorded + */ + calls: number, + /** + * Total time spent, in milliseconds + */ + totalMs: number, + /** + * Slowest single measurement, in milliseconds + */ + maxMs: number, + /** + * Average time per call, in milliseconds + */ + averageMs: number + } + + export interface Profiler { + /** + * Marks the start of a measurement + */ + mark(name: string): void, + /** + * Ends the most recent measurement started with the same name + */ + markEnd(name: string): void, + /** + * Clears previously collected measurements and starts recording + */ + start(): void, + /** + * Stops recording and returns the collected measurements + */ + stop(): Record, + /** + * Clears all collected measurements, without changing the enabled state + */ + reset(): void, + /** + * Returns the measurements collected so far + */ + snapshot(): Record, + /** + * Whether the profiler is currently recording + */ + readonly enabled: boolean + } + /** * Blits App Framework */ diff --git a/package.json b/package.json index 2d734354..2892f666 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,10 @@ "default": "./src/testing/index.js" }, "./symbols": "./src/lib/symbols.js", + "./profiler": { + "types": "./src/lib/profiler.d.ts", + "default": "./src/lib/profiler.js" + }, "./blitsFileConverter": "./src/lib/blitsfileconverter/blitsfileconverter.js", "./components": "./src/components/FPScounter.js" }, diff --git a/src/launch.js b/src/launch.js index f65d9f21..f7340ffc 100644 --- a/src/launch.js +++ b/src/launch.js @@ -20,6 +20,11 @@ import { initLog, Log } from './lib/log.js' import engine from './engine.js' import blitsPackageInfo from '../package.json' with { type: 'json' } import { configurePlatform } from './platform.js' +import profiler from './lib/profiler.js' + +// Vite replaces __DEV__ with a boolean at build time. The typeof check keeps +// the source usable outside Vite (for example in the test runner). +const isDev = typeof __DEV__ !== 'undefined' && __DEV__ /** * @typedef {Object} Font @@ -93,6 +98,7 @@ async function rendererVersion() { * @property {boolean} [enableMouse] - Enable mouse support (hover and click-to-focus). Defaults to false. * @property {number} [maxFPS] - Maximum FPS * @property {function} [platform] - Platform factory for non-browser environments + * @property {boolean} [profiler] - Start the performance profiler on launch */ /** @@ -130,6 +136,12 @@ export default (App, target, settings) => { initLog() + if (isDev === true) { + // expose the profiler, so it can be controlled from the devtools console + globalThis.__BLITS_PROFILER__ = profiler + if (Settings.get('profiler', false) === true) profiler.start() + } + if (warnLegacyRendererPlatform === true) { Log.warn( 'Passing a renderer platform via settings.platform is deprecated. Use settings.rendererPlatform instead.' diff --git a/src/lib/profiler.d.ts b/src/lib/profiler.d.ts new file mode 100644 index 00000000..ce56a0e5 --- /dev/null +++ b/src/lib/profiler.d.ts @@ -0,0 +1,40 @@ +/* + * Copyright 2026 Comcast Cable Communications Management, LLC + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { Profiler, ProfilerEntry } from '@lightningjs/blits' + +export type { Profiler, ProfilerEntry } + +/** + * Marks the start of a measurement. + * + * Returns `0` when the profiler is disabled. + */ +export function profileBegin(): number + +/** + * Records a measurement started with `profileBegin` + */ +export function profileEnd(name: string, start: number): void + +/** + * Measures the execution time of a (synchronous) function + */ +export function profile(name: string, fn: () => T): T + +declare const profiler: Profiler +export default profiler diff --git a/src/lib/profiler.js b/src/lib/profiler.js new file mode 100644 index 00000000..73b0bd68 --- /dev/null +++ b/src/lib/profiler.js @@ -0,0 +1,167 @@ +/* + * Copyright 2026 Comcast Cable Communications Management, LLC + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @typedef {Object} ProfilerEntry + * @property {number} calls - Number of times the measurement was recorded + * @property {number} totalMs - Total time spent, in milliseconds + * @property {number} maxMs - Slowest single measurement, in milliseconds + * @property {number} averageMs - Average time per call, in milliseconds + */ + +const stats = new Map() +const activeMarks = new Map() +let enabled = false + +const hasPerformance = typeof globalThis.performance === 'object' + +const now = () => (hasPerformance === true ? globalThis.performance.now() : Date.now()) + +/** + * Marks the start of a measurement. + * + * Returns `0` when the profiler is disabled, which keeps the overhead + * down to a single boolean check. + * + * @returns {number} Timestamp to pass into {@link profileEnd} + */ +export const profileBegin = () => (enabled === true ? now() : 0) + +/** + * Records a measurement started with {@link profileBegin}. + * + * @param {string} name - Label the measurement is aggregated under + * @param {number} start - Timestamp returned by {@link profileBegin} + * @returns {void} + */ +export const profileEnd = (name, start) => { + if (enabled !== true) return + + let entry = stats.get(name) + if (entry === undefined) { + entry = { calls: 0, totalMs: 0, maxMs: 0 } + stats.set(name, entry) + } + + const elapsed = now() - start + entry.calls++ + entry.totalMs += elapsed + if (elapsed > entry.maxMs) entry.maxMs = elapsed +} + +const mark = (name) => { + if (enabled !== true) return + + let marks = activeMarks.get(name) + if (marks === undefined) { + marks = [] + activeMarks.set(name, marks) + } + marks.push(now()) +} + +const markEnd = (name) => { + if (enabled !== true) return + + const marks = activeMarks.get(name) + if (marks === undefined) return + + const start = marks.pop() + if (marks.length === 0) activeMarks.delete(name) + profileEnd(name, start) +} + +/** + * Measures the execution time of a (synchronous) function. + * + * @template T + * @param {string} name - Label the measurement is aggregated under + * @param {() => T} fn - Function to measure + * @returns {T} The return value of `fn` + */ +export const profile = (name, fn) => { + if (enabled !== true) return fn() + const start = now() + try { + return fn() + } finally { + profileEnd(name, start) + } +} + +const snapshot = () => + Object.fromEntries( + [...stats].map(([name, entry]) => [name, { ...entry, averageMs: entry.totalMs / entry.calls }]) + ) + +/** + * Lightweight, opt-in profiler used to measure performance bottlenecks + * in Blits internals and in App code. + */ +const profiler = { + /** + * Clears previously collected measurements and starts recording + * @returns {void} + */ + start() { + stats.clear() + activeMarks.clear() + enabled = true + }, + /** + * Marks the start of a measurement + * @param {string} name - Label the measurement is aggregated under + * @returns {void} + */ + mark, + /** + * Ends the most recent measurement started with the same name + * @param {string} name - Label passed to {@link mark} + * @returns {void} + */ + markEnd, + /** + * Stops recording and returns the collected measurements + * @returns {Object.} + */ + stop() { + enabled = false + activeMarks.clear() + return snapshot() + }, + /** + * Clears all collected measurements, without changing the enabled state + * @returns {void} + */ + reset() { + stats.clear() + }, + /** + * Returns the measurements collected so far + * @returns {Object.} + */ + snapshot, + /** + * Whether the profiler is currently recording + * @returns {boolean} + */ + get enabled() { + return enabled + }, +} + +export default profiler diff --git a/src/lib/profiler.test.js b/src/lib/profiler.test.js new file mode 100644 index 00000000..9cdebf6c --- /dev/null +++ b/src/lib/profiler.test.js @@ -0,0 +1,119 @@ +/* + * Copyright 2026 Comcast Cable Communications Management, LLC + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import test from 'tape' +import profiler, { profile, profileBegin, profileEnd } from './profiler.js' + +test('Profiler - disabled by default', (assert) => { + assert.equal(profiler.enabled, false, 'profiler should be disabled') + assert.equal(profileBegin(), 0, 'profileBegin should return 0 when disabled') + + profileEnd('disabled', 0) + assert.deepEqual(profiler.snapshot(), {}, 'no measurements should be recorded') + assert.end() +}) + +test('Profiler - records measurements when started', (assert) => { + profiler.start() + assert.equal(profiler.enabled, true, 'profiler should be enabled') + + profileEnd('render', profileBegin()) + profileEnd('render', profileBegin()) + profileEnd('update', profileBegin()) + + const result = profiler.stop() + + assert.equal(profiler.enabled, false, 'profiler should be disabled after stop') + assert.deepEqual(Object.keys(result).sort(), ['render', 'update'], 'should record both labels') + assert.equal(result.render.calls, 2, 'render should be recorded twice') + assert.equal(result.update.calls, 1, 'update should be recorded once') + assert.equal( + result.render.averageMs, + result.render.totalMs / 2, + 'average should be total divided by the number of calls' + ) + assert.ok(result.render.maxMs <= result.render.totalMs, 'max should not exceed the total') + assert.end() +}) + +test('Profiler - named marks record measurements', (assert) => { + profiler.start() + + profiler.mark('render') + profiler.mark('render') + profiler.markEnd('render') + profiler.markEnd('render') + profiler.markEnd('missing') + + const result = profiler.stop() + assert.equal(result.render.calls, 2, 'should record both marks with the same name') + assert.equal(result.missing, undefined, 'an unmatched mark end should be ignored') + assert.end() +}) + +test('Profiler - named marks do not record when disabled', (assert) => { + profiler.reset() + profiler.mark('disabled') + profiler.markEnd('disabled') + + assert.deepEqual(profiler.snapshot(), {}, 'no measurements should be recorded') + assert.end() +}) + +test('Profiler - start clears previous measurements', (assert) => { + profiler.start() + profileEnd('first', profileBegin()) + profiler.start() + + assert.deepEqual(profiler.snapshot(), {}, 'previous measurements should be cleared') + + profileEnd('second', profileBegin()) + profiler.reset() + assert.deepEqual(profiler.snapshot(), {}, 'reset should clear measurements') + assert.equal(profiler.enabled, true, 'reset should not change the enabled state') + + profiler.stop() + assert.end() +}) + +test('Profiler - profile() wraps a function', (assert) => { + profiler.start() + + const result = profile('calculate', () => 42) + assert.equal(result, 42, 'should return the result of the wrapped function') + assert.equal(profiler.snapshot().calculate.calls, 1, 'should record a measurement') + + assert.throws( + () => + profile('failing', () => { + throw new Error('oops') + }), + /oops/, + 'should rethrow errors from the wrapped function' + ) + assert.equal(profiler.snapshot().failing.calls, 1, 'should record a measurement when throwing') + + profiler.stop() + + assert.equal( + profile('ignored', () => 'value'), + 'value', + 'should pass through when disabled' + ) + assert.equal(profiler.snapshot().ignored, undefined, 'should not record when disabled') + assert.end() +}) From 65bb89c196661dece8d7ff6d8b7f6fb03b9ee46a Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Thu, 8 Oct 2026 09:38:15 +0200 Subject: [PATCH 03/23] Added session to profiler to handle measurements between start / reset. --- src/lib/profiler.d.ts | 10 ++++++-- src/lib/profiler.js | 20 +++++++++------- src/lib/profiler.test.js | 50 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 10 deletions(-) diff --git a/src/lib/profiler.d.ts b/src/lib/profiler.d.ts index ce56a0e5..89e339f0 100644 --- a/src/lib/profiler.d.ts +++ b/src/lib/profiler.d.ts @@ -19,17 +19,23 @@ import type { Profiler, ProfilerEntry } from '@lightningjs/blits' export type { Profiler, ProfilerEntry } +/** Session-bound measurement token. Pass unchanged to profileEnd. */ +export interface ProfileToken { + readonly sessionId: number + readonly startTime: number +} + /** * Marks the start of a measurement. * * Returns `0` when the profiler is disabled. */ -export function profileBegin(): number +export function profileBegin(): ProfileToken | 0 /** * Records a measurement started with `profileBegin` */ -export function profileEnd(name: string, start: number): void +export function profileEnd(name: string, start: ProfileToken | 0): void /** * Measures the execution time of a (synchronous) function diff --git a/src/lib/profiler.js b/src/lib/profiler.js index 73b0bd68..31b03a29 100644 --- a/src/lib/profiler.js +++ b/src/lib/profiler.js @@ -26,6 +26,7 @@ const stats = new Map() const activeMarks = new Map() let enabled = false +let sessionId = 0 const hasPerformance = typeof globalThis.performance === 'object' @@ -37,19 +38,19 @@ const now = () => (hasPerformance === true ? globalThis.performance.now() : Date * Returns `0` when the profiler is disabled, which keeps the overhead * down to a single boolean check. * - * @returns {number} Timestamp to pass into {@link profileEnd} + * @returns {{sessionId: number, startTime: number} | 0} Token to pass into {@link profileEnd} */ -export const profileBegin = () => (enabled === true ? now() : 0) +export const profileBegin = () => (enabled === true ? { sessionId, startTime: now() } : 0) /** * Records a measurement started with {@link profileBegin}. * * @param {string} name - Label the measurement is aggregated under - * @param {number} start - Timestamp returned by {@link profileBegin} + * @param {{sessionId: number, startTime: number} | 0} start - Token returned by {@link profileBegin} * @returns {void} */ export const profileEnd = (name, start) => { - if (enabled !== true) return + if (enabled !== true || !start || start.sessionId !== sessionId) return let entry = stats.get(name) if (entry === undefined) { @@ -57,7 +58,7 @@ export const profileEnd = (name, start) => { stats.set(name, entry) } - const elapsed = now() - start + const elapsed = now() - start.startTime entry.calls++ entry.totalMs += elapsed if (elapsed > entry.maxMs) entry.maxMs = elapsed @@ -71,7 +72,7 @@ const mark = (name) => { marks = [] activeMarks.set(name, marks) } - marks.push(now()) + marks.push(profileBegin()) } const markEnd = (name) => { @@ -95,7 +96,7 @@ const markEnd = (name) => { */ export const profile = (name, fn) => { if (enabled !== true) return fn() - const start = now() + const start = profileBegin() try { return fn() } finally { @@ -118,6 +119,7 @@ const profiler = { * @returns {void} */ start() { + sessionId++ stats.clear() activeMarks.clear() enabled = true @@ -144,11 +146,13 @@ const profiler = { return snapshot() }, /** - * Clears all collected measurements, without changing the enabled state + * Clears collected measurements and pending spans, without changing the enabled state * @returns {void} */ reset() { + sessionId++ stats.clear() + activeMarks.clear() }, /** * Returns the measurements collected so far diff --git a/src/lib/profiler.test.js b/src/lib/profiler.test.js index 9cdebf6c..467ce7e1 100644 --- a/src/lib/profiler.test.js +++ b/src/lib/profiler.test.js @@ -18,6 +18,56 @@ import test from 'tape' import profiler, { profile, profileBegin, profileEnd } from './profiler.js' +test('Profiler - rejects measurements from outside the current session', (assert) => { + const disabled = profileBegin() + profiler.start() + profileEnd('disabled', disabled) + const previous = profileBegin() + profiler.start() + profileEnd('previous', previous) + const stopped = profileBegin() + profiler.stop() + profileEnd('stopped', stopped) + profiler.start() + profileEnd('restarted', stopped) + assert.deepEqual(profiler.stop(), {}, 'only current-session spans may be recorded') + assert.end() +}) + +test('Profiler - reset invalidates pending spans and preserves enabled state', (assert) => { + profiler.start() + const pending = profileBegin() + profiler.mark('named') + profiler.mark('named') + profile('recorded', () => 42) + profiler.reset() + profileEnd('pending', pending) + profiler.markEnd('named') + profiler.markEnd('named') + assert.equal(profiler.enabled, true) + assert.deepEqual(profiler.snapshot(), {}, 'reset clears results and all pending spans') + profileEnd('fresh', profileBegin()) + assert.equal(profiler.stop().fresh.calls, 1, 'new spans still record') + profiler.reset() + assert.equal(profiler.enabled, false, 'reset does not enable a stopped profiler') + assert.end() +}) + +test('Profiler - wrappers cannot record across session boundaries', (assert) => { + for (const changeSession of [() => profiler.start(), () => profiler.reset()]) { + profiler.start() + assert.equal( + profile('stale', () => { + changeSession() + return 42 + }), + 42 + ) + assert.deepEqual(profiler.stop(), {}, 'finally ignores a stale session token') + } + assert.end() +}) + test('Profiler - disabled by default', (assert) => { assert.equal(profiler.enabled, false, 'profiler should be disabled') assert.equal(profileBegin(), 0, 'profileBegin should return 0 when disabled') From f84be287e3f5e45819cd15e07c80149e69074504 Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Thu, 8 Oct 2026 12:04:23 +0200 Subject: [PATCH 04/23] Added frame analysis to profiler and improved reporting. --- index.d.ts | 14 ++- src/launch.js | 1 + src/lib/profiler.d.ts | 14 +++ src/lib/profiler.js | 216 ++++++++++++++++++++++++++++++++++++++- src/lib/profiler.test.js | 130 +++++++++++++++++++---- 5 files changed, 350 insertions(+), 25 deletions(-) diff --git a/index.d.ts b/index.d.ts index 94193f3e..6e09d1b9 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1517,11 +1517,11 @@ declare module '@lightningjs/blits' { /** * Clears previously collected measurements and starts recording */ - start(): void, + start(options?: { frames?: boolean, maxFrames?: number, targetFps?: number }): void, /** * Stops recording and returns the collected measurements */ - stop(): Record, + stop(): { measurements: Record, frames: object }, /** * Clears all collected measurements, without changing the enabled state */ @@ -1529,7 +1529,15 @@ declare module '@lightningjs/blits' { /** * Returns the measurements collected so far */ - snapshot(): Record, + snapshot(): { measurements: Record, frames: object }, + /** + * Returns frame-oriented results collected with start({ frames: true }) + */ + frameSnapshot(): object, + /** + * Logs marker and frame results as console tables and returns the combined snapshot + */ + report(): { measurements: Record, frames: object }, /** * Whether the profiler is currently recording */ diff --git a/src/launch.js b/src/launch.js index f7340ffc..8ade5cd5 100644 --- a/src/launch.js +++ b/src/launch.js @@ -156,4 +156,5 @@ export default (App, target, settings) => { stage.element = engine.Element renderer = engine.Launch(App, target, settings) + profiler.setRenderer(renderer) } diff --git a/src/lib/profiler.d.ts b/src/lib/profiler.d.ts index 89e339f0..7448b4d2 100644 --- a/src/lib/profiler.d.ts +++ b/src/lib/profiler.d.ts @@ -25,6 +25,17 @@ export interface ProfileToken { readonly startTime: number } +export interface ProfilerFrameOptions { + frames?: boolean + maxFrames?: number + targetFps?: number +} + +export interface ProfilerSnapshot { + measurements: Record + frames: object +} + /** * Marks the start of a measurement. * @@ -42,5 +53,8 @@ export function profileEnd(name: string, start: ProfileToken | 0): void */ export function profile(name: string, fn: () => T): T +/** Attaches the profiler to a renderer that emits frameTick events. */ +export function setProfilerRenderer(renderer: unknown): void + declare const profiler: Profiler export default profiler diff --git a/src/lib/profiler.js b/src/lib/profiler.js index 31b03a29..961ca1c2 100644 --- a/src/lib/profiler.js +++ b/src/lib/profiler.js @@ -15,6 +15,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { Log } from './log.js' + /** * @typedef {Object} ProfilerEntry * @property {number} calls - Number of times the measurement was recorded @@ -27,11 +29,76 @@ const stats = new Map() const activeMarks = new Map() let enabled = false let sessionId = 0 +let activeSpan +let renderer +let frameTickHandler +let frameConfig +let activeFrame +let frameHistory = [] const hasPerformance = typeof globalThis.performance === 'object' const now = () => (hasPerformance === true ? globalThis.performance.now() : Date.now()) +const percentile = (values, percent) => { + if (values.length === 0) return 0 + const index = Math.min(values.length - 1, Math.floor(values.length * percent)) + return values.slice().sort((a, b) => a - b)[index] +} + +const frameSummary = (values) => ({ + medianMs: percentile(values, 0.5), + p95Ms: percentile(values, 0.95), + p99Ms: percentile(values, 0.99), + maxMs: values.length === 0 ? 0 : Math.max(...values), +}) + +const clearFrames = () => { + activeFrame = undefined + frameHistory = [] +} + +const finishFrame = () => { + if (activeFrame === undefined || activeFrame.sessionId !== sessionId) return + frameHistory.push(activeFrame) + if (frameHistory.length > frameConfig.maxFrames) frameHistory.shift() +} + +const beginFrame = (data) => { + finishFrame() + activeFrame = { + sessionId, + intervalMs: typeof data.delta === 'number' ? data.delta : 0, + totalMs: 0, + labels: new Map(), + } +} + +const onFrameTick = (...args) => { + if (enabled !== true || frameConfig === undefined) return + const data = args[args.length - 1] + if (!data || typeof data.time !== 'number') return + beginFrame(data) +} + +/** + * Attaches optional frame collection to a renderer that emits `frameTick`. + * @param {{on?: Function, off?: Function}} nextRenderer RendererMain instance + * @returns {void} + */ +export const setProfilerRenderer = (nextRenderer) => { + if (renderer === nextRenderer) return + if (renderer && frameTickHandler && typeof renderer.off === 'function') { + renderer.off('frameTick', frameTickHandler) + } + renderer = nextRenderer + frameTickHandler = undefined + if (renderer && typeof renderer.on === 'function') { + frameTickHandler = onFrameTick + renderer.on('frameTick', frameTickHandler) + } +} + /** * Marks the start of a measurement. * @@ -40,7 +107,17 @@ const now = () => (hasPerformance === true ? globalThis.performance.now() : Date * * @returns {{sessionId: number, startTime: number} | 0} Token to pass into {@link profileEnd} */ -export const profileBegin = () => (enabled === true ? { sessionId, startTime: now() } : 0) +export const profileBegin = () => { + if (enabled !== true) return 0 + const token = { + sessionId, + startTime: now(), + parent: activeSpan, + frame: activeFrame, + } + activeSpan = token + return token +} /** * Records a measurement started with {@link profileBegin}. @@ -62,6 +139,20 @@ export const profileEnd = (name, start) => { entry.calls++ entry.totalMs += elapsed if (elapsed > entry.maxMs) entry.maxMs = elapsed + + if (start.frame && start.frame.sessionId === sessionId) { + let frameEntry = start.frame.labels.get(name) + if (frameEntry === undefined) { + frameEntry = { calls: 0, totalMs: 0 } + start.frame.labels.set(name, frameEntry) + } + frameEntry.calls++ + frameEntry.totalMs += elapsed + if (start.parent === undefined || start.parent.frame !== start.frame) + start.frame.totalMs += elapsed + } + + if (activeSpan === start) activeSpan = start.parent } const mark = (name) => { @@ -104,11 +195,111 @@ export const profile = (name, fn) => { } } -const snapshot = () => +const measurementsSnapshot = () => Object.fromEntries( [...stats].map(([name, entry]) => [name, { ...entry, averageMs: entry.totalMs / entry.calls }]) ) +const frameSnapshot = () => { + const config = frameConfig || { maxFrames: 0, targetFrameMs: 1000 / 60 } + const intervals = frameHistory.map((frame) => frame.intervalMs) + const instrumented = frameHistory.map((frame) => frame.totalMs) + const labels = new Map() + for (const frame of frameHistory) { + for (const [name, entry] of frame.labels) { + let total = labels.get(name) + if (total === undefined) { + total = { calls: 0, totalMs: 0 } + labels.set(name, total) + } + total.calls += entry.calls + total.totalMs += entry.totalMs + } + } + const frameCount = frameHistory.length + const slowFrames = intervals.filter((value) => value > config.targetFrameMs * 1.5).length + const slowestFrames = frameHistory + .slice() + .sort((a, b) => b.totalMs - a.totalMs) + .slice(0, 10) + .map((frame) => ({ + intervalMs: frame.intervalMs, + instrumentedMs: frame.totalMs, + labels: Object.fromEntries(frame.labels), + })) + return { + frameCount, + retainedFrameLimit: config.maxFrames, + frameInterval: frameSummary(intervals), + instrumentedTime: frameSummary(instrumented), + slowFramePercent: frameCount === 0 ? 0 : (slowFrames / frameCount) * 100, + slowestFrames, + labels: Object.fromEntries( + [...labels].map(([name, entry]) => [ + name, + { + ...entry, + callsPerFrame: frameCount === 0 ? 0 : entry.calls / frameCount, + averageMsPerFrame: frameCount === 0 ? 0 : entry.totalMs / frameCount, + }, + ]) + ), + } +} + +const snapshot = () => ({ + measurements: measurementsSnapshot(), + frames: frameSnapshot(), +}) + +const report = () => { + const result = snapshot() + const measurements = result.measurements + const frameLabels = result.frames.labels + const slowFrames = Object.fromEntries( + result.frames.slowestFrames.map((frame, index) => [ + `#${index + 1}`, + { + intervalMs: frame.intervalMs, + instrumentedMs: frame.instrumentedMs, + labels: Object.entries(frame.labels) + .sort(([, a], [, b]) => b.totalMs - a.totalMs) + .map( + ([name, entry]) => + `${name}: ${entry.totalMs.toFixed(2)} ms (${entry.calls} ${ + entry.calls === 1 ? 'call' : 'calls' + })` + ) + .join(' | '), + }, + ]) + ) + + Log.info('Profiler summary') + console.table({ + 'Measurements tracked': Object.keys(measurements).length, + 'Frames analyzed': result.frames.frameCount, + 'Median frame interval (ms)': result.frames.frameInterval.medianMs, + 'p95 frame interval (ms)': result.frames.frameInterval.p95Ms, + 'Median instrumented time (ms)': result.frames.instrumentedTime.medianMs, + 'p95 instrumented time (ms)': result.frames.instrumentedTime.p95Ms, + 'Slow frames (%)': result.frames.slowFramePercent, + }) + if (Object.keys(measurements).length) { + Log.info('Profiler: all marker timings') + console.table(measurements) + } + if (Object.keys(frameLabels).length) { + Log.info('Profiler: marker timings within retained frames') + console.table(frameLabels) + } + if (Object.keys(slowFrames).length) { + Log.info('Profiler: most expensive retained frames') + console.table(slowFrames) + } + return result +} + /** * Lightweight, opt-in profiler used to measure performance bottlenecks * in Blits internals and in App code. @@ -118,10 +309,19 @@ const profiler = { * Clears previously collected measurements and starts recording * @returns {void} */ - start() { + start(options = {}) { sessionId++ stats.clear() activeMarks.clear() + activeSpan = undefined + frameConfig = + options.frames === true + ? { + maxFrames: options.maxFrames || 300, + targetFrameMs: 1000 / (options.targetFps || 60), + } + : undefined + clearFrames() enabled = true }, /** @@ -143,6 +343,7 @@ const profiler = { stop() { enabled = false activeMarks.clear() + activeSpan = undefined return snapshot() }, /** @@ -153,12 +354,21 @@ const profiler = { sessionId++ stats.clear() activeMarks.clear() + activeSpan = undefined + clearFrames() }, /** * Returns the measurements collected so far * @returns {Object.} */ snapshot, + report, + setRenderer: setProfilerRenderer, + /** + * Returns frame-oriented measurements collected with start({ frames: true }). + * @returns {object} + */ + frameSnapshot, /** * Whether the profiler is currently recording * @returns {boolean} diff --git a/src/lib/profiler.test.js b/src/lib/profiler.test.js index 467ce7e1..7b419bee 100644 --- a/src/lib/profiler.test.js +++ b/src/lib/profiler.test.js @@ -17,6 +17,23 @@ import test from 'tape' import profiler, { profile, profileBegin, profileEnd } from './profiler.js' +import { initLog } from './log.js' +import Settings from '../settings.js' + +const fakeRenderer = () => { + let handler + return { + on(event, callback) { + if (event === 'frameTick') handler = callback + }, + off(event, callback) { + if (event === 'frameTick' && handler === callback) handler = undefined + }, + frame(time, delta) { + handler(this, { time, delta }) + }, + } +} test('Profiler - rejects measurements from outside the current session', (assert) => { const disabled = profileBegin() @@ -30,7 +47,7 @@ test('Profiler - rejects measurements from outside the current session', (assert profileEnd('stopped', stopped) profiler.start() profileEnd('restarted', stopped) - assert.deepEqual(profiler.stop(), {}, 'only current-session spans may be recorded') + assert.deepEqual(profiler.stop().measurements, {}, 'only current-session spans may be recorded') assert.end() }) @@ -45,9 +62,13 @@ test('Profiler - reset invalidates pending spans and preserves enabled state', ( profiler.markEnd('named') profiler.markEnd('named') assert.equal(profiler.enabled, true) - assert.deepEqual(profiler.snapshot(), {}, 'reset clears results and all pending spans') + assert.deepEqual( + profiler.snapshot().measurements, + {}, + 'reset clears results and all pending spans' + ) profileEnd('fresh', profileBegin()) - assert.equal(profiler.stop().fresh.calls, 1, 'new spans still record') + assert.equal(profiler.stop().measurements.fresh.calls, 1, 'new spans still record') profiler.reset() assert.equal(profiler.enabled, false, 'reset does not enable a stopped profiler') assert.end() @@ -63,7 +84,7 @@ test('Profiler - wrappers cannot record across session boundaries', (assert) => }), 42 ) - assert.deepEqual(profiler.stop(), {}, 'finally ignores a stale session token') + assert.deepEqual(profiler.stop().measurements, {}, 'finally ignores a stale session token') } assert.end() }) @@ -73,7 +94,7 @@ test('Profiler - disabled by default', (assert) => { assert.equal(profileBegin(), 0, 'profileBegin should return 0 when disabled') profileEnd('disabled', 0) - assert.deepEqual(profiler.snapshot(), {}, 'no measurements should be recorded') + assert.deepEqual(profiler.snapshot().measurements, {}, 'no measurements should be recorded') assert.end() }) @@ -88,15 +109,22 @@ test('Profiler - records measurements when started', (assert) => { const result = profiler.stop() assert.equal(profiler.enabled, false, 'profiler should be disabled after stop') - assert.deepEqual(Object.keys(result).sort(), ['render', 'update'], 'should record both labels') - assert.equal(result.render.calls, 2, 'render should be recorded twice') - assert.equal(result.update.calls, 1, 'update should be recorded once') + assert.deepEqual( + Object.keys(result.measurements).sort(), + ['render', 'update'], + 'should record both labels' + ) + assert.equal(result.measurements.render.calls, 2, 'render should be recorded twice') + assert.equal(result.measurements.update.calls, 1, 'update should be recorded once') assert.equal( - result.render.averageMs, - result.render.totalMs / 2, + result.measurements.render.averageMs, + result.measurements.render.totalMs / 2, 'average should be total divided by the number of calls' ) - assert.ok(result.render.maxMs <= result.render.totalMs, 'max should not exceed the total') + assert.ok( + result.measurements.render.maxMs <= result.measurements.render.totalMs, + 'max should not exceed the total' + ) assert.end() }) @@ -110,8 +138,8 @@ test('Profiler - named marks record measurements', (assert) => { profiler.markEnd('missing') const result = profiler.stop() - assert.equal(result.render.calls, 2, 'should record both marks with the same name') - assert.equal(result.missing, undefined, 'an unmatched mark end should be ignored') + assert.equal(result.measurements.render.calls, 2, 'should record both marks with the same name') + assert.equal(result.measurements.missing, undefined, 'an unmatched mark end should be ignored') assert.end() }) @@ -120,7 +148,7 @@ test('Profiler - named marks do not record when disabled', (assert) => { profiler.mark('disabled') profiler.markEnd('disabled') - assert.deepEqual(profiler.snapshot(), {}, 'no measurements should be recorded') + assert.deepEqual(profiler.snapshot().measurements, {}, 'no measurements should be recorded') assert.end() }) @@ -129,11 +157,11 @@ test('Profiler - start clears previous measurements', (assert) => { profileEnd('first', profileBegin()) profiler.start() - assert.deepEqual(profiler.snapshot(), {}, 'previous measurements should be cleared') + assert.deepEqual(profiler.snapshot().measurements, {}, 'previous measurements should be cleared') profileEnd('second', profileBegin()) profiler.reset() - assert.deepEqual(profiler.snapshot(), {}, 'reset should clear measurements') + assert.deepEqual(profiler.snapshot().measurements, {}, 'reset should clear measurements') assert.equal(profiler.enabled, true, 'reset should not change the enabled state') profiler.stop() @@ -145,7 +173,7 @@ test('Profiler - profile() wraps a function', (assert) => { const result = profile('calculate', () => 42) assert.equal(result, 42, 'should return the result of the wrapped function') - assert.equal(profiler.snapshot().calculate.calls, 1, 'should record a measurement') + assert.equal(profiler.snapshot().measurements.calculate.calls, 1, 'should record a measurement') assert.throws( () => @@ -155,7 +183,11 @@ test('Profiler - profile() wraps a function', (assert) => { /oops/, 'should rethrow errors from the wrapped function' ) - assert.equal(profiler.snapshot().failing.calls, 1, 'should record a measurement when throwing') + assert.equal( + profiler.snapshot().measurements.failing.calls, + 1, + 'should record a measurement when throwing' + ) profiler.stop() @@ -164,6 +196,66 @@ test('Profiler - profile() wraps a function', (assert) => { 'value', 'should pass through when disabled' ) - assert.equal(profiler.snapshot().ignored, undefined, 'should not record when disabled') + assert.equal( + profiler.snapshot().measurements.ignored, + undefined, + 'should not record when disabled' + ) + assert.end() +}) + +test('Profiler - frame analysis records bounded complete frames', (assert) => { + const renderer = fakeRenderer() + profiler.setRenderer(renderer) + profiler.start({ frames: true, maxFrames: 2, targetFps: 60 }) + renderer.frame(100, 16) + profile('outer', () => profile('inner', () => 42)) + renderer.frame(120, 20) + profileEnd('work', profileBegin()) + renderer.frame(150, 30) + profileEnd('work', profileBegin()) + renderer.frame(190, 40) + + const frames = profiler.frameSnapshot() + assert.equal(frames.frameCount, 2, 'keeps only the configured number of complete frames') + assert.equal(frames.frameInterval.maxMs, 30, 'uses renderer frame intervals') + assert.equal(frames.labels.work.calls, 2, 'attributes spans to frames') + assert.equal(frames.slowFramePercent, 50, 'counts intervals over 1.5 target frame times') + assert.equal(frames.slowestFrames.length, 2, 'keeps expensive frame attribution for inspection') + const table = console.table + const info = console.info + const tables = [] + const titles = [] + Settings.set('debugLevel', 1) + initLog() + console.table = (value) => tables.push(value) + console.info = (...args) => titles.push(args.at(-1)) + const result = profiler.report() + console.table = table + console.info = info + assert.equal(result.frames.frameCount, 2, 'includes frames in the combined snapshot') + assert.equal( + result.measurements.work.calls, + 2, + 'includes marker measurements in the combined snapshot' + ) + assert.equal(tables.length, 4, 'prints summary, markers, frame labels, and slow frames') + assert.match( + tables[3]['#1'].labels, + /work: \d+\.\d{2} ms \(1 call\)/, + 'shows each expensive frame label contribution' + ) + assert.deepEqual( + titles, + [ + 'Profiler summary', + 'Profiler: all marker timings', + 'Profiler: marker timings within retained frames', + 'Profiler: most expensive retained frames', + ], + 'labels each report table' + ) + profiler.stop() + profiler.setRenderer(undefined) assert.end() }) From 7c991ca62e739d1ebff21005c34feb791a3ea8e4 Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Thu, 8 Oct 2026 12:25:18 +0200 Subject: [PATCH 05/23] Added documentation on profiling and enabled frame analysis by default. --- docs/_sidebar.md | 1 + docs/performance/profiling.md | 80 +++++++++++++++++++++++++++++++++++ index.d.ts | 2 +- src/lib/profiler.js | 4 +- src/lib/profiler.test.js | 8 +++- 5 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 docs/performance/profiling.md diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 8c8828d4..7d6ee0ec 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -47,3 +47,4 @@ - [Animation](/plugins/animation.md) - Performance - [Lazy loading](/performance/lazy-loading.md) + - [Profiling](/performance/profiling.md) diff --git a/docs/performance/profiling.md b/docs/performance/profiling.md new file mode 100644 index 00000000..22949450 --- /dev/null +++ b/docs/performance/profiling.md @@ -0,0 +1,80 @@ +# Profiling + +Blits includes a small profiler that is used internally to find and address bottlenecks in the Blits codebase itself. You can also use it in App code to measure work that is specific to your application. + +Note that the profiler is tree-shaken from production builds. It is intended for local development and performance investigation. + +## Profiling App code + +Import the profiler and start a recording before the interaction or workload you want to inspect. + +```js +import profiler from '@lightningjs/blits/profiler' + +profiler.start() +``` + +Use `mark()` before the work and `markEnd()` afterwards. The supplied name groups calls in the report. + +```js +methods: { + updateProducts() { + profiler.mark('products.sort') + this.products = [...this.products].sort((a, b) => a.price - b.price) + profiler.markEnd('products.sort') + }, +} +``` + +When the workload is complete, stop the recording and print the report. + +```js +profiler.stop() +profiler.report() +``` + +The report shows all recorded marker timings, marker timings within retained frames, and the most expensive retained frames. Frame analysis also reports frame interval and instrumented-time percentiles. + +If the measured code can throw or return early, pair the markers with `try` and `finally`. + +```js +profiler.mark('products.filter') +try { + this.filteredProducts = this.products.filter(matchesSearch) +} finally { + profiler.markEnd('products.filter') +} +``` + +## The profile helper + +`profile()` is useful when wrapping a synchronous expression is more convenient. It calls `markEnd()` in a `finally` block, but changes the shape of the measured code. + +```js +import { profile } from '@lightningjs/blits/profiler' + +const sortedProducts = profile('products.sort', () => + [...this.products].sort((a, b) => a.price - b.price) +) +``` + +## Frame analysis + +Frame analysis is enabled by default. The profiler retains the latest 300 complete frames. You can change the limit and target frame rate when needed, or disable frame analysis when it is not needed. + +```js +profiler.start({ + maxFrames: 600, + targetFps: 60, +}) +``` + +```js +profiler.start({ frames: false }) +``` + +The frame report helps distinguish a marker that is expensive per call from one that is cheap but runs many times per frame. + +## Browser console + +In development, Blits exposes the profiler as `__BLITS_PROFILER__` in the browser console. This is useful when you want to start, stop or report on a recording without adding temporary App code. diff --git a/index.d.ts b/index.d.ts index 6e09d1b9..83f79087 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1531,7 +1531,7 @@ declare module '@lightningjs/blits' { */ snapshot(): { measurements: Record, frames: object }, /** - * Returns frame-oriented results collected with start({ frames: true }) + * Returns frame-oriented results collected by default or with start({ frames: true }) */ frameSnapshot(): object, /** diff --git a/src/lib/profiler.js b/src/lib/profiler.js index 961ca1c2..f9fe58f4 100644 --- a/src/lib/profiler.js +++ b/src/lib/profiler.js @@ -315,7 +315,7 @@ const profiler = { activeMarks.clear() activeSpan = undefined frameConfig = - options.frames === true + options.frames !== false ? { maxFrames: options.maxFrames || 300, targetFrameMs: 1000 / (options.targetFps || 60), @@ -365,7 +365,7 @@ const profiler = { report, setRenderer: setProfilerRenderer, /** - * Returns frame-oriented measurements collected with start({ frames: true }). + * Returns frame-oriented measurements collected by default or with start({ frames: true }). * @returns {object} */ frameSnapshot, diff --git a/src/lib/profiler.test.js b/src/lib/profiler.test.js index 7b419bee..73f8b636 100644 --- a/src/lib/profiler.test.js +++ b/src/lib/profiler.test.js @@ -207,7 +207,7 @@ test('Profiler - profile() wraps a function', (assert) => { test('Profiler - frame analysis records bounded complete frames', (assert) => { const renderer = fakeRenderer() profiler.setRenderer(renderer) - profiler.start({ frames: true, maxFrames: 2, targetFps: 60 }) + profiler.start({ maxFrames: 2, targetFps: 60 }) renderer.frame(100, 16) profile('outer', () => profile('inner', () => 42)) renderer.frame(120, 20) @@ -217,7 +217,11 @@ test('Profiler - frame analysis records bounded complete frames', (assert) => { renderer.frame(190, 40) const frames = profiler.frameSnapshot() - assert.equal(frames.frameCount, 2, 'keeps only the configured number of complete frames') + assert.equal( + frames.frameCount, + 2, + 'starts frame analysis by default and keeps the configured frames' + ) assert.equal(frames.frameInterval.maxMs, 30, 'uses renderer frame intervals') assert.equal(frames.labels.work.calls, 2, 'attributes spans to frames') assert.equal(frames.slowFramePercent, 50, 'counts intervals over 1.5 target frame times') From e10e859a4f043e27d3cdc9593343d2ddf3e01d93 Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Thu, 8 Oct 2026 12:44:01 +0200 Subject: [PATCH 06/23] Added documentation about intereacting with profiler from the console. --- docs/performance/profiling.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/performance/profiling.md b/docs/performance/profiling.md index 22949450..0b6a2725 100644 --- a/docs/performance/profiling.md +++ b/docs/performance/profiling.md @@ -77,4 +77,4 @@ The frame report helps distinguish a marker that is expensive per call from one ## Browser console -In development, Blits exposes the profiler as `__BLITS_PROFILER__` in the browser console. This is useful when you want to start, stop or report on a recording without adding temporary App code. +In development, Blits exposes the profiler as `__BLITS_PROFILER__` in the browser console. This is useful when you want to start, stop or report on a recording without adding temporary App code for that. The markers will need to be in the actual app code, of course. From 4aaf39e80b5c71a81979540b24b267384157e549 Mon Sep 17 00:00:00 2001 From: il-sairamg Date: Thu, 8 Oct 2026 16:50:32 +0530 Subject: [PATCH 07/23] test(router): update tests --- src/router/router.test.js | 227 +------------------------------------- 1 file changed, 1 insertion(+), 226 deletions(-) diff --git a/src/router/router.test.js b/src/router/router.test.js index 231daaf8..96cef0d4 100644 --- a/src/router/router.test.js +++ b/src/router/router.test.js @@ -2579,184 +2579,6 @@ test('reuseComponent reuses the same view instance when returning to the route', stage.element = originalElement }) -test('keepAlive: view is cached and can be restored', async (assert) => { - const originalElement = stage.element - let initCallCount = 0 - - stage.element = ({ parent }) => ({ - populate() {}, - set(prop, value) { - if (value && typeof value === 'object' && value.transition && value.transition.end) { - setTimeout(() => value.transition.end(), 0) - } - }, - destroy() {}, - parent, - }) - - const TestComponent1 = Component('TestComponent1', { - template: '', - code: { - render: () => ({ - elms: [ - { - [symbols.holder]: { destroy: () => {} }, - node: {}, - }, - ], - cleanup: () => {}, - }), - effects: [], - init() { - initCallCount++ - }, - }, - }) - - const TestComponent2 = Component('TestComponent2', { - template: '', - code: { - render: () => ({ - elms: [ - { - [symbols.holder]: { destroy: () => {} }, - node: {}, - }, - ], - cleanup: () => {}, - }), - effects: [], - }, - }) - - const host = { - parent: { - [symbols.routes]: [ - { - path: '/page1', - component: TestComponent1, - options: { keepAlive: true, inHistory: true, passFocus: false }, - }, - { - path: '/page2', - component: TestComponent2, - options: { inHistory: true, passFocus: false }, - }, - ], - }, - [symbols.children]: [{}], - [symbols.props]: {}, - } - - to('/page1') - await navigate.call(host) - const page1View = host[symbols.children][host[symbols.children].length - 1] - const initialInitCount = initCallCount - - to('/page2') - await navigate.call(host) - - back.call(host) - await navigate.call(host) - - const restoredView = host[symbols.children][host[symbols.children].length - 1] - assert.equal(restoredView, page1View, 'Should restore the same cached view instance') - assert.equal( - initCallCount, - initialInitCount, - 'Component init should not be called again when restored from cache' - ) - - stage.element = originalElement - assert.end() -}) - -test('reuseComponent: same component instance is reused when navigating to same route', async (assert) => { - const originalElement = stage.element - - stage.element = ({ parent }) => ({ - populate() {}, - set(prop, value) { - if (value && typeof value === 'object' && value.transition && value.transition.end) { - setTimeout(() => value.transition.end(), 0) - } - }, - destroy() {}, - parent, - }) - - const TestComponent = Component('TestComponent', { - template: '', - code: { - render: () => ({ - elms: [ - { - [symbols.holder]: { destroy: () => {} }, - node: {}, - }, - ], - cleanup: () => {}, - }), - effects: [], - }, - }) - - const TestComponent2 = Component('TestComponent2', { - template: '', - code: { - render: () => ({ - elms: [ - { - [symbols.holder]: { destroy: () => {} }, - node: {}, - }, - ], - cleanup: () => {}, - }), - effects: [], - }, - }) - - const host = { - parent: { - [symbols.routes]: [ - { - path: '/page1', - component: TestComponent, - options: { reuseComponent: true, keepAlive: false, inHistory: true, passFocus: false }, - }, - { - path: '/page2', - component: TestComponent2, - options: { inHistory: true, passFocus: false }, - }, - ], - }, - [symbols.children]: [{}], - [symbols.props]: {}, - } - - to('/page1') - await navigate.call(host) - const firstView = host[symbols.children][host[symbols.children].length - 1] - - to('/page2') - await navigate.call(host) - - to('/page1') - await navigate.call(host) - const secondView = host[symbols.children][host[symbols.children].length - 1] - - assert.equal( - firstView, - secondView, - 'Should reuse the same component instance when reuseComponent is true' - ) - - stage.element = originalElement - assert.end() -}) - test('Edge case: async hook failure with empty history should not cause endless loop', async (assert) => { const originalElement = stage.element stage.element = ({ parent }) => ({ @@ -2783,7 +2605,7 @@ test('Edge case: async hook failure with empty history should not cause endless }) const host = { - parent: { + [symbols.parent]: { [symbols.routes]: [ { path: '/page1', @@ -2821,51 +2643,4 @@ test('Edge case: async hook failure with empty history should not cause endless assert.ok(true, 'Should not cause endless loop when hook fails with empty history') stage.element = originalElement - assert.end() -}) - -test('Router hooks: beforeEach and before returning false cancel navigation', async (assert) => { - const originalElement = stage.element - stage.element = ({ parent }) => ({ populate() {}, set() {}, parent }) - - const TestComponent = Component('TestComponent', { - template: '', - code: { render: () => ({ elms: [], cleanup: () => {} }), effects: [] }, - }) - - const host = { - parent: { - [symbols.routes]: [ - { path: '/page1', component: TestComponent }, - { - path: '/page2', - component: TestComponent, - hooks: { - before() { - return false - }, - }, - }, - ], - [symbols.routerHooks]: { - beforeEach(to) { - if (to.path === '/page2') { - return false - } - }, - }, - }, - [symbols.children]: [{}], - [symbols.props]: {}, - } - - to('/page1') - await navigate.call(host) - - to('/page2') - await navigate.call(host) - assert.equal(state.path, '/page1', 'Navigation should be cancelled when hooks return false') - - stage.element = originalElement - assert.end() }) From 3d4194b3aa065bb5bda2a66b5db7c6ecec442cbe Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Thu, 8 Oct 2026 15:31:13 +0200 Subject: [PATCH 08/23] Fixed profiler frame interval attribution. --- docs/performance/profiling.md | 2 +- src/lib/profiler.js | 7 ++++--- src/lib/profiler.test.js | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/performance/profiling.md b/docs/performance/profiling.md index 0b6a2725..b4b6a813 100644 --- a/docs/performance/profiling.md +++ b/docs/performance/profiling.md @@ -60,7 +60,7 @@ const sortedProducts = profile('products.sort', () => ## Frame analysis -Frame analysis is enabled by default. The profiler retains the latest 300 complete frames. You can change the limit and target frame rate when needed, or disable frame analysis when it is not needed. +Frame analysis is enabled by default. A frame is a renderer-frame sample bounded by consecutive `frameTick` events. The profiler retains the latest 300 complete frames. You can change the limit and target frame rate when needed, or disable frame analysis when it is not needed. ```js profiler.start({ diff --git a/src/lib/profiler.js b/src/lib/profiler.js index f9fe58f4..10ad04d8 100644 --- a/src/lib/profiler.js +++ b/src/lib/profiler.js @@ -58,17 +58,18 @@ const clearFrames = () => { frameHistory = [] } -const finishFrame = () => { +const finishFrame = (data) => { if (activeFrame === undefined || activeFrame.sessionId !== sessionId) return + activeFrame.intervalMs = typeof data.delta === 'number' ? data.delta : 0 frameHistory.push(activeFrame) if (frameHistory.length > frameConfig.maxFrames) frameHistory.shift() } const beginFrame = (data) => { - finishFrame() + finishFrame(data) activeFrame = { sessionId, - intervalMs: typeof data.delta === 'number' ? data.delta : 0, + intervalMs: 0, totalMs: 0, labels: new Map(), } diff --git a/src/lib/profiler.test.js b/src/lib/profiler.test.js index 73f8b636..693b7098 100644 --- a/src/lib/profiler.test.js +++ b/src/lib/profiler.test.js @@ -222,9 +222,9 @@ test('Profiler - frame analysis records bounded complete frames', (assert) => { 2, 'starts frame analysis by default and keeps the configured frames' ) - assert.equal(frames.frameInterval.maxMs, 30, 'uses renderer frame intervals') + assert.equal(frames.frameInterval.maxMs, 40, 'uses the closing renderer frame interval') assert.equal(frames.labels.work.calls, 2, 'attributes spans to frames') - assert.equal(frames.slowFramePercent, 50, 'counts intervals over 1.5 target frame times') + assert.equal(frames.slowFramePercent, 100, 'counts intervals over 1.5 target frame times') assert.equal(frames.slowestFrames.length, 2, 'keeps expensive frame attribution for inspection') const table = console.table const info = console.info From 6561dca0c9c2f8565eb6477d981378035f4a90d3 Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Fri, 9 Oct 2026 10:55:38 +0200 Subject: [PATCH 09/23] Added dev condition around profiler.setRenderer call to ensure proper tree shaking. --- src/launch.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/launch.js b/src/launch.js index 8ade5cd5..92685ad1 100644 --- a/src/launch.js +++ b/src/launch.js @@ -156,5 +156,7 @@ export default (App, target, settings) => { stage.element = engine.Element renderer = engine.Launch(App, target, settings) - profiler.setRenderer(renderer) + if (isDev === true) { + profiler.setRenderer(renderer) + } } From f9387c8fd34674128c9c5279684b2ea17cec59eb Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Fri, 9 Oct 2026 11:04:38 +0200 Subject: [PATCH 10/23] Fixed issue with potentially overlapping markers corrupting results. --- src/lib/profiler.js | 6 +++++- src/lib/profiler.test.js | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/lib/profiler.js b/src/lib/profiler.js index 10ad04d8..342e196c 100644 --- a/src/lib/profiler.js +++ b/src/lib/profiler.js @@ -137,6 +137,7 @@ export const profileEnd = (name, start) => { } const elapsed = now() - start.startTime + start.closed = true entry.calls++ entry.totalMs += elapsed if (elapsed > entry.maxMs) entry.maxMs = elapsed @@ -153,7 +154,10 @@ export const profileEnd = (name, start) => { start.frame.totalMs += elapsed } - if (activeSpan === start) activeSpan = start.parent + if (activeSpan === start) { + activeSpan = start.parent + while (activeSpan !== undefined && activeSpan.closed === true) activeSpan = activeSpan.parent + } } const mark = (name) => { diff --git a/src/lib/profiler.test.js b/src/lib/profiler.test.js index 693b7098..eaad0c4b 100644 --- a/src/lib/profiler.test.js +++ b/src/lib/profiler.test.js @@ -263,3 +263,25 @@ test('Profiler - frame analysis records bounded complete frames', (assert) => { profiler.setRenderer(undefined) assert.end() }) + +test('Profiler - out-of-order marks do not hide later frame totals', (assert) => { + const renderer = fakeRenderer() + profiler.setRenderer(renderer) + profiler.start() + renderer.frame(100, 16) + + profiler.mark('first') + profiler.mark('second') + profiler.markEnd('first') + profiler.markEnd('second') + profiler.mark('independent') + setTimeout(() => { + profiler.markEnd('independent') + renderer.frame(120, 20) + const frame = profiler.frameSnapshot().slowestFrames[0] + assert.ok(frame.instrumentedMs >= 10, 'includes work after the out-of-order marks close') + assert.ok(frame.labels.independent.totalMs >= 10, 'records the independent marker in the frame') + profiler.stop() + assert.end() + }, 20) +}) From 68bc942fa66e213151f414616f5f0cc49be3459c Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Fri, 9 Oct 2026 11:15:33 +0200 Subject: [PATCH 11/23] Added types for snapshots. --- index.d.ts | 42 ++++++++++++++++++++++++++++++++++++++---- src/lib/profiler.d.ts | 16 +++++++++------- 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/index.d.ts b/index.d.ts index 83f79087..e2f35a76 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1505,6 +1505,40 @@ declare module '@lightningjs/blits' { averageMs: number } + export interface ProfilerFrameSummary { + medianMs: number, + p95Ms: number, + p99Ms: number, + maxMs: number + } + + export interface ProfilerFrameLabel { + calls: number, + totalMs: number + } + + export interface ProfilerFrameSnapshot { + frameCount: number, + retainedFrameLimit: number, + frameInterval: ProfilerFrameSummary, + instrumentedTime: ProfilerFrameSummary, + slowFramePercent: number, + slowestFrames: Array<{ + intervalMs: number, + instrumentedMs: number, + labels: Record + }>, + labels: Record + } + + export interface ProfilerSnapshot { + measurements: Record, + frames: ProfilerFrameSnapshot + } + export interface Profiler { /** * Marks the start of a measurement @@ -1521,7 +1555,7 @@ declare module '@lightningjs/blits' { /** * Stops recording and returns the collected measurements */ - stop(): { measurements: Record, frames: object }, + stop(): ProfilerSnapshot, /** * Clears all collected measurements, without changing the enabled state */ @@ -1529,15 +1563,15 @@ declare module '@lightningjs/blits' { /** * Returns the measurements collected so far */ - snapshot(): { measurements: Record, frames: object }, + snapshot(): ProfilerSnapshot, /** * Returns frame-oriented results collected by default or with start({ frames: true }) */ - frameSnapshot(): object, + frameSnapshot(): ProfilerFrameSnapshot, /** * Logs marker and frame results as console tables and returns the combined snapshot */ - report(): { measurements: Record, frames: object }, + report(): ProfilerSnapshot, /** * Whether the profiler is currently recording */ diff --git a/src/lib/profiler.d.ts b/src/lib/profiler.d.ts index 7448b4d2..e79194b2 100644 --- a/src/lib/profiler.d.ts +++ b/src/lib/profiler.d.ts @@ -15,9 +15,16 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type { Profiler, ProfilerEntry } from '@lightningjs/blits' +import type { Profiler } from '@lightningjs/blits' -export type { Profiler, ProfilerEntry } +export type { + Profiler, + ProfilerEntry, + ProfilerFrameLabel, + ProfilerFrameSummary, + ProfilerFrameSnapshot, + ProfilerSnapshot, +} from '@lightningjs/blits' /** Session-bound measurement token. Pass unchanged to profileEnd. */ export interface ProfileToken { @@ -31,11 +38,6 @@ export interface ProfilerFrameOptions { targetFps?: number } -export interface ProfilerSnapshot { - measurements: Record - frames: object -} - /** * Marks the start of a measurement. * From cd3cae201125fa0fc19cd2802f5ee6be6235eadb Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Fri, 16 Oct 2026 17:10:03 +0200 Subject: [PATCH 12/23] Added basic performance benchmarking. --- benchmarks/.gitignore | 3 + benchmarks/README.md | 26 + benchmarks/index.html | 14 + benchmarks/package-lock.json | 896 +++++++++ benchmarks/package.json | 16 + benchmarks/public/.gitkeep | 1 + benchmarks/public/fonts/OpenSans-Medium.ttf | Bin 0 -> 130980 bytes benchmarks/runner.js | 55 + benchmarks/src/Benchmark.js | 82 + benchmarks/src/benchmark-utils.js | 138 ++ benchmarks/src/main.js | 19 + benchmarks/vite.config.js | 9 + package-lock.json | 1793 ++++++++++++++++++- package.json | 4 + 14 files changed, 3048 insertions(+), 8 deletions(-) create mode 100644 benchmarks/.gitignore create mode 100644 benchmarks/README.md create mode 100644 benchmarks/index.html create mode 100644 benchmarks/package-lock.json create mode 100644 benchmarks/package.json create mode 100644 benchmarks/public/.gitkeep create mode 100644 benchmarks/public/fonts/OpenSans-Medium.ttf create mode 100644 benchmarks/runner.js create mode 100644 benchmarks/src/Benchmark.js create mode 100644 benchmarks/src/benchmark-utils.js create mode 100644 benchmarks/src/main.js create mode 100644 benchmarks/vite.config.js diff --git a/benchmarks/.gitignore b/benchmarks/.gitignore new file mode 100644 index 00000000..7f924016 --- /dev/null +++ b/benchmarks/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +results/ diff --git a/benchmarks/README.md b/benchmarks/README.md new file mode 100644 index 00000000..7c884d3b --- /dev/null +++ b/benchmarks/README.md @@ -0,0 +1,26 @@ +# Element generation benchmark + +One workload: clear a keyed loop and generate Elements again. Data creation and clearing happen outside the measured generation time. The default run uses 1,000 Elements, five warmups, and 20 measured samples. Each sample verifies the number of live rendered Elements. + +Install dependencies in this folder with `npm install` and install Chromium with `npx playwright install chromium`. + +From the repository root: + +```sh +npm run bench +npm run bench -- --size 100 --samples 5 --warmup 2 +npm run bench -- --headed +npm run bench:serve +``` + +For browser inspection, open the URL printed by `bench:serve` and press **Enter**. The loop is cleared and recreated on every repetition, and the final sample stays visible. Results are logged in the browser console. Options can be supplied in the URL: + +```text +http://localhost:5173/?size=1000&warmup=5&samples=20 +``` + +Browser and CLI runs use the same workload. The CLI applies a 20x Chromium CPU slowdown; manual browser runs use the browser's current CPU settings. Use DevTools CPU throttling for a manual slowdown. + +Results include synchronous generation time (`generationSyncMs`) and elapsed time until the renderer completes an active → idle transition (`totalToIdleMs`). Both metrics report median, arithmetic average, minimum, and maximum in the browser console table and CLI JSON (for example, `medianGenerationSyncMs`, `averageGenerationSyncMs`, `minGenerationSyncMs`, and `maxGenerationSyncMs`). Each sample also waits for renderer idle after clearing the previous loop, outside the measured generation time. Idle means the renderer has no scene updates to process; this measurement includes frame scheduling and does not establish GPU completion. Each idle wait has a 30-second timeout. Warmup samples are excluded from all statistics. Compare repeated runs on the same machine with the same browser and settings. + +`npm run bench:profile` enables development profiling while running this workload. Profile timings should be compared separately from production benchmark timings. diff --git a/benchmarks/index.html b/benchmarks/index.html new file mode 100644 index 00000000..1e0b1d27 --- /dev/null +++ b/benchmarks/index.html @@ -0,0 +1,14 @@ + + + + + Blits benchmark + + + +
+ + + diff --git a/benchmarks/package-lock.json b/benchmarks/package-lock.json new file mode 100644 index 00000000..e1db4cf9 --- /dev/null +++ b/benchmarks/package-lock.json @@ -0,0 +1,896 @@ +{ + "name": "@lightningjs/blits-benchmarks", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@lightningjs/blits-benchmarks", + "devDependencies": { + "@lightningjs/blits": "file:..", + "playwright": "^1.62.1", + "vite": "^8.2.2" + } + }, + "..": { + "name": "@lightningjs/blits", + "version": "2.12.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@lightningjs/renderer": "^3.6.0", + "magic-string": "^1.4.1" + }, + "bin": { + "blits": "bin/index.js" + }, + "devDependencies": { + "@babel/eslint-parser": "^7.28.6", + "@babel/plugin-syntax-import-assertions": "^7.28.6", + "@eslint/eslintrc": "^3.3.4", + "@eslint/js": "^9.39.3", + "@lightningjs/msdf-generator": "^1.4.0", + "c8": "^12.0.0", + "eslint": "^9.39.3", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.4", + "fast-glob": "^3.3.3", + "global-jsdom": "^30.0.0", + "globals": "^17.12.0", + "husky": "^9.1.7", + "jsdom": "^30.1.0", + "lint-staged": "^15.5.2", + "prettier": "^3.6.2", + "sinon": "^22.1.0", + "tap-diff": "^0.1.1", + "tap-filter": "^1.0.3", + "tape": "^5.9.0" + } + }, + "node_modules/@lightningjs/blits": { + "resolved": "..", + "link": true + }, + "node_modules/@oxc-project/types": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.153.0.tgz", + "integrity": "sha512-jSNrRIyvPVLL5E1z/tS5zLAprosGz/FuI1PWwEOz59j3E4TTTzSpqKb/RDGqJhhW0QHYISGXxq582wGx6T11Tw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/oxc-project" + } + }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.13.tgz", + "integrity": "sha512-Vu/NICrgdRvUOeaJZIyB2FP63s6fO/4ud5l8dXmxDx9yDyn+mN0y+gsvsVviSli+T361Aqo0h46EuMoobrwawA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.13.tgz", + "integrity": "sha512-6LEoIrGy5vvCd2boNnv2soPSUZj/PRvhp8R/t9KlX+mIZxThIE/ZqxrGXxmuNX0FuvQFvLdZr7S4HrYpYBVhEQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.13.tgz", + "integrity": "sha512-CaJSRzlQ6Q+zkDMVA9OkVxBjVoPRCDEE3v0FSJ9kIQBgRjFApA3Si4ijhbNVUji7ZJt+PfO0e46qy4nPInHUPA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.13.tgz", + "integrity": "sha512-e4r340I5hxSzJkD3YJBxgzq/rJ3wwxwdLLb2FPt1Zpyem20nrQPT11DDEEkazwxaHumH8wpglsp/Ozgj/wT6mw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.13.tgz", + "integrity": "sha512-hoBWDZ4/K/mJKVMbsgUd1jfpA2DiOrJ54mpegObU9TBHBBrtX4FLIXJJQRDPZ/2Dn91GvPob+Z2hwKJzf4E5SQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.13.tgz", + "integrity": "sha512-oF9fZu3vTWnTiVAkDFxU9PJLt1nh20K9RRpuHsR7ZnE2L7XookBbDBVLojCVSLGbvtSlxeUNnjAj6DbNsKyzbg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.13.tgz", + "integrity": "sha512-DTpb/+jMiqWMPgphn4ksW1zW+XLJ6dJJ9FMvnIk9I291LhCctMPhMYJrP5t9p6C9qa3KXadDdUEZHBaPh3kNxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.13.tgz", + "integrity": "sha512-xU+Kvv9N2A3SY35zFh1FWWNjxbadE/BKeqnkXe7vScU4GZsw757BlVmDLITZEq8DPpk6m7uoDYkK7P+x/uU5Ow==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.13.tgz", + "integrity": "sha512-XMk/hnhh25oQ756Lclb1Q11h4ll0xC0Esa5w4Vzp+mAE2S3AGJgeM0SonrIgY8fHhjarb6hcoTuwoFG0VVP2ig==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.13.tgz", + "integrity": "sha512-U1BoTHYmlywE2n3HUWjzZ+8EQsKr+OwpxYuDWaubx42tUL8QSL7nOqRz/NVHanibbFP3M1Rxuprz/wNcbMKjqw==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.13.tgz", + "integrity": "sha512-J4cm80Pc6wEwV5AZ42o62sIQgDk+Y7R2s646jIwuvO+4V46QB58N4vVuMPWCBaNgursXLpmUWJV//WQAkPvHBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.13.tgz", + "integrity": "sha512-W0euZS40U4V6JkmEnVCIX9mcsL6cbXQ4X8c2w+MMOIjFg68TXUW+u3RT+2e1t1pv9mg/EP3pDtK3POXdQ7Rxyw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.13.tgz", + "integrity": "sha512-do/AF/4n9MRshia3a5aXoy8xM2c6mgfT6D7H/dY6my5z+hfCIR4ab7mk4IRj++nsEQpKy39kd0DQnDq+r692tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.13.tgz", + "integrity": "sha512-6FtUf2cJZobutJdxnGYX8z0vtbgqU0b2v8vfBhkXb9wp2MppMFtkunE+IaOntyi01rkIQoPW2GHABtfzsC0BRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.13.tgz", + "integrity": "sha512-UKhFaZTeTcbKrb3iU82m17jVcPEAoqDkCN5MBqgPyZSwp/cQmib21crTS0DdH7T1B7wZRWEI89YQAOJMet5yhg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/nanoid": { + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/playwright": { + "version": "1.64.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.64.0.tgz", + "integrity": "sha512-kVzTnYFYEyQ9RGzBjNCd2+yEqWmVbxl3AHPynv3qQ6JTjv4a+kwRcnqQwIpuP3NnrnIrE/Q6wWAIAvMfIE2/Yg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.64.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/playwright-core": { + "version": "1.64.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.64.0.tgz", + "integrity": "sha512-T9r+MZkTECl2+oUcZ26YgZzTrYidTsIhYYkJix+6iDOymOb1FB2RLLSOxRGnZGv5DStt+aduIVbt+E8WygXYpg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/postcss": { + "version": "8.5.29", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.29.tgz", + "integrity": "sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.19", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rolldown": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.13.tgz", + "integrity": "sha512-/5apOkwWxtACcQtZ5WlrAsKS4JwXVThQPJZ3RqIruRY9UG9M8e0VlXN068xum/JRKA4aIUoJ13ghe/kGCvidiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.153.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.13", + "@rolldown/binding-android-arm64": "1.2.13", + "@rolldown/binding-darwin-arm64": "1.2.13", + "@rolldown/binding-darwin-x64": "1.2.13", + "@rolldown/binding-freebsd-x64": "1.2.13", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.13", + "@rolldown/binding-linux-arm64-gnu": "1.2.13", + "@rolldown/binding-linux-arm64-musl": "1.2.13", + "@rolldown/binding-linux-ppc64-gnu": "1.2.13", + "@rolldown/binding-linux-s390x-gnu": "1.2.13", + "@rolldown/binding-linux-x64-gnu": "1.2.13", + "@rolldown/binding-linux-x64-musl": "1.2.13", + "@rolldown/binding-openharmony-arm64": "1.2.13", + "@rolldown/binding-win32-arm64-msvc": "1.2.13", + "@rolldown/binding-win32-x64-msvc": "1.2.13" + } + }, + "node_modules/source-map-js": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/vite": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.4.tgz", + "integrity": "sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.29", + "rolldown": "~1.2.12", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.7.1", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + } + } +} diff --git a/benchmarks/package.json b/benchmarks/package.json new file mode 100644 index 00000000..f4cb4450 --- /dev/null +++ b/benchmarks/package.json @@ -0,0 +1,16 @@ +{ + "name": "@lightningjs/blits-benchmarks", + "private": true, + "type": "module", + "scripts": { + "build": "vite build --mode production", + "serve": "vite dev --host", + "bench": "node runner.js", + "bench:profile": "node runner.js --profile" + }, + "devDependencies": { + "@lightningjs/blits": "file:..", + "playwright": "^1.62.1", + "vite": "^8.2.2" + } +} diff --git a/benchmarks/public/.gitkeep b/benchmarks/public/.gitkeep new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/benchmarks/public/.gitkeep @@ -0,0 +1 @@ + diff --git a/benchmarks/public/fonts/OpenSans-Medium.ttf b/benchmarks/public/fonts/OpenSans-Medium.ttf new file mode 100644 index 0000000000000000000000000000000000000000..a76d4ced2dd011d57d6891fc8fc90a9f391ca779 GIT binary patch literal 130980 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�r+$1Dp#BZD!_Q9V5YCT&SEargfy zo;rxwT#|Bl=U);sA|AGm0`Efs# zi21$#S*~u-3bCE&vm6Q%h9!+vTO@~Z*+bB%0*SW@XT}S9jrL$s?0M98dh<=S(f4>xWUrXqm|=(@<7%zu>?Zk zPi+2aA^zx-_xbB~%YVwo;*?zqe|3yBn3jZdSsWwtOiQs{$Tqo*#yN0`Sn7|F$)V*v zJy=qEO#HcO27k~&%ro)*U~4GfAMm}5xBSRZJgfC31fl_q?U?&WD1UJ3<-5wucQN?# z9`tw^A0zcwWy@8L&gQZB6GJ^luo|1Rj;LnIyJt?%EIclTA0uI$z3dN9_Y382Ty$s~ z?~wWmd5YkN;qx#NV#p?C2DwpZA$D~e>C!(#=IA~m4fI`dBf}{%gmG#uDM4N}Gm
uMGu$VhwCfV^VQd;X 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

>GK` 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 { + const index = args.indexOf(name) + return index === -1 ? fallback : args[index + 1] +} +const options = { + size: Number(value('--size', 1000)), + samples: Number(value('--samples', 20)), + warmup: Number(value('--warmup', 5)), +} + +const run = async () => { + const config = { + root: benchmarkRoot, + mode: profile ? 'development' : 'production', + } + await build(config) + const server = await preview({ + ...config, + preview: { host: '127.0.0.1', port: 0 }, + }) + let browser + try { + browser = await chromium.launch({ headless: !headed }) + const page = await browser.newPage({ + viewport: { width: 1920, height: 1080 }, + }) + page.on('pageerror', (error) => console.error(error)) + const client = await page.context().newCDPSession(page) + await client.send('Emulation.setCPUThrottlingRate', { rate: 20 }) + const url = server.resolvedUrls.local[0] + await page.goto(profile ? `${url}?profile` : url) + await page.waitForFunction(() => window.__BLITS_BENCH__?.ready === true) + const results = await page.evaluate((options) => window.__BLITS_BENCH__.run(options), options) + console.log(JSON.stringify({ cpuSlowdown: 20, ...results }, null, 2)) + } finally { + try { + await browser?.close() + } finally { + await server.close() + } + } +} + +run().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/benchmarks/src/Benchmark.js b/benchmarks/src/Benchmark.js new file mode 100644 index 00000000..0d8d1fec --- /dev/null +++ b/benchmarks/src/Benchmark.js @@ -0,0 +1,82 @@ +import Blits from '@lightningjs/blits' +import symbols from '@lightningjs/blits/symbols' +import { untilIdle, updateIdle, runBenchmark, getManualOptions } from './benchmark-utils.js' + +const columns = 34 + +export default Blits.Application({ + template: ` + + + + + + `, + state() { + return { items: [], benchmarkRunning: false } + }, + input: { + async enter(event) { + if (event.repeat || this.benchmarkRunning || !this.runBenchmark) return + event.preventDefault() + try { + await this.runBenchmark(getManualOptions()) + } catch (error) { + console.error(error) + } + }, + }, + hooks: { + idle(idle) { + updateIdle(idle) + }, + ready() { + const run = async (options = {}) => { + if (this.benchmarkRunning) return + this.benchmarkRunning = true + try { + return await runBenchmark({ + ...options, + title: 'Element generation benchmark', + setup: async ({ size }) => { + if (this.items.length > 0) { + await untilIdle(() => { + this.items = [] + }) + } else { + await untilIdle() + } + // Prepare data outside timing; each assignment starts from an empty loop. + return Array.from({ length: size }, (_, item) => ({ + id: `item-${item}`, + x: (item % columns) * 55, + y: Math.floor(item / columns) * 34, + color: item % 2 === 0 ? '#334155' : '#475569', + })) + }, + measure: async (items) => { + let start + let generationSyncMs + await untilIdle(() => { + start = performance.now() + this.items = items + generationSyncMs = performance.now() - start + }) + return { generationSyncMs, totalToIdleMs: performance.now() - start } + }, + verify: ({ size }) => { + const rendered = Object.values(this[symbols.children][1] || {}) + if (rendered.length !== size || rendered.some((element) => element.eol === true)) { + throw new Error(`Expected ${size} live Elements, found ${rendered.length}`) + } + }, + }) + } finally { + this.benchmarkRunning = false + } + } + this.runBenchmark = run + window.__BLITS_BENCH__ = { ready: true, run } + }, + }, +}) diff --git a/benchmarks/src/benchmark-utils.js b/benchmarks/src/benchmark-utils.js new file mode 100644 index 00000000..eacd7470 --- /dev/null +++ b/benchmarks/src/benchmark-utils.js @@ -0,0 +1,138 @@ +export const getManualOptions = () => { + const query = new URLSearchParams(window.location.search) + return { + size: Number(query.get('size') ?? 1000), + warmup: Number(query.get('warmup') ?? 5), + samples: Number(query.get('samples') ?? 20), + } +} + +let isIdle = false +let idleWaiter + +export const untilIdle = (update) => { + if (!update && isIdle) return Promise.resolve() + return new Promise((resolve, reject) => { + // Updates must produce their own active -> idle transition. + let sawActive = !update + const cleanup = () => { + clearTimeout(timeout) + idleWaiter = undefined + } + const timeout = setTimeout(() => { + cleanup() + reject(new Error('Renderer did not become idle within 30 seconds')) + }, 30_000) + idleWaiter = (idle) => { + if (!idle) sawActive = true + if (idle && sawActive) { + cleanup() + resolve() + } + } + try { + update?.() + } catch (error) { + cleanup() + reject(error) + } + }) +} + +export const updateIdle = (idle) => { + isIdle = idle + idleWaiter?.(idle) +} + +const median = (values) => { + const sorted = values.slice().sort((a, b) => a - b) + const middle = Math.floor(sorted.length / 2) + return sorted.length % 2 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2 +} + +export const validateOptions = ({ size, warmup, samples }) => { + for (const [name, value] of Object.entries({ size, warmup, samples })) { + if (!Number.isSafeInteger(value) || value < (name === 'warmup' ? 0 : 1)) { + throw new Error( + `${name} must be ${name === 'warmup' ? 'a non-negative' : 'a positive'} integer` + ) + } + } +} + +export const summarizeResults = ({ title, size, warmup, samples }, results) => { + const syncTimes = results.map((result) => result.generationSyncMs) + const idleTimes = results.map((result) => result.totalToIdleMs) + return { + title, + size, + warmup, + samples, + medianGenerationSyncMs: median(syncTimes), + averageGenerationSyncMs: syncTimes.reduce((sum, value) => sum + value, 0) / samples, + minGenerationSyncMs: Math.min(...syncTimes), + maxGenerationSyncMs: Math.max(...syncTimes), + medianTotalToIdleMs: median(idleTimes), + averageTotalToIdleMs: idleTimes.reduce((sum, value) => sum + value, 0) / samples, + minTotalToIdleMs: Math.min(...idleTimes), + maxTotalToIdleMs: Math.max(...idleTimes), + results, + } +} + +export const logResults = (summary) => { + const { title, size, warmup, samples, results } = summary + console.group(title) + console.table({ + size: { value: size }, + warmup: { value: warmup }, + samples: { value: samples }, + }) + console.table({ + 'Synchronous generation (ms)': { + median: summary.medianGenerationSyncMs, + average: summary.averageGenerationSyncMs, + min: summary.minGenerationSyncMs, + max: summary.maxGenerationSyncMs, + }, + 'Total time to idle (ms)': { + median: summary.medianTotalToIdleMs, + average: summary.averageTotalToIdleMs, + min: summary.minTotalToIdleMs, + max: summary.maxTotalToIdleMs, + }, + }) + console.log('Samples', results) + console.groupEnd() +} + +let running = false + +export const runBenchmark = async ({ + title = 'Benchmark', + size = 1000, + warmup = 5, + samples = 20, + setup, + measure, + verify, +}) => { + if (running) throw new Error('Benchmark is already running') + const options = { title, size, warmup, samples } + validateOptions(options) + running = true + try { + const results = [] + for (let index = 0; index < warmup + samples; index++) { + const prepared = await setup(options) + const result = await measure(prepared) + await verify(options) + if (index >= warmup) results.push(result) + } + const summary = summarizeResults(options, results) + logResults(summary) + return summary + } finally { + running = false + } +} diff --git a/benchmarks/src/main.js b/benchmarks/src/main.js new file mode 100644 index 00000000..22766726 --- /dev/null +++ b/benchmarks/src/main.js @@ -0,0 +1,19 @@ +import Blits from '@lightningjs/blits' +import profiler from '@lightningjs/blits/profiler' +import Benchmark from './Benchmark.js' + +const query = new URLSearchParams(window.location.search) +if (query.has('profile')) profiler.start({ frames: false }) +Blits.Launch(Benchmark, 'app', { + w: 1920, + h: 1080, + debugLevel: 0, + fonts: [ + { + family: 'opensans', + type: 'msdf', + file: 'fonts/OpenSans-Medium.ttf', + }, + ], + defaultFont: 'opensans', +}) diff --git a/benchmarks/vite.config.js b/benchmarks/vite.config.js new file mode 100644 index 00000000..6d37a24d --- /dev/null +++ b/benchmarks/vite.config.js @@ -0,0 +1,9 @@ +import { defineConfig } from 'vite' +import blitsVitePlugins from '@lightningjs/blits/vite' + +export default defineConfig({ + base: './', + plugins: [...blitsVitePlugins], + resolve: { mainFields: ['browser', 'module', 'jsnext:main', 'jsnext'] }, + build: { outDir: 'dist', emptyOutDir: true }, +}) diff --git a/package-lock.json b/package-lock.json index 6207c1c8..fab56b57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@babel/plugin-syntax-import-assertions": "^7.28.6", "@eslint/eslintrc": "^3.3.4", "@eslint/js": "^9.39.3", + "@lightningjs/msdf-generator": "^1.4.0", "c8": "^12.0.0", "eslint": "^9.39.3", "eslint-config-prettier": "^10.1.8", @@ -392,6 +393,17 @@ "node": ">=18" } }, + "node_modules/@borewit/text-codec": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz", + "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/@bramus/specificity": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", @@ -796,6 +808,446 @@ "node": ">=8" } }, + "node_modules/@jimp/core": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/core/-/core-1.6.1.tgz", + "integrity": "sha512-+BoKC5G6hkrSy501zcJ2EpfnllP+avPevcBfRcZe/CW+EwEfY6X1EZ8QWyT7NpDIvEEJb1fdJnMMfUnFkxmw9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/file-ops": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "await-to-js": "^3.0.0", + "exif-parser": "^0.1.12", + "file-type": "^21.3.3", + "mime": "3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/diff": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/diff/-/diff-1.6.1.tgz", + "integrity": "sha512-YkKDPdHjLgo1Api3+Bhc0GLAygldlpt97NfOKoNg1U6IUNXA6X2MgosCjPfSBiSvJvrrz1fsIR+/4cfYXBI/HQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/plugin-resize": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "pixelmatch": "^5.3.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/file-ops": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/file-ops/-/file-ops-1.6.1.tgz", + "integrity": "sha512-T+gX6osHjprbDRad0/B71Evyre7ZdVY1z/gFGEG9Z8KOtZPKboWvPeP2UjbZYWQLy9UKCPQX1FNAnDiOPkJL7w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-bmp": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/js-bmp/-/js-bmp-1.6.1.tgz", + "integrity": "sha512-xzWzNT4/u5zGrTT3Tme9sGU7YzIKxi13+BCQwLqACbt5DXf9SAfdzRkopZQnmDko+6In5nqaT89Gjs43/WdnYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "bmp-ts": "^1.0.9" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-gif": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/js-gif/-/js-gif-1.6.1.tgz", + "integrity": "sha512-YjY2W26rQa05XhanYhRZ7dingCiNN+T2Ymb1JiigIbABY0B28wHE3v3Cf1/HZPWGu0hOg36ylaKgV5KxF2M58w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", + "gifwrap": "^0.10.1", + "omggif": "^1.0.10" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-jpeg": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/js-jpeg/-/js-jpeg-1.6.1.tgz", + "integrity": "sha512-HT9H3yOmlOFzYmdI15IYdfy6ggQhSRIaHeA+OTJSEORXBqEo97sUZu/DsgHIcX5NJ7TkJBTgZ9BZXsV6UbsyMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", + "jpeg-js": "^0.4.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-png": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/js-png/-/js-png-1.6.1.tgz", + "integrity": "sha512-SZ/KVhI5UjcSzzlXsXdIi/LhJ7UShf2NkMOtVrbZQcGzsqNtynAelrOXeoTxcanfVqmNhAoVHg8yR2cYoqrYjA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", + "pngjs": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-tiff": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/js-tiff/-/js-tiff-1.6.1.tgz", + "integrity": "sha512-jDG/eJquID1M4MBlKMmDRBmz2TpXMv7TUyu2nIRUxhlUc2ogC82T+VQUkca9GJH1BBJ9dx5sSE5dGkWNjIbZxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", + "utif2": "^4.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-blit": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-1.6.1.tgz", + "integrity": "sha512-MwnI7C7K81uWddY9FLw1fCOIy6SsPIUftUz36Spt7jisCn8/40DhQMlSxpxTNelnZb/2SnloFimQfRZAmHLOqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-blur": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-1.6.1.tgz", + "integrity": "sha512-lIo7Tzp5jQu30EFFSK/phXANK3citKVEjepDjQ6ljHoIFtuMRrnybnmI2Md24ulvWlDaz+hh3n6qrMb8ydwhZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/utils": "1.6.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-circle": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-1.6.1.tgz", + "integrity": "sha512-kK1PavY6cKHNNKce37vdV4Tmpc1/zDKngGoeOV3j+EMatoHFZUinV3s6F9aWryPs3A0xhCLZgdJ6Zeea1d5LCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-color": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-1.6.1.tgz", + "integrity": "sha512-LtUN1vAP+LRlZAtTNVhDRSiXx+26Kbz3zJaG6a5k59gQ95jgT5mknnF8lxkHcqJthM4MEk3/tPxkdJpEybyF/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "tinycolor2": "^1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-contain": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-1.6.1.tgz", + "integrity": "sha512-m0qhrfA8jkTqretGv4w+T/ADFR4GwBpE0sCOC2uJ0dzr44/ddOMsIdrpi89kabqYiPYIrxkgdCVCLm3zn1Vkkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/plugin-blit": "1.6.1", + "@jimp/plugin-resize": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-cover": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-1.6.1.tgz", + "integrity": "sha512-hZytnsth0zoll6cPf434BrT+p/v569Wr5tyO6Dp0dH1IDPhzhB5F38sZGMLDo7bzQiN9JFVB3fxkcJ/WYCJ3Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/plugin-crop": "1.6.1", + "@jimp/plugin-resize": "1.6.1", + "@jimp/types": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-crop": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-1.6.1.tgz", + "integrity": "sha512-EerRSLlclXyKDnYc/H9w/1amZW7b7v3OGi/VlerPd2M/pAu5X8TkyYWtfqYCXnNp1Ixtd8oCo9zGfY9zoXT4rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-displace": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-1.6.1.tgz", + "integrity": "sha512-K07QVl7xQwIfD6KfxRV/c3E9e7ZBXxUXdWuvoTWcKHL2qV48MOF5Nqbz/aJW4ThnQARIsxvYlZjPFiqkCjlU+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-dither": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-1.6.1.tgz", + "integrity": "sha512-+2V+GCV2WycMoX1/z977TkZ8Zq/4MVSKElHYatgUqtwXMi2fDK2gKYU2g9V39IqFvTJsTIsK0+58VFz/ROBVew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-fisheye": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-1.6.1.tgz", + "integrity": "sha512-XtS5ZyoZ0vxZxJ6gkqI63SivhtI58vX95foMPM+cyzYkRsJXMOYCr8DScxF5bp4Xr003NjYm/P+7+08tibwzHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-flip": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-1.6.1.tgz", + "integrity": "sha512-ws38W/sGj7LobNRayQ83garxiktOyWxM5vO/y4a/2cy9v65SLEUzVkrj+oeAaUSSObdz4HcCEla7XtGlnAGAaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-hash": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-hash/-/plugin-hash-1.6.1.tgz", + "integrity": "sha512-sZt6ZcMX6i8vFWb4GYnw0pR/o9++ef0dTVcboTB5B/g7nrxCODIB4wfEkJ/YqZM5wUvol77K1qeS0/rVO6z21A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/js-bmp": "1.6.1", + "@jimp/js-jpeg": "1.6.1", + "@jimp/js-png": "1.6.1", + "@jimp/js-tiff": "1.6.1", + "@jimp/plugin-color": "1.6.1", + "@jimp/plugin-resize": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "any-base": "^1.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-mask": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-1.6.1.tgz", + "integrity": "sha512-SIG0/FcmEj3tkwFxc7fAGLO8o4uNzMpSOdQOhbCgxefQKq5wOVMk9BQx/sdMPBwtMLr9WLq0GzLA/rk6t2v20A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-print": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-1.6.1.tgz", + "integrity": "sha512-BYVz/X3Xzv8XYilVeDy11NOp0h7BTDjlOtu0BekIFHP1yHVd24AXNzbOy52XlzYZWQ0Dl36HOHEpl/nSNrzc6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/js-jpeg": "1.6.1", + "@jimp/js-png": "1.6.1", + "@jimp/plugin-blit": "1.6.1", + "@jimp/types": "1.6.1", + "parse-bmfont-ascii": "^1.0.6", + "parse-bmfont-binary": "^1.0.6", + "parse-bmfont-xml": "^1.1.6", + "simple-xml-to-json": "^1.2.2", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-quantize": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-quantize/-/plugin-quantize-1.6.1.tgz", + "integrity": "sha512-J2En9PLURfP+vwYDtuZ9T8yBW6BWYZBScydAjRiPBmJfEhTcNQqiiQODrZf7EqbbX/Sy5H6dAeRiqkgoV9N6Ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "image-q": "^4.0.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-resize": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.6.1.tgz", + "integrity": "sha512-CLkrtJoIz2HdWnpYiN6p8KYcPc00rCH/SUu6o+lfZL05Q4uhecJlnvXuj9x+U6mDn3ldPmJj6aZqMHuUJzdVqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-rotate": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-1.6.1.tgz", + "integrity": "sha512-nOjVjbbj705B02ksysKnh0POAwEBXZtJ9zQ5qC+X7Tavl3JNn+P3BzQovbBxLPSbUSld6XID9z5ijin4PtOAUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/plugin-crop": "1.6.1", + "@jimp/plugin-resize": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-threshold": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-1.6.1.tgz", + "integrity": "sha512-JOKv9F8s6tnVLf4sB/2fF0F339EFnHvgEdFYugO6VhowKLsap0pEZmLyE/DlRnYtIj2RddHZVxVMp/eKJ04l2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/plugin-color": "1.6.1", + "@jimp/plugin-hash": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/types": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/types/-/types-1.6.1.tgz", + "integrity": "sha512-leI7YbveTNi565m910XgIOwXyuu074H5qazAD1357HImJSv2hqxnWXpwxQbadGWZ7goZRYBDZy5lpqud0p7q5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/utils": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-1.6.1.tgz", + "integrity": "sha512-veFPRd93FCnS7AgmCkPgARVGoDRrJ9cm1ujuNyA+UfQ5VKbED2002sm5XfFLFwTsKC8j04heTrwe+tU1dluXOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.1", + "tinycolor2": "^1.6.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -847,6 +1299,35 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@lightningjs/msdf-generator": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@lightningjs/msdf-generator/-/msdf-generator-1.4.0.tgz", + "integrity": "sha512-AVmaU9lWu+7L3vPHVLEzbkGY0QBElH6vr2zivK+3qXQYTJOAPcf320YFSK81Wuu+VxMPm0v5yJ7FYuYjD1lEWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "chalk": "^6.0.1", + "fs-extra": "^11.2.0", + "msdf-bmfont-xml": "^2.8.0", + "opentype.js": "2.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@lightningjs/msdf-generator/node_modules/chalk": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-6.0.1.tgz", + "integrity": "sha512-/Ce6KNm3vIbWdMlNna6RVIZ/ICQxnJxCicet5LBKK9ZffBkqzDw0xh9EiKSljdRtiIQ1S1z4YgcscUUGzNCWrA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@lightningjs/renderer": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/@lightningjs/renderer/-/renderer-3.6.0.tgz", @@ -1006,6 +1487,51 @@ "url": "https://opencollective.com/pkgr" } }, + "node_modules/@pnpm/config.env-replace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", + "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", + "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "4.2.10" + }, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true, + "license": "ISC" + }, + "node_modules/@pnpm/npm-conf": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-3.0.3.tgz", + "integrity": "sha512-//0sR/cow/s4ICQaYoAobOl4aU8cjU6x/V24V7XkKotb9+O+3zySIYp146vpaobYHnxa4pZX8NkV54Z5AwbDKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pnpm/config.env-replace": "^1.1.0", + "@pnpm/network.ca-file": "^1.0.1", + "config-chain": "^1.1.11" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@sinonjs/commons": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", @@ -1047,6 +1573,31 @@ "node": ">=4" } }, + "node_modules/@tokenizer/inflate": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "token-types": "^6.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", @@ -1068,6 +1619,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "16.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", + "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==", + "dev": true, + "license": "MIT" + }, "node_modules/acorn": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", @@ -1108,6 +1666,71 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-align/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-align/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/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/ansi-escapes": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", @@ -1153,6 +1776,20 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/any-base": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", + "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/arabic-persian-reshaper": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arabic-persian-reshaper/-/arabic-persian-reshaper-1.0.1.tgz", + "integrity": "sha512-VYBjkhz6o4W1Xt4mD2LAReljJpLSw5CUZMqSBDIQRvFgUSlTKEYghapgBWvkeMWF4W+KF3Fm+/z8EywJU4PBeg==", + "dev": true, + "license": "MIT" + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -1248,6 +1885,17 @@ "node": ">= 0.4" } }, + "node_modules/atomically": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/atomically/-/atomically-2.1.1.tgz", + "integrity": "sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "stubborn-fs": "^2.0.0", + "when-exit": "^2.1.4" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -1264,6 +1912,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/await-to-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/await-to-js/-/await-to-js-3.0.0.tgz", + "integrity": "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1295,6 +1953,49 @@ "require-from-string": "^2.0.2" } }, + "node_modules/bmp-ts": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/bmp-ts/-/bmp-ts-1.0.9.tgz", + "integrity": "sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw==", + "dev": true, + "license": "MIT" + }, + "node_modules/boxen": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz", + "integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^8.0.0", + "chalk": "^5.3.0", + "cli-boxes": "^3.0.0", + "string-width": "^7.2.0", + "type-fest": "^4.21.0", + "widest-line": "^5.0.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "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/brace-expansion": { "version": "1.1.21", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", @@ -1448,6 +2149,19 @@ "node": ">=6" } }, + "node_modules/camelcase": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", + "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001810", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", @@ -1487,6 +2201,19 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cli-cursor": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", @@ -1503,6 +2230,74 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cli-progress": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", + "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.3" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cli-progress/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-progress/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-progress/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-progress/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-progress/node_modules/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/cli-truncate": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", @@ -1579,6 +2374,43 @@ "dev": true, "license": "MIT" }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/config-chain/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/configstore": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-7.1.0.tgz", + "integrity": "sha512-N4oog6YJWbR9kGyXvS7jEykLDXIE2C0ILYqNBZBp9iwiJpoCBWYsuAdW6PPFn6w06jjnC+3JstVvWHO4cZqvRg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "atomically": "^2.0.3", + "dot-prop": "^9.0.0", + "graceful-fs": "^4.2.11", + "xdg-basedir": "^5.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -1770,6 +2602,16 @@ "dev": true, "license": "MIT" }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -1833,6 +2675,22 @@ "node": ">=0.3.1" } }, + "node_modules/dot-prop": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-9.0.0.tgz", + "integrity": "sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^4.18.2" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/dotignore": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/dotignore/-/dotignore-0.1.2.tgz", @@ -2118,6 +2976,19 @@ "node": ">=6" } }, + "node_modules/escape-goat": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz", + "integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -2452,6 +3323,12 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/exif-parser": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", + "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==", + "dev": true + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2557,6 +3434,25 @@ "node": ">=16.0.0" } }, + "node_modules/file-type": { + "version": "21.3.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz", + "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -2641,6 +3537,21 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/fs-extra": { + "version": "11.4.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.1.tgz", + "integrity": "sha512-KYAb4c9BJQI6QqGKthV68OHe0badztdXJWKo0WtBA9IuCFPTKvE5ZdUBglP833aMjhaSPNO4A5j/EkzZtGlKjA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -2816,6 +3727,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gifwrap": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.10.1.tgz", + "integrity": "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "image-q": "^4.0.0", + "omggif": "^1.0.10" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -2851,6 +3773,22 @@ "node": ">=10.13.0" } }, + "node_modules/global-directory": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", + "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "4.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/global-jsdom": { "version": "30.0.0", "resolved": "https://registry.npmjs.org/global-jsdom/-/global-jsdom-30.0.0.tgz", @@ -2907,6 +3845,35 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/handlebars": { + "version": "4.7.10", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.10.tgz", + "integrity": "sha512-P5VJMVM7qgBn6vjXMw8WG9uVI+ncf2pi72j4de4yz5ZULLj2RGqLYaKOYGsgyrViQ0tePOVlN1tDCCXXtFqXKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.8", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, "node_modules/has-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", @@ -3088,6 +4055,27 @@ "url": "https://github.com/sponsors/typicode" } }, + "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", @@ -3098,6 +4086,16 @@ "node": ">= 4" } }, + "node_modules/image-q": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz", + "integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "16.9.1" + } + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -3144,6 +4142,16 @@ "dev": true, "license": "ISC" }, + "node_modules/ini": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -3412,6 +4420,49 @@ "node": ">=0.10.0" } }, + "node_modules/is-in-ci": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-1.0.0.tgz", + "integrity": "sha512-eUuAjybVTHMYWm/U+vBO1sY/JOCgoPCXRxzdju0K+K0BiGW0SChEL1MLC0PoCIR1OlPo5YAp8HuQoUlsWEICwg==", + "dev": true, + "license": "MIT", + "bin": { + "is-in-ci": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-installed-globally": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-1.0.1.tgz", + "integrity": "sha512-li88H4QGRoNMX27jEmY8nR515oJVspAgC/WFMlhHfFV8RJ4QyGpHhXnwAe9gH5RIgKmdYRUmrOhQobcgEjIdSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "global-directory": "^4.0.1", + "is-path-inside": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-invalid-path": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-invalid-path/-/is-invalid-path-1.0.2.tgz", + "integrity": "sha512-6KLcFrPCEP3AFXMfnWrIFkZpYNBVzZAoBJJDEZKtI3LXkaDjM3uFMJQjxiizUuZTZ9Oh9FNv/soXbx5TcpaDmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -3438,6 +4489,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-npm": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-6.1.0.tgz", + "integrity": "sha512-O2z4/kNgyjhQwVR1Wpkbfc19JIhggF97NZNCpWTnjH7kVcZMUrnut9XSN7txI7VdyIYk5ZatOq3zvSuWpU8hoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -3465,6 +4529,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-path-inside": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", + "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -3683,6 +4760,52 @@ "node": ">=8" } }, + "node_modules/jimp": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-1.6.1.tgz", + "integrity": "sha512-hNQh6rZtWfSVWSNVmvq87N5BPJsNH7k7I7qyrXf9DOma9xATQk3fsyHazCQe51nCjdkoWdTmh0vD7bjVSLoxxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/diff": "1.6.1", + "@jimp/js-bmp": "1.6.1", + "@jimp/js-gif": "1.6.1", + "@jimp/js-jpeg": "1.6.1", + "@jimp/js-png": "1.6.1", + "@jimp/js-tiff": "1.6.1", + "@jimp/plugin-blit": "1.6.1", + "@jimp/plugin-blur": "1.6.1", + "@jimp/plugin-circle": "1.6.1", + "@jimp/plugin-color": "1.6.1", + "@jimp/plugin-contain": "1.6.1", + "@jimp/plugin-cover": "1.6.1", + "@jimp/plugin-crop": "1.6.1", + "@jimp/plugin-displace": "1.6.1", + "@jimp/plugin-dither": "1.6.1", + "@jimp/plugin-fisheye": "1.6.1", + "@jimp/plugin-flip": "1.6.1", + "@jimp/plugin-hash": "1.6.1", + "@jimp/plugin-mask": "1.6.1", + "@jimp/plugin-print": "1.6.1", + "@jimp/plugin-quantize": "1.6.1", + "@jimp/plugin-resize": "1.6.1", + "@jimp/plugin-rotate": "1.6.1", + "@jimp/plugin-threshold": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/jpeg-js": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", + "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3714,6 +4837,16 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/js2xmlparser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-5.0.0.tgz", + "integrity": "sha512-ckXs0Fzd6icWurbeAXuqo+3Mhq2m8pOPygsQjTPh8K5UWgKaUgDSHrdDxAfexmT11xvBKOQ6sgYwPkYc5RW/bg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "xmlcreate": "^2.0.4" + } + }, "node_modules/jsdom": { "version": "30.1.1", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.1.1.tgz", @@ -3803,6 +4936,19 @@ "node": ">=6" } }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -3813,6 +4959,35 @@ "json-buffer": "3.0.1" } }, + "node_modules/ky": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/ky/-/ky-1.14.3.tgz", + "integrity": "sha512-9zy9lkjac+TR1c2tG+mkNSVlyOpInnWdSMiue4F+kq8TwJSgv6o8jhLRg8Ho6SnZ9wOYUq/yozts9qQCfk7bIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/ky?sponsor=1" + } + }, + "node_modules/latest-version": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-9.0.0.tgz", + "integrity": "sha512-7W0vV3rqv5tokqkBAFV1LbR7HPOWzXQDpDgEuib/aJ1jsZZx6x3c2mBI+TJhJzOhkGeaLbCKEHXEXLfirtG2JA==", + "dev": true, + "license": "MIT", + "dependencies": { + "package-json": "^10.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -4036,6 +5211,26 @@ "node": ">=10" } }, + "node_modules/map-limit": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/map-limit/-/map-limit-0.0.1.tgz", + "integrity": "sha512-pJpcfLPnIF/Sk3taPW21G/RQsEEirGaFpCW3oXRwH9dnFHPHNGjNyvh++rdmC2fNqEaTw2MhYJraoJWAHx8kEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "~1.3.0" + } + }, + "node_modules/map-limit/node_modules/once": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", + "integrity": "sha512-6vaNInhu+CHxtONf3zw3vq4SP2DOQhjBvIa3rNcG0+P7eKWlYH6Peu7rHizSloRU2EwMz6GraLieis9Ac9+p1w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -4046,6 +5241,13 @@ "node": ">= 0.4" } }, + "node_modules/maxrects-packer": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/maxrects-packer/-/maxrects-packer-2.7.3.tgz", + "integrity": "sha512-bG6qXujJ1QgttZVIH4WDanhoJtvbud/xP/XPyf6A69C9RdA61BM4TomFALCq2nrTa+tARRIBB4LuIFsnUQU2wA==", + "dev": true, + "license": "MIT" + }, "node_modules/mdn-data": { "version": "2.27.1", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", @@ -4084,6 +5286,19 @@ "node": ">=8.6" } }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/mimic-fn": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", @@ -4180,6 +5395,56 @@ "dev": true, "license": "MIT" }, + "node_modules/msdf-bmfont-xml": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/msdf-bmfont-xml/-/msdf-bmfont-xml-2.8.0.tgz", + "integrity": "sha512-VK6US7QqNhY9K5sq6TKlpKNlbBch1M2P1vLirf8mZLHK3j7X86fM4sqEyVnwCBYwZ/xiTbJeUWMEv5Ji+jQQMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "arabic-persian-reshaper": "^1.0.1", + "cli-progress": "^3.12.0", + "commander": "^14.0.0", + "handlebars": "^4.7.8", + "is-invalid-path": "^1.0.2", + "jimp": "^1.6.0", + "js2xmlparser": "^5.0.0", + "map-limit": "0.0.1", + "maxrects-packer": "^2.7.3", + "opentype.js": "^1.3.4", + "update-notifier": "^7.3.1" + }, + "bin": { + "msdf-bmfont": "cli.js" + } + }, + "node_modules/msdf-bmfont-xml/node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/msdf-bmfont-xml/node_modules/opentype.js": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-1.3.4.tgz", + "integrity": "sha512-d2JE9RP/6uagpQAVtJoF0pJJA/fgai89Cc50Yp0EJHk+eLp6QQ7gBoblsnubRULNY132I0J1QKMJ+JTbMqz4sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "string.prototype.codepointat": "^0.2.1", + "tiny-inflate": "^1.0.3" + }, + "bin": { + "ot": "bin/ot" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -4187,6 +5452,13 @@ "dev": true, "license": "MIT" }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, "node_modules/node-exports-info": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.2.tgz", @@ -4333,6 +5605,13 @@ "node": ">= 0.4" } }, + "node_modules/omggif": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", + "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==", + "dev": true, + "license": "MIT" + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -4359,6 +5638,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/opentype.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-2.0.0.tgz", + "integrity": "sha512-kCyjv6xdDY1W/jLWZ/L3QhhTlKUqDZMQ5+Jdlw12b3dXkKNpYBqqlMMj0YDQPShWFTMwgZI1hG14kN3XUDSg/A==", + "dev": true, + "license": "MIT", + "bin": { + "ot": "bin/ot" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -4428,6 +5717,45 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-json": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-10.0.1.tgz", + "integrity": "sha512-ua1L4OgXSBdsu1FPb7F3tYH0F48a6kxvod4pLUlGY9COeJAJQNX/sNH2IiEmsxw7lqYiAwrdHMjz1FctOsyDQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ky": "^1.2.0", + "registry-auth-token": "^5.0.2", + "registry-url": "^6.0.1", + "semver": "^7.6.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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", @@ -4441,6 +5769,31 @@ "node": ">=6" } }, + "node_modules/parse-bmfont-ascii": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", + "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse-bmfont-binary": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", + "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse-bmfont-xml": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.6.tgz", + "integrity": "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-parse-from-string": "^1.0.0", + "xml2js": "^0.5.0" + } + }, "node_modules/parse-ms": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-1.0.1.tgz", @@ -4539,17 +5892,40 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pidtree": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.1.tgz", - "integrity": "sha512-e0F9AOF1JMrCfBsyJOwU9lNvQ0WtXTq0j/4jk0BQ5JSI9VAybPXmDpPRw/2FQ3e5d3ZFN1mLh7jW99m/jjaptw==", + "node_modules/pidtree": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.1.tgz", + "integrity": "sha512-e0F9AOF1JMrCfBsyJOwU9lNvQ0WtXTq0j/4jk0BQ5JSI9VAybPXmDpPRw/2FQ3e5d3ZFN1mLh7jW99m/jjaptw==", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pixelmatch": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz", + "integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "pngjs": "^6.0.0" + }, + "bin": { + "pixelmatch": "bin/pixelmatch" + } + }, + "node_modules/pixelmatch/node_modules/pngjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", "dev": true, "license": "MIT", - "bin": { - "pidtree": "bin/pidtree.js" - }, "engines": { - "node": ">=0.10" + "node": ">=12.13.0" } }, "node_modules/plur": { @@ -4562,6 +5938,16 @@ "node": ">=0.10.0" } }, + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.19.0" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -4633,6 +6019,13 @@ "dev": true, "license": "MIT" }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true, + "license": "ISC" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -4643,6 +6036,22 @@ "node": ">=6" } }, + "node_modules/pupa": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.3.0.tgz", + "integrity": "sha512-LjgDO2zPtoXP2wJpDjZrGdojii1uqO0cnwKoIoUzkfS98HDmbeiGmYiXo3lXeFlq2xvne1QFQhwYXSUCLKtEuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-goat": "^4.0.0" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -4664,6 +6073,39 @@ ], "license": "MIT" }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -4724,6 +6166,35 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/registry-auth-token": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.1.tgz", + "integrity": "sha512-P7B4+jq8DeD2nMsAcdfaqHbssgHtZ7Z5+++a5ask90fvmJ8p5je4mOa+wzu+DB4vQ5tdJV/xywY+UnVFeQLV5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pnpm/npm-conf": "^3.0.2" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/registry-url": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz", + "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "rc": "1.2.8" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -4919,6 +6390,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sax": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -5103,6 +6584,16 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-xml-to-json": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/simple-xml-to-json/-/simple-xml-to-json-1.2.7.tgz", + "integrity": "sha512-mz9VXphOxQWX3eQ/uXCtm6upltoN0DLx8Zb5T4TFC4FHB7S9FDPGre8CfLWqPWQQH/GrQYd2AXhhVM5LDpYx6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.12.2" + } + }, "node_modules/sinon": { "version": "22.1.0", "resolved": "https://registry.npmjs.org/sinon/-/sinon-22.1.0.tgz", @@ -5150,6 +6641,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -5229,6 +6730,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/string.prototype.codepointat": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz", + "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==", + "dev": true, + "license": "MIT" + }, "node_modules/string.prototype.trim": { "version": "1.2.11", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz", @@ -5331,6 +6839,40 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strtok3": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.4.0.tgz", + "integrity": "sha512-zzNQzrgQUNHyFFDzGCkzgWPgxiBcfwqg+/RNK9cZHKRH95KuCKxqXBXe+jC/1Xq+MRLAe/DkizyPe3kR37QV3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/stubborn-fs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/stubborn-fs/-/stubborn-fs-2.0.0.tgz", + "integrity": "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "stubborn-utils": "^1.0.1" + } + }, + "node_modules/stubborn-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stubborn-utils/-/stubborn-utils-1.0.2.tgz", + "integrity": "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==", + "dev": true, + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -5699,6 +7241,20 @@ "xtend": "~4.0.1" } }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", + "dev": true, + "license": "MIT" + }, "node_modules/tldts": { "version": "7.4.14", "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.14.tgz", @@ -5732,6 +7288,25 @@ "node": ">=8.0" } }, + "node_modules/token-types": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@borewit/text-codec": "^0.2.1", + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/tough-cookie": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz", @@ -5781,6 +7356,19 @@ "node": ">=4" } }, + "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/typed-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", @@ -5858,6 +7446,33 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/uint8array-extras": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.6.0.tgz", + "integrity": "sha512-8iAasVS4wUx0gPLjH8Xtz2PeDzTSiy8QiLGu2DT3X5GU+egFEQhpnbtkehbAwjvDcxIERmCYcysiODFmE3Ud0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -5887,6 +7502,16 @@ "node": ">=22.19.0" } }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz", @@ -5919,6 +7544,57 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/update-notifier": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-7.3.1.tgz", + "integrity": "sha512-+dwUY4L35XFYEzE+OAL3sarJdUioVovq+8f7lcIJ7wnmnYQV5UD1Y/lcwaMSyaQ6Bj3JMj1XSTjZbNLHn/19yA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boxen": "^8.0.1", + "chalk": "^5.3.0", + "configstore": "^7.0.0", + "is-in-ci": "^1.0.0", + "is-installed-globally": "^1.0.0", + "is-npm": "^6.0.0", + "latest-version": "^9.0.0", + "pupa": "^3.1.0", + "semver": "^7.6.3", + "xdg-basedir": "^5.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/yeoman/update-notifier?sponsor=1" + } + }, + "node_modules/update-notifier/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "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/update-notifier/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -5929,6 +7605,16 @@ "punycode": "^2.1.0" } }, + "node_modules/utif2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/utif2/-/utif2-4.1.0.tgz", + "integrity": "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==", + "dev": true, + "license": "MIT", + "dependencies": { + "pako": "^1.0.11" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -5999,6 +7685,13 @@ "node": "^22.14.0 || >=24.0.0" } }, + "node_modules/when-exit": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/when-exit/-/when-exit-2.1.5.tgz", + "integrity": "sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg==", + "dev": true, + "license": "MIT" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6111,6 +7804,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/widest-line": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", + "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -6121,6 +7830,13 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, "node_modules/wrap-ansi": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", @@ -6159,6 +7875,19 @@ "dev": true, "license": "ISC" }, + "node_modules/xdg-basedir": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", + "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/xml-name-validator": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", @@ -6169,6 +7898,37 @@ "node": ">=18" } }, + "node_modules/xml-parse-from-string": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", + "integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, "node_modules/xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", @@ -6176,6 +7936,13 @@ "dev": true, "license": "MIT" }, + "node_modules/xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -6287,6 +8054,16 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 2892f666..64104bcb 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,9 @@ "postpublish": "node scripts/postpublish.js", "prepare": "husky", "pack-preview": "npm pack --dry-run", + "bench": "npm --prefix benchmarks run bench --", + "bench:profile": "npm --prefix benchmarks run bench:profile --", + "bench:serve": "npm --prefix benchmarks run serve", "release": "npm publish --access public" }, "types": "./index.d.ts", @@ -82,6 +85,7 @@ "@babel/plugin-syntax-import-assertions": "^7.28.6", "@eslint/eslintrc": "^3.3.4", "@eslint/js": "^9.39.3", + "@lightningjs/msdf-generator": "^1.4.0", "c8": "^12.0.0", "eslint": "^9.39.3", "eslint-config-prettier": "^10.1.8", From cfa44048937ec81acfeca878dc4f338c317692f0 Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Fri, 16 Oct 2026 17:27:00 +0200 Subject: [PATCH 13/23] Added GH workflow to run the benchmark on PR. --- .github/workflows/run-benchmarks.yml | 50 ++++++++++++++++++++++++++++ benchmarks/README.md | 3 ++ benchmarks/runner.js | 24 ++++++++++++- benchmarks/summary.js | 28 ++++++++++++++++ 4 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/run-benchmarks.yml create mode 100644 benchmarks/summary.js diff --git a/.github/workflows/run-benchmarks.yml b/.github/workflows/run-benchmarks.yml new file mode 100644 index 00000000..c521ffd8 --- /dev/null +++ b/.github/workflows/run-benchmarks.yml @@ -0,0 +1,50 @@ +name: Run Benchmarks + +on: + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: benchmarks-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + benchmark: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout repository + uses: actions/checkout@v7 + + - name: Set up Node.js + uses: actions/setup-node@v7 + with: + node-version: 22.23.2 + cache: npm + cache-dependency-path: | + package-lock.json + benchmarks/package-lock.json + + - name: Install root dependencies + run: npm ci + + - name: Install benchmark dependencies + run: npm ci --prefix benchmarks + + - name: Install Chromium + working-directory: benchmarks + run: npx playwright install --with-deps chromium + + - name: Run benchmark and write workflow summary + run: npm run bench -- --output benchmarks/results/benchmark.json + + - name: Upload benchmark results + uses: actions/upload-artifact@v7 + with: + name: benchmark-results + path: benchmarks/results/benchmark.json + if-no-files-found: error + retention-days: 30 diff --git a/benchmarks/README.md b/benchmarks/README.md index 7c884d3b..d5c740fb 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -10,6 +10,7 @@ From the repository root: npm run bench npm run bench -- --size 100 --samples 5 --warmup 2 npm run bench -- --headed +npm run bench -- --output benchmarks/results/benchmark.json npm run bench:serve ``` @@ -24,3 +25,5 @@ Browser and CLI runs use the same workload. The CLI applies a 20x Chromium CPU s Results include synchronous generation time (`generationSyncMs`) and elapsed time until the renderer completes an active → idle transition (`totalToIdleMs`). Both metrics report median, arithmetic average, minimum, and maximum in the browser console table and CLI JSON (for example, `medianGenerationSyncMs`, `averageGenerationSyncMs`, `minGenerationSyncMs`, and `maxGenerationSyncMs`). Each sample also waits for renderer idle after clearing the previous loop, outside the measured generation time. Idle means the renderer has no scene updates to process; this measurement includes frame scheduling and does not establish GPU completion. Each idle wait has a 30-second timeout. Warmup samples are excluded from all statistics. Compare repeated runs on the same machine with the same browser and settings. `npm run bench:profile` enables development profiling while running this workload. Profile timings should be compared separately from production benchmark timings. + +The Run Benchmarks workflow runs on pull requests and can also be started manually. It uploads a `benchmark-results` JSON artifact and writes configuration and timing tables to the workflow summary. Results include individual samples, summary statistics, browser version, CPU slowdown, build mode, and the tested commit in GitHub Actions. The workflow reports timings without a base comparison or performance thresholds; benchmark execution errors still fail the job. diff --git a/benchmarks/runner.js b/benchmarks/runner.js index c30e6e64..75b3d345 100644 --- a/benchmarks/runner.js +++ b/benchmarks/runner.js @@ -1,6 +1,9 @@ import { fileURLToPath } from 'node:url' +import { appendFile, mkdir, writeFile } from 'node:fs/promises' +import { dirname } from 'node:path' import { build, preview } from 'vite' import { chromium } from 'playwright' +import { formatSummary } from './summary.js' const benchmarkRoot = fileURLToPath(new URL('.', import.meta.url)) const args = process.argv.slice(2) @@ -15,6 +18,10 @@ const options = { samples: Number(value('--samples', 20)), warmup: Number(value('--warmup', 5)), } +const output = value('--output', undefined) +if (args.includes('--output') && (!output || output.startsWith('--'))) { + throw new Error('--output requires a file path') +} const run = async () => { const config = { @@ -39,7 +46,22 @@ const run = async () => { await page.goto(profile ? `${url}?profile` : url) await page.waitForFunction(() => window.__BLITS_BENCH__?.ready === true) const results = await page.evaluate((options) => window.__BLITS_BENCH__.run(options), options) - console.log(JSON.stringify({ cpuSlowdown: 20, ...results }, null, 2)) + const report = { + ...results, + cpuSlowdown: 20, + browserVersion: browser.version(), + mode: config.mode, + commit: process.env.GITHUB_SHA, + } + const json = `${JSON.stringify(report, null, 2)}\n` + console.log(json) + if (output) { + await mkdir(dirname(output), { recursive: true }) + await writeFile(output, json) + } + if (process.env.GITHUB_STEP_SUMMARY) { + await appendFile(process.env.GITHUB_STEP_SUMMARY, formatSummary(report)) + } } finally { try { await browser?.close() diff --git a/benchmarks/summary.js b/benchmarks/summary.js new file mode 100644 index 00000000..df494f5a --- /dev/null +++ b/benchmarks/summary.js @@ -0,0 +1,28 @@ +export const formatSummary = (report) => { + const timingRow = (label, metric) => { + const values = ['median', 'average', 'min', 'max'].map((stat) => + report[`${stat}${metric}`].toFixed(2) + ) + return `| ${label} | ${values.join(' | ')} |` + } + return [ + `## ${report.title}`, + '', + '| Configuration | Value |', + '| --- | --- |', + `| Size | ${report.size} |`, + `| Warmup | ${report.warmup} |`, + `| Samples | ${report.samples} |`, + `| CPU slowdown | ${report.cpuSlowdown}× |`, + `| Chromium | ${report.browserVersion} |`, + `| Mode | ${report.mode} |`, + '', + '| Timing (ms) | Median | Average | Min | Max |', + '| --- | ---: | ---: | ---: | ---: |', + timingRow('Synchronous generation', 'GenerationSyncMs'), + timingRow('Total time to idle', 'TotalToIdleMs'), + '', + 'Warmups are excluded. Total time to idle includes synchronous generation and frame scheduling.', + '', + ].join('\n') +} From bd9e767bf1ca3e918ed0f15743faaed53b0f2293 Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Fri, 16 Oct 2026 17:32:38 +0200 Subject: [PATCH 14/23] Fix issue with dependencies in GH CI runner. --- benchmarks/vite.config.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/benchmarks/vite.config.js b/benchmarks/vite.config.js index 6d37a24d..524e85e7 100644 --- a/benchmarks/vite.config.js +++ b/benchmarks/vite.config.js @@ -1,9 +1,19 @@ import { defineConfig } from 'vite' +import { fileURLToPath } from 'node:url' import blitsVitePlugins from '@lightningjs/blits/vite' export default defineConfig({ base: './', plugins: [...blitsVitePlugins], - resolve: { mainFields: ['browser', 'module', 'jsnext:main', 'jsnext'] }, + resolve: { + mainFields: ['browser', 'module', 'jsnext:main', 'jsnext'], + alias: { + // launch.js also supports a sibling renderer checkout. Benchmarks use + // the installed dependency, including for its version metadata. + '../../renderer/package.json': fileURLToPath( + new URL('../node_modules/@lightningjs/renderer/package.json', import.meta.url) + ), + }, + }, build: { outDir: 'dist', emptyOutDir: true }, }) From 1e4f875e835528d101119cb0c88eeafe039b903c Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Fri, 16 Oct 2026 17:40:05 +0200 Subject: [PATCH 15/23] Fixed incorrect path. --- .github/workflows/run-benchmarks.yml | 2 +- benchmarks/README.md | 4 +++- benchmarks/runner.js | 8 +++++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/run-benchmarks.yml b/.github/workflows/run-benchmarks.yml index c521ffd8..16454f8a 100644 --- a/.github/workflows/run-benchmarks.yml +++ b/.github/workflows/run-benchmarks.yml @@ -39,7 +39,7 @@ jobs: run: npx playwright install --with-deps chromium - name: Run benchmark and write workflow summary - run: npm run bench -- --output benchmarks/results/benchmark.json + run: npm run bench -- --output results/benchmark.json - name: Upload benchmark results uses: actions/upload-artifact@v7 diff --git a/benchmarks/README.md b/benchmarks/README.md index d5c740fb..cefed28a 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -10,10 +10,12 @@ From the repository root: npm run bench npm run bench -- --size 100 --samples 5 --warmup 2 npm run bench -- --headed -npm run bench -- --output benchmarks/results/benchmark.json +npm run bench -- --output results/benchmark.json npm run bench:serve ``` +Relative `--output` paths resolve from the `benchmarks/` directory, so the example saves to `benchmarks/results/benchmark.json`. + For browser inspection, open the URL printed by `bench:serve` and press **Enter**. The loop is cleared and recreated on every repetition, and the final sample stays visible. Results are logged in the browser console. Options can be supplied in the URL: ```text diff --git a/benchmarks/runner.js b/benchmarks/runner.js index 75b3d345..85b0cb7b 100644 --- a/benchmarks/runner.js +++ b/benchmarks/runner.js @@ -1,6 +1,6 @@ import { fileURLToPath } from 'node:url' import { appendFile, mkdir, writeFile } from 'node:fs/promises' -import { dirname } from 'node:path' +import { dirname, resolve } from 'node:path' import { build, preview } from 'vite' import { chromium } from 'playwright' import { formatSummary } from './summary.js' @@ -56,8 +56,10 @@ const run = async () => { const json = `${JSON.stringify(report, null, 2)}\n` console.log(json) if (output) { - await mkdir(dirname(output), { recursive: true }) - await writeFile(output, json) + const outputPath = resolve(benchmarkRoot, output) + await mkdir(dirname(outputPath), { recursive: true }) + await writeFile(outputPath, json) + console.log(`Results saved to ${outputPath}`) } if (process.env.GITHUB_STEP_SUMMARY) { await appendFile(process.env.GITHUB_STEP_SUMMARY, formatSummary(report)) From 9f6ef5e3e2aad9659cda30ebdc3239ae33ec7703 Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Fri, 16 Oct 2026 17:44:32 +0200 Subject: [PATCH 16/23] Added benchmark folder to npm ignore. --- .npmignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.npmignore b/.npmignore index 95036ae3..7ada5bf7 100644 --- a/.npmignore +++ b/.npmignore @@ -5,6 +5,7 @@ coverage src/components/*.orig.js # Development files +benchmarks/ .editorconfig .eslintignore .eslintrc.cjs From 09bce2d380def7601deccd31f8a0d4935e67bcb4 Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Mon, 19 Oct 2026 15:01:46 +0200 Subject: [PATCH 17/23] Added benchmark comparison against a branch or commit, locally and in GH. --- .github/workflows/run-benchmarks.yml | 25 +++++- benchmarks/README.md | 32 ++++++- benchmarks/compare.js | 129 +++++++++++++++++++++++++++ benchmarks/comparison.js | 63 +++++++++++++ benchmarks/comparison.test.js | 56 ++++++++++++ benchmarks/package.json | 1 + benchmarks/runner.js | 118 ++++++++++++++++-------- package.json | 1 + 8 files changed, 381 insertions(+), 44 deletions(-) create mode 100644 benchmarks/compare.js create mode 100644 benchmarks/comparison.js create mode 100644 benchmarks/comparison.test.js diff --git a/.github/workflows/run-benchmarks.yml b/.github/workflows/run-benchmarks.yml index 16454f8a..f3e53732 100644 --- a/.github/workflows/run-benchmarks.yml +++ b/.github/workflows/run-benchmarks.yml @@ -3,6 +3,11 @@ name: Run Benchmarks on: pull_request: workflow_dispatch: + inputs: + base: + description: Baseline branch or commit + required: true + default: master permissions: contents: read @@ -14,10 +19,19 @@ concurrency: jobs: benchmark: runs-on: ubuntu-latest - timeout-minutes: 15 + timeout-minutes: 30 steps: - name: Checkout repository uses: actions/checkout@v7 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + fetch-depth: 0 + + - name: Fetch PR base commit + if: github.event_name == 'pull_request' + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + run: git fetch origin "$BASE_SHA" - name: Set up Node.js uses: actions/setup-node@v7 @@ -38,13 +52,16 @@ jobs: working-directory: benchmarks run: npx playwright install --with-deps chromium - - name: Run benchmark and write workflow summary - run: npm run bench -- --output results/benchmark.json + - name: Compare benchmarks and write workflow summary + env: + BASE_REF: ${{ github.event.pull_request.base.sha || inputs.base }} + CANDIDATE_SHA: ${{ github.event.pull_request.head.sha || github.sha }} + run: npm run bench:compare -- --base "$BASE_REF" --candidate "$CANDIDATE_SHA" --output results/comparison.json - name: Upload benchmark results uses: actions/upload-artifact@v7 with: name: benchmark-results - path: benchmarks/results/benchmark.json + path: benchmarks/results/comparison.json if-no-files-found: error retention-days: 30 diff --git a/benchmarks/README.md b/benchmarks/README.md index cefed28a..a1c65f21 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -12,6 +12,7 @@ npm run bench -- --size 100 --samples 5 --warmup 2 npm run bench -- --headed npm run bench -- --output results/benchmark.json npm run bench:serve +npm run bench:compare -- --base master ``` Relative `--output` paths resolve from the `benchmarks/` directory, so the example saves to `benchmarks/results/benchmark.json`. @@ -28,4 +29,33 @@ Results include synchronous generation time (`generationSyncMs`) and elapsed tim `npm run bench:profile` enables development profiling while running this workload. Profile timings should be compared separately from production benchmark timings. -The Run Benchmarks workflow runs on pull requests and can also be started manually. It uploads a `benchmark-results` JSON artifact and writes configuration and timing tables to the workflow summary. Results include individual samples, summary statistics, browser version, CPU slowdown, build mode, and the tested commit in GitHub Actions. The workflow reports timings without a base comparison or performance thresholds; benchmark execution errors still fail the job. +## Compare versions + +From the repository root: + +```sh +npm run bench:compare -- --base master +npm run bench:compare -- --base origin/dev +npm run bench:compare -- --base abc1234 --candidate def5678 +npm run bench:compare -- --base HEAD --size 100 --warmup 2 --samples 5 --pairs 2 +``` + +`--base` accepts a locally available branch, remote branch, or commit. Without `--candidate`, the candidate is the current working tree, including uncommitted and untracked source files, excluding ignored files. With `--candidate`, it is that exact commit or branch. Fetch remote references first if needed. Temporary snapshots are removed on success or failure; your branch and working tree are not modified. + +Both snapshots use the current benchmark harness, Vite plugins, installed dependencies (including the renderer), and Chromium. This compares Blits source changes, not dependency or build-tool version changes. Baselines that are incompatible with the current harness fail rather than silently running a different workload. + +Both production builds finish before measurement begins. Each run uses a fresh browser and its own warmups, with the same viewport and 20× CPU slowdown. The default is two pairs: base → candidate, then candidate → base. Use `--pairs` to increase repetitions, and `--headed` to see the browser. Profiling is not supported for comparisons. + +The console prints a Markdown comparison table and saves `results/comparison.json` by default. Override the path with `--output`; relative paths resolve from `benchmarks/`. JSON includes commit identities, working-tree dirty status, execution order, all samples and statistics, and paired differences. Negative percentages mean faster. Summary columns show medians of run medians; differences and percentages are calculated within each pair and then summarized with a median. Per-pair percentages remain visible to expose inconsistent results. Percent changes from a zero baseline are reported as unavailable. + +To estimate comparison noise, run the same commit on both sides: + +```sh +npm run bench:compare -- --base HEAD --candidate HEAD +``` + +## GitHub Actions + +The Run Benchmarks workflow compares the PR head SHA with the target branch's base SHA from the PR event, sequentially in the same job. It uses the PR's benchmark harness. For manual runs, supply a baseline branch or commit; the candidate is the selected workflow commit. + +It uploads a `benchmark-results` artifact containing `comparison.json` and writes configuration, aggregate comparison, and per-pair tables to the workflow summary. Results are informational without performance thresholds; build or benchmark execution errors still fail the job. diff --git a/benchmarks/compare.js b/benchmarks/compare.js new file mode 100644 index 00000000..0e28d314 --- /dev/null +++ b/benchmarks/compare.js @@ -0,0 +1,129 @@ +import { execFileSync } from 'node:child_process' +import { cp, mkdir, mkdtemp, rm, symlink } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { dirname, join, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' +import { buildBenchmark, measureBenchmark, writeReport } from './runner.js' +import { comparePairs, formatComparison } from './comparison.js' +import { validateOptions } from './src/benchmark-utils.js' + +const root = fileURLToPath(new URL('..', import.meta.url)) +const args = process.argv.slice(2) +const value = (name, fallback) => { + const index = args.indexOf(name) + if (index === -1) return fallback + if (!args[index + 1] || args[index + 1].startsWith('--')) { + throw new Error(`${name} requires a value`) + } + return args[index + 1] +} +const git = (...args) => execFileSync('git', args, { cwd: root, encoding: 'utf8' }).trim() +const commit = (ref) => { + try { + return git('rev-parse', '--verify', '--end-of-options', `${ref}^{commit}`) + } catch { + // Full fetches in CI usually provide origin/, not local branches. + return git('rev-parse', '--verify', '--end-of-options', `refs/remotes/origin/${ref}^{commit}`) + } +} + +const snapshot = async (destination, sha, temporary) => { + await mkdir(destination) + if (sha) { + const archive = join(temporary, 'source.tar') + git('archive', '--format=tar', `--output=${archive}`, sha) + execFileSync('tar', ['-xf', archive, '-C', destination]) + } else { + // Include uncommitted and untracked source, but no ignored files or dependencies. + const files = execFileSync( + 'git', + ['ls-files', '--cached', '--others', '--exclude-standard', '-z'], + { + cwd: root, + encoding: 'utf8', + } + ) + .split('\0') + .filter(Boolean) + for (const file of new Set(files)) { + if (file.startsWith('benchmarks/')) continue + const target = join(destination, file) + await mkdir(dirname(target), { recursive: true }) + try { + await cp(join(root, file), target, { dereference: false }) + } catch (error) { + if (error.code !== 'ENOENT') throw error // Deleted working-tree files stay deleted. + } + } + } + // Compare Blits source with identical installed dependencies and build tooling. + await rm(join(destination, 'node_modules'), { recursive: true, force: true }) + await symlink(join(root, 'node_modules'), join(destination, 'node_modules'), 'dir') +} + +const run = async () => { + const baseRef = value('--base') + if (!baseRef) throw new Error('Usage: npm run bench:compare -- --base ') + const candidateRef = value('--candidate') + const output = value('--output', 'results/comparison.json') + const pairCount = Number(value('--pairs', 2)) + const options = { + size: Number(value('--size', 1000)), + warmup: Number(value('--warmup', 5)), + samples: Number(value('--samples', 20)), + } + validateOptions(options) + if (!Number.isSafeInteger(pairCount) || pairCount < 1) + throw new Error('--pairs must be a positive integer') + if (args.includes('--profile')) + throw new Error('Comparisons use production mode; --profile is unsupported') + const base = { ref: baseRef, commit: commit(baseRef) } + const candidate = { + ref: candidateRef || 'working tree', + commit: commit(candidateRef || 'HEAD'), + dirty: !candidateRef && git('status', '--porcelain').length > 0, + } + const temporary = await mkdtemp(join(tmpdir(), 'blits-benchmark-')) + try { + const configs = {} + for (const [name, sha] of [ + ['base', base.commit], + ['candidate', candidateRef ? candidate.commit : undefined], + ]) { + console.log(`Preparing ${name}…`) + const source = join(temporary, name) + await snapshot(source, sha, temporary) + configs[name] = await buildBenchmark(source, resolve(temporary, `${name}-dist`)) + } + const pairs = [] + for (let index = 0; index < pairCount; index++) { + const order = index % 2 === 0 ? ['base', 'candidate'] : ['candidate', 'base'] + const pair = { order } + for (const name of order) { + console.log(`Pair ${index + 1}/${pairCount}: ${name}`) + pair[name] = await measureBenchmark(configs[name], options, args.includes('--headed')) + } + pairs.push(pair) + } + const report = { + title: pairs[0].candidate.title, + ...options, + base, + candidate, + cpuSlowdown: 20, + browserVersion: pairs[0].candidate.browserVersion, + pairs, + comparison: comparePairs(pairs), + } + const summary = formatComparison(report) + console.log(summary) + await writeReport(report, output, summary) + } finally { + await rm(temporary, { recursive: true, force: true }) + } +} + +run().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/benchmarks/comparison.js b/benchmarks/comparison.js new file mode 100644 index 00000000..3fe3f80c --- /dev/null +++ b/benchmarks/comparison.js @@ -0,0 +1,63 @@ +const median = (values) => { + const sorted = [...values].sort((a, b) => a - b) + const middle = Math.floor(sorted.length / 2) + return sorted.length % 2 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2 +} + +export const comparePairs = (pairs) => { + return ['medianGenerationSyncMs', 'medianTotalToIdleMs'].map((metric) => { + const differences = pairs.map(({ base, candidate }) => candidate[metric] - base[metric]) + const percentages = pairs.map(({ base }, index) => + base[metric] === 0 ? null : (differences[index] / base[metric]) * 100 + ) + return { + metric, + baseMs: median(pairs.map((pair) => pair.base[metric])), + candidateMs: median(pairs.map((pair) => pair.candidate[metric])), + differenceMs: median(differences), + changePercent: percentages.includes(null) ? null : median(percentages), + pairChangesPercent: percentages, + } + }) +} + +export const formatComparison = (report) => { + const number = (value) => (value === null ? 'n/a' : value.toFixed(2)) + const labels = { + medianGenerationSyncMs: 'Synchronous generation', + medianTotalToIdleMs: 'Total time to idle', + } + return [ + `## ${report.title}: comparison`, + '', + '| Configuration | Value |', + '| --- | --- |', + `| Base commit | ${report.base.commit} |`, + `| Candidate commit | ${report.candidate.commit} |`, + `| Candidate working tree changes | ${report.candidate.dirty} |`, + `| Size / warmup / samples per run | ${report.size} / ${report.warmup} / ${report.samples} |`, + `| Pairs | ${report.pairs.length} |`, + `| CPU slowdown | ${report.cpuSlowdown}× |`, + `| Chromium | ${report.browserVersion} |`, + '| Mode | production |', + '', + '| Median timing | Base (ms) | Candidate (ms) | Difference (ms) | Change (%) |', + '| --- | ---: | ---: | ---: | ---: |', + ...report.comparison.map( + (row) => + `| ${labels[row.metric]} | ${number(row.baseMs)} | ${number(row.candidateMs)} | ${number(row.differenceMs)} | ${number(row.changePercent)} |` + ), + '', + '| Pair | Order | Generation change (%) | Time to idle change (%) |', + '| --- | --- | ---: | ---: |', + ...report.pairs.map( + (pair, index) => + `| ${index + 1} | ${pair.order.join(' → ')} | ${report.comparison.map((row) => number(row.pairChangesPercent[index])).join(' | ')} |` + ), + '', + 'Negative changes mean faster. Differences and percentages are medians of paired comparisons; base and candidate columns are medians of run medians.', + '', + 'Both versions use the current harness and installed dependencies. Warmups are excluded. Time to idle includes synchronous generation and frame scheduling, not GPU completion. Results are informational; no performance threshold is applied.', + '', + ].join('\n') +} diff --git a/benchmarks/comparison.test.js b/benchmarks/comparison.test.js new file mode 100644 index 00000000..ded0a46f --- /dev/null +++ b/benchmarks/comparison.test.js @@ -0,0 +1,56 @@ +import assert from 'node:assert/strict' +import test from 'node:test' +import { comparePairs, formatComparison } from './comparison.js' + +const timings = (value) => ({ + medianGenerationSyncMs: value, + medianTotalToIdleMs: value * 2, +}) + +test('compares within pairs before aggregating percentages', () => { + const rows = comparePairs([ + { base: timings(10), candidate: timings(20) }, + { base: timings(100), candidate: timings(90) }, + ]) + assert.deepEqual(rows[0], { + metric: 'medianGenerationSyncMs', + baseMs: 55, + candidateMs: 55, + differenceMs: 0, + changePercent: 45, + pairChangesPercent: [100, -10], + }) + assert.equal(rows[1].changePercent, 45) +}) + +test('identical timings produce no change and zero baselines have no percentage', () => { + assert.equal(comparePairs([{ base: timings(10), candidate: timings(10) }])[0].changePercent, 0) + const [row] = comparePairs([{ base: timings(0), candidate: timings(10) }]) + assert.equal(row.differenceMs, 10) + assert.equal(row.changePercent, null) +}) + +test('summary exposes run order and faster changes with a negative percentage', () => { + const pairs = [ + { + base: timings(100), + candidate: timings(80), + order: ['candidate', 'base'], + }, + ] + const summary = formatComparison({ + title: 'Example', + base: { commit: 'base-sha' }, + candidate: { commit: 'candidate-sha', dirty: true }, + size: 1000, + warmup: 5, + samples: 20, + cpuSlowdown: 20, + browserVersion: 'test', + pairs, + comparison: comparePairs(pairs), + }) + assert.match(summary, /candidate → base/) + assert.match(summary, /100\.00 \| 80\.00 \| -20\.00 \| -20\.00/) + assert.match(summary, /Candidate working tree changes \| true/) +}) diff --git a/benchmarks/package.json b/benchmarks/package.json index f4cb4450..6e750b6a 100644 --- a/benchmarks/package.json +++ b/benchmarks/package.json @@ -6,6 +6,7 @@ "build": "vite build --mode production", "serve": "vite dev --host", "bench": "node runner.js", + "bench:compare": "node compare.js", "bench:profile": "node runner.js --profile" }, "devDependencies": { diff --git a/benchmarks/runner.js b/benchmarks/runner.js index 85b0cb7b..58db2463 100644 --- a/benchmarks/runner.js +++ b/benchmarks/runner.js @@ -1,34 +1,43 @@ -import { fileURLToPath } from 'node:url' -import { appendFile, mkdir, writeFile } from 'node:fs/promises' +import { fileURLToPath, pathToFileURL } from 'node:url' +import { appendFile, mkdir, readFile, writeFile } from 'node:fs/promises' import { dirname, resolve } from 'node:path' import { build, preview } from 'vite' import { chromium } from 'playwright' import { formatSummary } from './summary.js' +import viteConfig from './vite.config.js' const benchmarkRoot = fileURLToPath(new URL('.', import.meta.url)) -const args = process.argv.slice(2) -const profile = args.includes('--profile') -const headed = args.includes('--headed') -const value = (name, fallback) => { - const index = args.indexOf(name) - return index === -1 ? fallback : args[index + 1] -} -const options = { - size: Number(value('--size', 1000)), - samples: Number(value('--samples', 20)), - warmup: Number(value('--warmup', 5)), -} -const output = value('--output', undefined) -if (args.includes('--output') && (!output || output.startsWith('--'))) { - throw new Error('--output requires a file path') -} - -const run = async () => { +export const buildBenchmark = async (sourceRoot, outDir) => { + // Resolve public Blits imports against this snapshot; keep one toolchain. + const pkg = JSON.parse(await readFile(resolve(sourceRoot, 'package.json'), 'utf8')) + const aliases = Object.entries(pkg.exports) + .map(([name, entry]) => ({ + find: name === '.' ? '@lightningjs/blits' : `@lightningjs/blits/${name.slice(2)}`, + replacement: resolve(sourceRoot, typeof entry === 'string' ? entry : entry.default), + })) + .sort((a, b) => b.find.length - a.find.length) const config = { + ...viteConfig, + configFile: false, root: benchmarkRoot, - mode: profile ? 'development' : 'production', + mode: 'production', + resolve: { + ...viteConfig.resolve, + alias: [ + ...aliases, + ...Object.entries(viteConfig.resolve.alias).map(([find, replacement]) => ({ + find, + replacement, + })), + ], + }, + build: { ...viteConfig.build, outDir }, } await build(config) + return config +} + +export const measureBenchmark = async (config, options, headed = false) => { const server = await preview({ ...config, preview: { host: '127.0.0.1', port: 0 }, @@ -43,26 +52,14 @@ const run = async () => { const client = await page.context().newCDPSession(page) await client.send('Emulation.setCPUThrottlingRate', { rate: 20 }) const url = server.resolvedUrls.local[0] - await page.goto(profile ? `${url}?profile` : url) + await page.goto(config.mode === 'development' ? `${url}?profile` : url) await page.waitForFunction(() => window.__BLITS_BENCH__?.ready === true) const results = await page.evaluate((options) => window.__BLITS_BENCH__.run(options), options) - const report = { + return { ...results, cpuSlowdown: 20, browserVersion: browser.version(), mode: config.mode, - commit: process.env.GITHUB_SHA, - } - const json = `${JSON.stringify(report, null, 2)}\n` - console.log(json) - if (output) { - const outputPath = resolve(benchmarkRoot, output) - await mkdir(dirname(outputPath), { recursive: true }) - await writeFile(outputPath, json) - console.log(`Results saved to ${outputPath}`) - } - if (process.env.GITHUB_STEP_SUMMARY) { - await appendFile(process.env.GITHUB_STEP_SUMMARY, formatSummary(report)) } } finally { try { @@ -73,7 +70,50 @@ const run = async () => { } } -run().catch((error) => { - console.error(error) - process.exitCode = 1 -}) +export const writeReport = async (report, output, summary) => { + const json = `${JSON.stringify(report, null, 2)}\n` + console.log(json) + if (output) { + const outputPath = resolve(benchmarkRoot, output) + await mkdir(dirname(outputPath), { recursive: true }) + await writeFile(outputPath, json) + console.log(`Results saved to ${outputPath}`) + } + if (process.env.GITHUB_STEP_SUMMARY) { + await appendFile(process.env.GITHUB_STEP_SUMMARY, summary) + } +} + +const run = async () => { + const args = process.argv.slice(2) + const profile = args.includes('--profile') + const headed = args.includes('--headed') + const value = (name, fallback) => { + const index = args.indexOf(name) + return index === -1 ? fallback : args[index + 1] + } + const options = { + size: Number(value('--size', 1000)), + samples: Number(value('--samples', 20)), + warmup: Number(value('--warmup', 5)), + } + const output = value('--output', undefined) + if (args.includes('--output') && (!output || output.startsWith('--'))) { + throw new Error('--output requires a file path') + } + const config = { + root: benchmarkRoot, + mode: profile ? 'development' : 'production', + } + await build(config) + const report = await measureBenchmark(config, options, headed) + report.commit = process.env.GITHUB_SHA + await writeReport(report, output, formatSummary(report)) +} + +if (import.meta.url === pathToFileURL(process.argv[1]).href) { + run().catch((error) => { + console.error(error) + process.exitCode = 1 + }) +} diff --git a/package.json b/package.json index 64104bcb..453d6ca6 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "prepare": "husky", "pack-preview": "npm pack --dry-run", "bench": "npm --prefix benchmarks run bench --", + "bench:compare": "npm --prefix benchmarks run bench:compare --", "bench:profile": "npm --prefix benchmarks run bench:profile --", "bench:serve": "npm --prefix benchmarks run serve", "release": "npm publish --access public" From 3e5f0936142b4d6b875173e07aa1bb75597cc7be Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Mon, 19 Oct 2026 15:24:34 +0200 Subject: [PATCH 18/23] Changed workflow to run the benchmark 4 times. --- .github/workflows/run-benchmarks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-benchmarks.yml b/.github/workflows/run-benchmarks.yml index f3e53732..4457acf7 100644 --- a/.github/workflows/run-benchmarks.yml +++ b/.github/workflows/run-benchmarks.yml @@ -56,7 +56,7 @@ jobs: env: BASE_REF: ${{ github.event.pull_request.base.sha || inputs.base }} CANDIDATE_SHA: ${{ github.event.pull_request.head.sha || github.sha }} - run: npm run bench:compare -- --base "$BASE_REF" --candidate "$CANDIDATE_SHA" --output results/comparison.json + run: npm run bench:compare -- --base "$BASE_REF" --candidate "$CANDIDATE_SHA" --output results/comparison.json --pairs 4 - name: Upload benchmark results uses: actions/upload-artifact@v7 From 5f7ebcc4db578242c63d2f9daf3acc8658227d72 Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Mon, 19 Oct 2026 15:32:30 +0200 Subject: [PATCH 19/23] Added test with 20 benchmark runs. --- .github/workflows/run-benchmarks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-benchmarks.yml b/.github/workflows/run-benchmarks.yml index 4457acf7..fd54abc1 100644 --- a/.github/workflows/run-benchmarks.yml +++ b/.github/workflows/run-benchmarks.yml @@ -56,7 +56,7 @@ jobs: env: BASE_REF: ${{ github.event.pull_request.base.sha || inputs.base }} CANDIDATE_SHA: ${{ github.event.pull_request.head.sha || github.sha }} - run: npm run bench:compare -- --base "$BASE_REF" --candidate "$CANDIDATE_SHA" --output results/comparison.json --pairs 4 + run: npm run bench:compare -- --base "$BASE_REF" --candidate "$CANDIDATE_SHA" --output results/comparison.json --pairs 20 - name: Upload benchmark results uses: actions/upload-artifact@v7 From 2d80b27a7a4905d95082693a6e2603f7f234a4c6 Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Mon, 19 Oct 2026 15:50:30 +0200 Subject: [PATCH 20/23] Reduced amount of pair runs in the workflow. --- .github/workflows/run-benchmarks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-benchmarks.yml b/.github/workflows/run-benchmarks.yml index fd54abc1..8bf59fca 100644 --- a/.github/workflows/run-benchmarks.yml +++ b/.github/workflows/run-benchmarks.yml @@ -56,7 +56,7 @@ jobs: env: BASE_REF: ${{ github.event.pull_request.base.sha || inputs.base }} CANDIDATE_SHA: ${{ github.event.pull_request.head.sha || github.sha }} - run: npm run bench:compare -- --base "$BASE_REF" --candidate "$CANDIDATE_SHA" --output results/comparison.json --pairs 20 + run: npm run bench:compare -- --base "$BASE_REF" --candidate "$CANDIDATE_SHA" --output results/comparison.json --pairs 5 - name: Upload benchmark results uses: actions/upload-artifact@v7 From bdd3e9479c52242e5945b8acabd233e188e30999 Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Mon, 19 Oct 2026 16:03:09 +0200 Subject: [PATCH 21/23] Added publishing of results and posting a comment on the PR. --- .github/scripts/publish-benchmark.cjs | 169 ++++++++++++++++++ .github/scripts/publish-benchmark.test.cjs | 139 ++++++++++++++ .../workflows/publish-benchmark-results.yml | 43 +++++ benchmarks/README.md | 4 + 4 files changed, 355 insertions(+) create mode 100644 .github/scripts/publish-benchmark.cjs create mode 100644 .github/scripts/publish-benchmark.test.cjs create mode 100644 .github/workflows/publish-benchmark-results.yml diff --git a/.github/scripts/publish-benchmark.cjs b/.github/scripts/publish-benchmark.cjs new file mode 100644 index 00000000..84f0d475 --- /dev/null +++ b/.github/scripts/publish-benchmark.cjs @@ -0,0 +1,169 @@ +const { readFile } = require('node:fs/promises') + +const marker = '' +const branch = 'benchmark-reports' + +module.exports = async ({ github, context, core }) => { + const { comparePairs, formatComparison } = await import('../../benchmarks/comparison.js') + const repo = context.repo + const run = context.payload.workflow_run + let pulls = run.pull_requests + if (!pulls?.length) { + pulls = await github.paginate(github.rest.repos.listPullRequestsAssociatedWithCommit, { + ...repo, + commit_sha: run.head_sha, + }) + } + const report = JSON.parse(await readFile('benchmark-data/comparison.json', 'utf8')) + const prs = [] + for (const pull of pulls) { + const { data: pr } = await github.rest.pulls.get({ ...repo, pull_number: pull.number }) + if ( + pr.state === 'open' && + pr.head.sha === run.head_sha && + pr.head.sha === report.candidate?.commit && + pr.base.sha === report.base?.commit + ) { + prs.push(pr) + } + } + if (!prs.length) { + core.info('No open PR matches these head and base commits; skipping stale results.') + return + } + + // Artifacts are untrusted data. Validate numeric inputs and escape display text. + const positiveInteger = (value) => Number.isSafeInteger(value) && value > 0 && value <= 100_000 + if ( + !positiveInteger(report.size) || + !positiveInteger(report.samples) || + !Number.isSafeInteger(report.warmup) || + report.warmup < 0 || + report.warmup > 100_000 || + !Array.isArray(report.pairs) || + !report.pairs.length || + report.pairs.length > 100 + ) { + throw new Error('Invalid benchmark configuration') + } + for (const pair of report.pairs) { + if ( + !Array.isArray(pair.order) || + pair.order.length !== 2 || + [...pair.order].sort().join(',') !== 'base,candidate' + ) { + throw new Error('Invalid benchmark execution order') + } + for (const name of ['base', 'candidate']) { + for (const metric of ['medianGenerationSyncMs', 'medianTotalToIdleMs']) { + if (!Number.isFinite(pair[name]?.[metric]) || pair[name][metric] < 0) { + throw new Error('Invalid benchmark timing') + } + } + } + } + const text = (value) => + String(value) + .slice(0, 120) + .replace(/[\\`*_[\]<>|#\r\n]/g, ' ') + report.title = text(report.title) + report.browserVersion = text(report.browserVersion) + report.cpuSlowdown = 20 + report.candidate.dirty = false + report.comparison = comparePairs(report.pairs) + if ( + report.comparison.some( + (row) => + !Number.isFinite(row.differenceMs) || + (row.changePercent !== null && !Number.isFinite(row.changePercent)) + ) + ) { + throw new Error('Invalid benchmark comparison') + } + + const path = `runs/${run.id}-${run.run_attempt || 1}` + const markdown = `${formatComparison(report)}\n[JSON data](comparison.json) · [Workflow run](${run.html_url})\n` + let parent + let baseTree + try { + const { data: ref } = await github.rest.git.getRef({ ...repo, ref: `heads/${branch}` }) + parent = ref.object.sha + const { data: commit } = await github.rest.git.getCommit({ ...repo, commit_sha: parent }) + baseTree = commit.tree.sha + } catch (error) { + if (error.status !== 404) throw error + } + const { data: tree } = await github.rest.git.createTree({ + ...repo, + ...(baseTree ? { base_tree: baseTree } : {}), + tree: [ + { path: `${path}/report.md`, mode: '100644', type: 'blob', content: markdown }, + { + path: `${path}/comparison.json`, + mode: '100644', + type: 'blob', + content: JSON.stringify(report, null, 2), + }, + ], + }) + const { data: commit } = await github.rest.git.createCommit({ + ...repo, + message: `Added benchmark report for run ${run.id}.`, + tree: tree.sha, + parents: parent ? [parent] : [], + }) + if (parent) { + await github.rest.git.updateRef({ + ...repo, + ref: `heads/${branch}`, + sha: commit.sha, + force: false, + }) + } else { + await github.rest.git.createRef({ ...repo, ref: `refs/heads/${branch}`, sha: commit.sha }) + } + + const url = `${context.serverUrl}/${repo.owner}/${repo.repo}/blob/${commit.sha}/${path}/report.md` + const change = (metric) => { + const value = report.comparison.find((row) => row.metric === metric).changePercent + return value === null ? 'n/a' : `${value > 0 ? '+' : ''}${value.toFixed(2)}%` + } + const body = [ + marker, + '### Benchmark comparison', + '', + '| Timing | Change |', + '| --- | ---: |', + `| Synchronous generation | ${change('medianGenerationSyncMs')} |`, + `| Total time to idle | ${change('medianTotalToIdleMs')} |`, + '', + 'Negative means faster. Informational results; small changes can be measurement noise.', + '', + `[Full report](${url}) · [Workflow run](${run.html_url})`, + '', + `Head: \`${report.candidate.commit.slice(0, 12)}\` · Base: \`${report.base.commit.slice(0, 12)}\``, + ].join('\n') + for (const pr of prs) { + // Check again in case the PR changed while publishing. + const { data: current } = await github.rest.pulls.get({ ...repo, pull_number: pr.number }) + if ( + current.state !== 'open' || + current.head.sha !== report.candidate.commit || + current.base.sha !== report.base.commit + ) + continue + const comments = await github.paginate(github.rest.issues.listComments, { + ...repo, + issue_number: pr.number, + }) + const existing = comments.find( + (comment) => comment.user.login === 'github-actions[bot]' && comment.body?.includes(marker) + ) + if (existing) { + await github.rest.issues.updateComment({ ...repo, comment_id: existing.id, body }) + } else { + await github.rest.issues.createComment({ ...repo, issue_number: pr.number, body }) + } + } + core.info(`Published benchmark report: ${url}`) +} diff --git a/.github/scripts/publish-benchmark.test.cjs b/.github/scripts/publish-benchmark.test.cjs new file mode 100644 index 00000000..dc40e792 --- /dev/null +++ b/.github/scripts/publish-benchmark.test.cjs @@ -0,0 +1,139 @@ +const assert = require('node:assert/strict') +const { before, after, test } = require('node:test') +const { mkdtemp, mkdir, writeFile, rm } = require('node:fs/promises') +const { tmpdir } = require('node:os') +const { join } = require('node:path') +const publish = require('./publish-benchmark.cjs') + +const head = 'a'.repeat(40) +const base = 'b'.repeat(40) +const originalDirectory = process.cwd() +let directory +before(async () => { + directory = await mkdtemp(join(tmpdir(), 'benchmark-publisher-test-')) + process.chdir(directory) + await mkdir('benchmark-data') +}) +after(async () => { + process.chdir(originalDirectory) + await rm(directory, { recursive: true, force: true }) +}) + +const fixture = async ({ existing = false, stale = false, invalid = false } = {}) => { + const calls = [] + const report = { + title: 'Element generation benchmark', + base: { commit: base }, + candidate: { commit: head }, + size: 1000, + warmup: 5, + samples: 20, + browserVersion: '156.0', + pairs: [ + { + order: ['base', 'candidate'], + base: { medianGenerationSyncMs: 100, medianTotalToIdleMs: 200 }, + candidate: { medianGenerationSyncMs: invalid ? 'bad' : 80, medianTotalToIdleMs: 220 }, + }, + ], + comparison: [{ changePercent: 999 }], // Recomputed rather than trusted. + } + await writeFile('benchmark-data/comparison.json', JSON.stringify(report)) + const method = (name, data) => async (args) => { + calls.push({ name, args }) + return { data } + } + const github = { + rest: { + pulls: { + get: method('pulls.get', { + number: 12, + state: 'open', + head: { sha: stale ? 'c'.repeat(40) : head }, + base: { sha: base }, + }), + }, + repos: { listPullRequestsAssociatedWithCommit: () => {} }, + issues: { + listComments: () => {}, + createComment: method('createComment', {}), + updateComment: method('updateComment', {}), + }, + git: { + getRef: existing + ? method('getRef', { object: { sha: 'parent' } }) + : async () => { + throw Object.assign(new Error('missing'), { status: 404 }) + }, + getCommit: method('getCommit', { tree: { sha: 'previous-tree' } }), + createTree: method('createTree', { sha: 'tree' }), + createCommit: method('createCommit', { sha: 'report-commit' }), + createRef: method('createRef', {}), + updateRef: method('updateRef', {}), + }, + }, + } + github.paginate = async () => + existing + ? [ + { + id: 42, + user: { login: 'github-actions[bot]' }, + body: '', + }, + ] + : [] + const context = { + repo: { owner: 'example', repo: 'blits' }, + serverUrl: 'https://github.com', + payload: { + workflow_run: { + id: 123, + run_attempt: 1, + head_sha: head, + pull_requests: [{ number: 12 }], + html_url: 'https://github.com/example/blits/actions/runs/123', + }, + }, + } + return { github, context, core: { info: () => {} }, calls } +} + +test('creates an isolated report branch and links its immutable commit with computed percentages', async () => { + const mock = await fixture() + await publish(mock) + const tree = mock.calls.find((call) => call.name === 'createTree').args + assert.equal(tree.base_tree, undefined) + assert.match(tree.tree[0].content, /Synchronous generation/) + assert.deepEqual(mock.calls.find((call) => call.name === 'createCommit').args.parents, []) + assert.equal( + mock.calls.find((call) => call.name === 'createRef').args.ref, + 'refs/heads/benchmark-reports' + ) + const body = mock.calls.find((call) => call.name === 'createComment').args.body + assert.match(body, /-20\.00%/) + assert.match(body, /\+10\.00%/) + assert.match(body, /blob\/report-commit\/runs\/123-1\/report.md/) +}) + +test('preserves previous reports and updates the existing bot comment', async () => { + const mock = await fixture({ existing: true }) + await publish(mock) + assert.equal( + mock.calls.find((call) => call.name === 'createTree').args.base_tree, + 'previous-tree' + ) + assert.deepEqual(mock.calls.find((call) => call.name === 'createCommit').args.parents, ['parent']) + assert.equal(mock.calls.find((call) => call.name === 'updateRef').args.force, false) + assert.equal(mock.calls.find((call) => call.name === 'updateComment').args.comment_id, 42) + assert.ok(!mock.calls.some((call) => call.name === 'createComment')) +}) + +test('skips stale results and rejects invalid timings before publication', async () => { + const stale = await fixture({ stale: true }) + await publish(stale) + assert.ok(!stale.calls.some((call) => call.name === 'createTree')) + const invalid = await fixture({ invalid: true }) + await assert.rejects(publish(invalid), /Invalid benchmark timing/) + assert.ok(!invalid.calls.some((call) => call.name === 'createTree')) +}) diff --git a/.github/workflows/publish-benchmark-results.yml b/.github/workflows/publish-benchmark-results.yml new file mode 100644 index 00000000..5018af5c --- /dev/null +++ b/.github/workflows/publish-benchmark-results.yml @@ -0,0 +1,43 @@ +name: Publish Benchmark Results + +on: + workflow_run: + workflows: [Run Benchmarks] + types: [completed] + +permissions: + contents: write + actions: read + pull-requests: write + +concurrency: + group: publish-benchmark-reports + cancel-in-progress: false + +jobs: + publish: + if: github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request' + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + # Only execute trusted default-branch code, never the PR checkout/artifact. + - name: Checkout report publisher + uses: actions/checkout@v7 + with: + ref: ${{ github.event.repository.default_branch }} + persist-credentials: false + + - name: Download benchmark data + uses: actions/download-artifact@v8 + with: + name: benchmark-results + path: benchmark-data + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Publish report and update PR comment + uses: actions/github-script@v7 + with: + script: | + const publish = require('./.github/scripts/publish-benchmark.cjs'); + await publish({ github, context, core }); diff --git a/benchmarks/README.md b/benchmarks/README.md index a1c65f21..3084b081 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -59,3 +59,7 @@ npm run bench:compare -- --base HEAD --candidate HEAD The Run Benchmarks workflow compares the PR head SHA with the target branch's base SHA from the PR event, sequentially in the same job. It uses the PR's benchmark harness. For manual runs, supply a baseline branch or commit; the candidate is the selected workflow commit. It uploads a `benchmark-results` artifact containing `comparison.json` and writes configuration, aggregate comparison, and per-pair tables to the workflow summary. Results are informational without performance thresholds; build or benchmark execution errors still fail the job. + +After a successful PR run, Publish Benchmark Results saves a rendered Markdown report and JSON under `runs/-/` on the separate `benchmark-reports` branch. The branch is created automatically with report files only. Reports are publicly readable on GitHub while the repository is public and remain available after the Actions artifact expires. + +One PR comment is created and updated with the synchronous generation and time-to-idle percentage changes, plus an immutable full-report link. Outdated head/base results are skipped. Publishing runs from the default branch with write permissions, reads the benchmark artifact as data, and never executes PR code. The publisher must be merged into the default branch before it can run; repository rules must allow Actions to write the report branch and PR comments. No extra hosting or secrets are needed. Manual benchmark runs still produce an artifact and workflow summary but do not publish a PR comment. From 8f7c99ebac94a228bb0b0a93ead98ff4b64f9c6b Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Mon, 19 Oct 2026 21:38:15 +0200 Subject: [PATCH 22/23] Moved msdf-generator package dependency to benchmarks folder. --- benchmarks/package-lock.json | 1992 +++++++++++++++++++++++++++++++++- benchmarks/package.json | 1 + package-lock.json | 1809 +----------------------------- package.json | 1 - 4 files changed, 1991 insertions(+), 1812 deletions(-) diff --git a/benchmarks/package-lock.json b/benchmarks/package-lock.json index e1db4cf9..80361e97 100644 --- a/benchmarks/package-lock.json +++ b/benchmarks/package-lock.json @@ -7,6 +7,7 @@ "name": "@lightningjs/blits-benchmarks", "devDependencies": { "@lightningjs/blits": "file:..", + "@lightningjs/msdf-generator": "^1.4.0", "playwright": "^1.62.1", "vite": "^8.2.2" } @@ -28,7 +29,6 @@ "@babel/plugin-syntax-import-assertions": "^7.28.6", "@eslint/eslintrc": "^3.3.4", "@eslint/js": "^9.39.3", - "@lightningjs/msdf-generator": "^1.4.0", "c8": "^12.0.0", "eslint": "^9.39.3", "eslint-config-prettier": "^10.1.8", @@ -46,10 +46,477 @@ "tape": "^5.9.0" } }, + "node_modules/@borewit/text-codec": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz", + "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@jimp/core": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/core/-/core-1.6.1.tgz", + "integrity": "sha512-+BoKC5G6hkrSy501zcJ2EpfnllP+avPevcBfRcZe/CW+EwEfY6X1EZ8QWyT7NpDIvEEJb1fdJnMMfUnFkxmw9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/file-ops": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "await-to-js": "^3.0.0", + "exif-parser": "^0.1.12", + "file-type": "^21.3.3", + "mime": "3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/diff": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/diff/-/diff-1.6.1.tgz", + "integrity": "sha512-YkKDPdHjLgo1Api3+Bhc0GLAygldlpt97NfOKoNg1U6IUNXA6X2MgosCjPfSBiSvJvrrz1fsIR+/4cfYXBI/HQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/plugin-resize": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "pixelmatch": "^5.3.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/file-ops": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/file-ops/-/file-ops-1.6.1.tgz", + "integrity": "sha512-T+gX6osHjprbDRad0/B71Evyre7ZdVY1z/gFGEG9Z8KOtZPKboWvPeP2UjbZYWQLy9UKCPQX1FNAnDiOPkJL7w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-bmp": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/js-bmp/-/js-bmp-1.6.1.tgz", + "integrity": "sha512-xzWzNT4/u5zGrTT3Tme9sGU7YzIKxi13+BCQwLqACbt5DXf9SAfdzRkopZQnmDko+6In5nqaT89Gjs43/WdnYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "bmp-ts": "^1.0.9" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-gif": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/js-gif/-/js-gif-1.6.1.tgz", + "integrity": "sha512-YjY2W26rQa05XhanYhRZ7dingCiNN+T2Ymb1JiigIbABY0B28wHE3v3Cf1/HZPWGu0hOg36ylaKgV5KxF2M58w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", + "gifwrap": "^0.10.1", + "omggif": "^1.0.10" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-jpeg": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/js-jpeg/-/js-jpeg-1.6.1.tgz", + "integrity": "sha512-HT9H3yOmlOFzYmdI15IYdfy6ggQhSRIaHeA+OTJSEORXBqEo97sUZu/DsgHIcX5NJ7TkJBTgZ9BZXsV6UbsyMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", + "jpeg-js": "^0.4.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-png": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/js-png/-/js-png-1.6.1.tgz", + "integrity": "sha512-SZ/KVhI5UjcSzzlXsXdIi/LhJ7UShf2NkMOtVrbZQcGzsqNtynAelrOXeoTxcanfVqmNhAoVHg8yR2cYoqrYjA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", + "pngjs": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-tiff": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/js-tiff/-/js-tiff-1.6.1.tgz", + "integrity": "sha512-jDG/eJquID1M4MBlKMmDRBmz2TpXMv7TUyu2nIRUxhlUc2ogC82T+VQUkca9GJH1BBJ9dx5sSE5dGkWNjIbZxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", + "utif2": "^4.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-blit": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-1.6.1.tgz", + "integrity": "sha512-MwnI7C7K81uWddY9FLw1fCOIy6SsPIUftUz36Spt7jisCn8/40DhQMlSxpxTNelnZb/2SnloFimQfRZAmHLOqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-blur": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-1.6.1.tgz", + "integrity": "sha512-lIo7Tzp5jQu30EFFSK/phXANK3citKVEjepDjQ6ljHoIFtuMRrnybnmI2Md24ulvWlDaz+hh3n6qrMb8ydwhZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/utils": "1.6.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-circle": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-1.6.1.tgz", + "integrity": "sha512-kK1PavY6cKHNNKce37vdV4Tmpc1/zDKngGoeOV3j+EMatoHFZUinV3s6F9aWryPs3A0xhCLZgdJ6Zeea1d5LCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-color": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-1.6.1.tgz", + "integrity": "sha512-LtUN1vAP+LRlZAtTNVhDRSiXx+26Kbz3zJaG6a5k59gQ95jgT5mknnF8lxkHcqJthM4MEk3/tPxkdJpEybyF/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "tinycolor2": "^1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-contain": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-1.6.1.tgz", + "integrity": "sha512-m0qhrfA8jkTqretGv4w+T/ADFR4GwBpE0sCOC2uJ0dzr44/ddOMsIdrpi89kabqYiPYIrxkgdCVCLm3zn1Vkkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/plugin-blit": "1.6.1", + "@jimp/plugin-resize": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-cover": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-1.6.1.tgz", + "integrity": "sha512-hZytnsth0zoll6cPf434BrT+p/v569Wr5tyO6Dp0dH1IDPhzhB5F38sZGMLDo7bzQiN9JFVB3fxkcJ/WYCJ3Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/plugin-crop": "1.6.1", + "@jimp/plugin-resize": "1.6.1", + "@jimp/types": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-crop": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-1.6.1.tgz", + "integrity": "sha512-EerRSLlclXyKDnYc/H9w/1amZW7b7v3OGi/VlerPd2M/pAu5X8TkyYWtfqYCXnNp1Ixtd8oCo9zGfY9zoXT4rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-displace": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-1.6.1.tgz", + "integrity": "sha512-K07QVl7xQwIfD6KfxRV/c3E9e7ZBXxUXdWuvoTWcKHL2qV48MOF5Nqbz/aJW4ThnQARIsxvYlZjPFiqkCjlU+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-dither": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-1.6.1.tgz", + "integrity": "sha512-+2V+GCV2WycMoX1/z977TkZ8Zq/4MVSKElHYatgUqtwXMi2fDK2gKYU2g9V39IqFvTJsTIsK0+58VFz/ROBVew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-fisheye": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-1.6.1.tgz", + "integrity": "sha512-XtS5ZyoZ0vxZxJ6gkqI63SivhtI58vX95foMPM+cyzYkRsJXMOYCr8DScxF5bp4Xr003NjYm/P+7+08tibwzHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-flip": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-1.6.1.tgz", + "integrity": "sha512-ws38W/sGj7LobNRayQ83garxiktOyWxM5vO/y4a/2cy9v65SLEUzVkrj+oeAaUSSObdz4HcCEla7XtGlnAGAaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-hash": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-hash/-/plugin-hash-1.6.1.tgz", + "integrity": "sha512-sZt6ZcMX6i8vFWb4GYnw0pR/o9++ef0dTVcboTB5B/g7nrxCODIB4wfEkJ/YqZM5wUvol77K1qeS0/rVO6z21A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/js-bmp": "1.6.1", + "@jimp/js-jpeg": "1.6.1", + "@jimp/js-png": "1.6.1", + "@jimp/js-tiff": "1.6.1", + "@jimp/plugin-color": "1.6.1", + "@jimp/plugin-resize": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "any-base": "^1.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-mask": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-1.6.1.tgz", + "integrity": "sha512-SIG0/FcmEj3tkwFxc7fAGLO8o4uNzMpSOdQOhbCgxefQKq5wOVMk9BQx/sdMPBwtMLr9WLq0GzLA/rk6t2v20A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-print": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-1.6.1.tgz", + "integrity": "sha512-BYVz/X3Xzv8XYilVeDy11NOp0h7BTDjlOtu0BekIFHP1yHVd24AXNzbOy52XlzYZWQ0Dl36HOHEpl/nSNrzc6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/js-jpeg": "1.6.1", + "@jimp/js-png": "1.6.1", + "@jimp/plugin-blit": "1.6.1", + "@jimp/types": "1.6.1", + "parse-bmfont-ascii": "^1.0.6", + "parse-bmfont-binary": "^1.0.6", + "parse-bmfont-xml": "^1.1.6", + "simple-xml-to-json": "^1.2.2", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-quantize": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-quantize/-/plugin-quantize-1.6.1.tgz", + "integrity": "sha512-J2En9PLURfP+vwYDtuZ9T8yBW6BWYZBScydAjRiPBmJfEhTcNQqiiQODrZf7EqbbX/Sy5H6dAeRiqkgoV9N6Ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "image-q": "^4.0.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-resize": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.6.1.tgz", + "integrity": "sha512-CLkrtJoIz2HdWnpYiN6p8KYcPc00rCH/SUu6o+lfZL05Q4uhecJlnvXuj9x+U6mDn3ldPmJj6aZqMHuUJzdVqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-rotate": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-1.6.1.tgz", + "integrity": "sha512-nOjVjbbj705B02ksysKnh0POAwEBXZtJ9zQ5qC+X7Tavl3JNn+P3BzQovbBxLPSbUSld6XID9z5ijin4PtOAUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/plugin-crop": "1.6.1", + "@jimp/plugin-resize": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-threshold": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-1.6.1.tgz", + "integrity": "sha512-JOKv9F8s6tnVLf4sB/2fF0F339EFnHvgEdFYugO6VhowKLsap0pEZmLyE/DlRnYtIj2RddHZVxVMp/eKJ04l2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/plugin-color": "1.6.1", + "@jimp/plugin-hash": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/types": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/types/-/types-1.6.1.tgz", + "integrity": "sha512-leI7YbveTNi565m910XgIOwXyuu074H5qazAD1357HImJSv2hqxnWXpwxQbadGWZ7goZRYBDZy5lpqud0p7q5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/utils": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-1.6.1.tgz", + "integrity": "sha512-veFPRd93FCnS7AgmCkPgARVGoDRrJ9cm1ujuNyA+UfQ5VKbED2002sm5XfFLFwTsKC8j04heTrwe+tU1dluXOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.1", + "tinycolor2": "^1.6.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@lightningjs/blits": { "resolved": "..", "link": true }, + "node_modules/@lightningjs/msdf-generator": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@lightningjs/msdf-generator/-/msdf-generator-1.4.0.tgz", + "integrity": "sha512-AVmaU9lWu+7L3vPHVLEzbkGY0QBElH6vr2zivK+3qXQYTJOAPcf320YFSK81Wuu+VxMPm0v5yJ7FYuYjD1lEWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "chalk": "^6.0.1", + "fs-extra": "^11.2.0", + "msdf-bmfont-xml": "^2.8.0", + "opentype.js": "2.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@oxc-project/types": { "version": "0.153.0", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.153.0.tgz", @@ -60,6 +527,51 @@ "url": "https://github.com/sponsors/oxc-project" } }, + "node_modules/@pnpm/config.env-replace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", + "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", + "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "4.2.10" + }, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true, + "license": "ISC" + }, + "node_modules/@pnpm/npm-conf": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-3.0.3.tgz", + "integrity": "sha512-//0sR/cow/s4ICQaYoAobOl4aU8cjU6x/V24V7XkKotb9+O+3zySIYp146vpaobYHnxa4pZX8NkV54Z5AwbDKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pnpm/config.env-replace": "^1.1.0", + "@pnpm/network.ca-file": "^1.0.1", + "config-chain": "^1.1.11" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@rolldown/binding-android-arm-eabi": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.13.tgz", @@ -340,26 +852,385 @@ "dev": true, "license": "MIT" }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "node_modules/@tokenizer/inflate": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "token-types": "^6.1.1" + }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" } }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-base": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", + "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/arabic-persian-reshaper": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arabic-persian-reshaper/-/arabic-persian-reshaper-1.0.1.tgz", + "integrity": "sha512-VYBjkhz6o4W1Xt4mD2LAReljJpLSw5CUZMqSBDIQRvFgUSlTKEYghapgBWvkeMWF4W+KF3Fm+/z8EywJU4PBeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/atomically": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/atomically/-/atomically-2.1.1.tgz", + "integrity": "sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "stubborn-fs": "^2.0.0", + "when-exit": "^2.1.4" + } + }, + "node_modules/await-to-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/await-to-js/-/await-to-js-3.0.0.tgz", + "integrity": "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bmp-ts": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/bmp-ts/-/bmp-ts-1.0.9.tgz", + "integrity": "sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw==", + "dev": true, + "license": "MIT" + }, + "node_modules/boxen": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz", + "integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^8.0.0", + "chalk": "^5.3.0", + "cli-boxes": "^3.0.0", + "string-width": "^7.2.0", + "type-fest": "^4.21.0", + "widest-line": "^5.0.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/ansi-regex": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/boxen/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "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/boxen/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/boxen/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/camelcase": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", + "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-6.0.1.tgz", + "integrity": "sha512-/Ce6KNm3vIbWdMlNna6RVIZ/ICQxnJxCicet5LBKK9ZffBkqzDw0xh9EiKSljdRtiIQ1S1z4YgcscUUGzNCWrA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-progress": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", + "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.3" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/config-chain/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/configstore": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-7.1.0.tgz", + "integrity": "sha512-N4oog6YJWbR9kGyXvS7jEykLDXIE2C0ILYqNBZBp9iwiJpoCBWYsuAdW6PPFn6w06jjnC+3JstVvWHO4cZqvRg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "atomically": "^2.0.3", + "dot-prop": "^9.0.0", + "graceful-fs": "^4.2.11", + "xdg-basedir": "^5.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dot-prop": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-9.0.0.tgz", + "integrity": "sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^4.18.2" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/escape-goat": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz", + "integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/exif-parser": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", + "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==", + "dev": true + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { "picomatch": "^3 || ^4" }, "peerDependenciesMeta": { @@ -368,6 +1239,40 @@ } } }, + "node_modules/file-type": { + "version": "21.3.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz", + "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/fs-extra": { + "version": "11.4.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.1.tgz", + "integrity": "sha512-KYAb4c9BJQI6QqGKthV68OHe0badztdXJWKo0WtBA9IuCFPTKvE5ZdUBglP833aMjhaSPNO4A5j/EkzZtGlKjA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -383,6 +1288,300 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/get-east-asian-width": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.7.0.tgz", + "integrity": "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gifwrap": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.10.1.tgz", + "integrity": "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "image-q": "^4.0.0", + "omggif": "^1.0.10" + } + }, + "node_modules/global-directory": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", + "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "4.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/handlebars": { + "version": "4.7.10", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.10.tgz", + "integrity": "sha512-P5VJMVM7qgBn6vjXMw8WG9uVI+ncf2pi72j4de4yz5ZULLj2RGqLYaKOYGsgyrViQ0tePOVlN1tDCCXXtFqXKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.8", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "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/image-q": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz", + "integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "16.9.1" + } + }, + "node_modules/image-q/node_modules/@types/node": { + "version": "16.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", + "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/ini": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-in-ci": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-1.0.0.tgz", + "integrity": "sha512-eUuAjybVTHMYWm/U+vBO1sY/JOCgoPCXRxzdju0K+K0BiGW0SChEL1MLC0PoCIR1OlPo5YAp8HuQoUlsWEICwg==", + "dev": true, + "license": "MIT", + "bin": { + "is-in-ci": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-installed-globally": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-1.0.1.tgz", + "integrity": "sha512-li88H4QGRoNMX27jEmY8nR515oJVspAgC/WFMlhHfFV8RJ4QyGpHhXnwAe9gH5RIgKmdYRUmrOhQobcgEjIdSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "global-directory": "^4.0.1", + "is-path-inside": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-invalid-path": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-invalid-path/-/is-invalid-path-1.0.2.tgz", + "integrity": "sha512-6KLcFrPCEP3AFXMfnWrIFkZpYNBVzZAoBJJDEZKtI3LXkaDjM3uFMJQjxiizUuZTZ9Oh9FNv/soXbx5TcpaDmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/is-npm": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-6.1.0.tgz", + "integrity": "sha512-O2z4/kNgyjhQwVR1Wpkbfc19JIhggF97NZNCpWTnjH7kVcZMUrnut9XSN7txI7VdyIYk5ZatOq3zvSuWpU8hoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-path-inside": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", + "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jimp": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-1.6.1.tgz", + "integrity": "sha512-hNQh6rZtWfSVWSNVmvq87N5BPJsNH7k7I7qyrXf9DOma9xATQk3fsyHazCQe51nCjdkoWdTmh0vD7bjVSLoxxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/diff": "1.6.1", + "@jimp/js-bmp": "1.6.1", + "@jimp/js-gif": "1.6.1", + "@jimp/js-jpeg": "1.6.1", + "@jimp/js-png": "1.6.1", + "@jimp/js-tiff": "1.6.1", + "@jimp/plugin-blit": "1.6.1", + "@jimp/plugin-blur": "1.6.1", + "@jimp/plugin-circle": "1.6.1", + "@jimp/plugin-color": "1.6.1", + "@jimp/plugin-contain": "1.6.1", + "@jimp/plugin-cover": "1.6.1", + "@jimp/plugin-crop": "1.6.1", + "@jimp/plugin-displace": "1.6.1", + "@jimp/plugin-dither": "1.6.1", + "@jimp/plugin-fisheye": "1.6.1", + "@jimp/plugin-flip": "1.6.1", + "@jimp/plugin-hash": "1.6.1", + "@jimp/plugin-mask": "1.6.1", + "@jimp/plugin-print": "1.6.1", + "@jimp/plugin-quantize": "1.6.1", + "@jimp/plugin-resize": "1.6.1", + "@jimp/plugin-rotate": "1.6.1", + "@jimp/plugin-threshold": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/jpeg-js": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", + "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/js2xmlparser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-5.0.0.tgz", + "integrity": "sha512-ckXs0Fzd6icWurbeAXuqo+3Mhq2m8pOPygsQjTPh8K5UWgKaUgDSHrdDxAfexmT11xvBKOQ6sgYwPkYc5RW/bg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "xmlcreate": "^2.0.4" + } + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/ky": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/ky/-/ky-1.14.3.tgz", + "integrity": "sha512-9zy9lkjac+TR1c2tG+mkNSVlyOpInnWdSMiue4F+kq8TwJSgv6o8jhLRg8Ho6SnZ9wOYUq/yozts9qQCfk7bIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/ky?sponsor=1" + } + }, + "node_modules/latest-version": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-9.0.0.tgz", + "integrity": "sha512-7W0vV3rqv5tokqkBAFV1LbR7HPOWzXQDpDgEuib/aJ1jsZZx6x3c2mBI+TJhJzOhkGeaLbCKEHXEXLfirtG2JA==", + "dev": true, + "license": "MIT", + "dependencies": { + "package-json": "^10.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lightningcss": { "version": "1.33.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", @@ -656,6 +1855,93 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/map-limit": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/map-limit/-/map-limit-0.0.1.tgz", + "integrity": "sha512-pJpcfLPnIF/Sk3taPW21G/RQsEEirGaFpCW3oXRwH9dnFHPHNGjNyvh++rdmC2fNqEaTw2MhYJraoJWAHx8kEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "~1.3.0" + } + }, + "node_modules/maxrects-packer": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/maxrects-packer/-/maxrects-packer-2.7.3.tgz", + "integrity": "sha512-bG6qXujJ1QgttZVIH4WDanhoJtvbud/xP/XPyf6A69C9RdA61BM4TomFALCq2nrTa+tARRIBB4LuIFsnUQU2wA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/msdf-bmfont-xml": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/msdf-bmfont-xml/-/msdf-bmfont-xml-2.8.0.tgz", + "integrity": "sha512-VK6US7QqNhY9K5sq6TKlpKNlbBch1M2P1vLirf8mZLHK3j7X86fM4sqEyVnwCBYwZ/xiTbJeUWMEv5Ji+jQQMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "arabic-persian-reshaper": "^1.0.1", + "cli-progress": "^3.12.0", + "commander": "^14.0.0", + "handlebars": "^4.7.8", + "is-invalid-path": "^1.0.2", + "jimp": "^1.6.0", + "js2xmlparser": "^5.0.0", + "map-limit": "0.0.1", + "maxrects-packer": "^2.7.3", + "opentype.js": "^1.3.4", + "update-notifier": "^7.3.1" + }, + "bin": { + "msdf-bmfont": "cli.js" + } + }, + "node_modules/msdf-bmfont-xml/node_modules/opentype.js": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-1.3.4.tgz", + "integrity": "sha512-d2JE9RP/6uagpQAVtJoF0pJJA/fgai89Cc50Yp0EJHk+eLp6QQ7gBoblsnubRULNY132I0J1QKMJ+JTbMqz4sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "string.prototype.codepointat": "^0.2.1", + "tiny-inflate": "^1.0.3" + }, + "bin": { + "ot": "bin/ot" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/nanoid": { "version": "3.3.20", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", @@ -675,6 +1961,91 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/omggif": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", + "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/once": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", + "integrity": "sha512-6vaNInhu+CHxtONf3zw3vq4SP2DOQhjBvIa3rNcG0+P7eKWlYH6Peu7rHizSloRU2EwMz6GraLieis9Ac9+p1w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/opentype.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-2.0.0.tgz", + "integrity": "sha512-kCyjv6xdDY1W/jLWZ/L3QhhTlKUqDZMQ5+Jdlw12b3dXkKNpYBqqlMMj0YDQPShWFTMwgZI1hG14kN3XUDSg/A==", + "dev": true, + "license": "MIT", + "bin": { + "ot": "bin/ot" + } + }, + "node_modules/package-json": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-10.0.1.tgz", + "integrity": "sha512-ua1L4OgXSBdsu1FPb7F3tYH0F48a6kxvod4pLUlGY9COeJAJQNX/sNH2IiEmsxw7lqYiAwrdHMjz1FctOsyDQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ky": "^1.2.0", + "registry-auth-token": "^5.0.2", + "registry-url": "^6.0.1", + "semver": "^7.6.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/parse-bmfont-ascii": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", + "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse-bmfont-binary": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", + "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse-bmfont-xml": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.6.tgz", + "integrity": "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-parse-from-string": "^1.0.0", + "xml2js": "^0.5.0" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -695,6 +2066,29 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pixelmatch": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz", + "integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "pngjs": "^6.0.0" + }, + "bin": { + "pixelmatch": "bin/pixelmatch" + } + }, + "node_modules/pixelmatch/node_modules/pngjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.13.0" + } + }, "node_modules/playwright": { "version": "1.64.0", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.64.0.tgz", @@ -724,6 +2118,16 @@ "node": ">=20" } }, + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.19.0" + } + }, "node_modules/postcss": { "version": "8.5.29", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.29.tgz", @@ -745,12 +2149,87 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.19", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.2" + "nanoid": "^3.3.19", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true, + "license": "ISC" + }, + "node_modules/pupa": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.3.0.tgz", + "integrity": "sha512-LjgDO2zPtoXP2wJpDjZrGdojii1uqO0cnwKoIoUzkfS98HDmbeiGmYiXo3lXeFlq2xvne1QFQhwYXSUCLKtEuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-goat": "^4.0.0" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/registry-auth-token": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.1.tgz", + "integrity": "sha512-P7B4+jq8DeD2nMsAcdfaqHbssgHtZ7Z5+++a5ask90fvmJ8p5je4mOa+wzu+DB4vQ5tdJV/xywY+UnVFeQLV5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pnpm/npm-conf": "^3.0.2" }, "engines": { - "node": "^10 || ^12 || >=14" + "node": ">=14" + } + }, + "node_modules/registry-url": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz", + "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "rc": "1.2.8" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/rolldown": { @@ -787,6 +2266,49 @@ "@rolldown/binding-win32-x64-msvc": "1.2.13" } }, + "node_modules/sax": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-xml-to-json": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/simple-xml-to-json/-/simple-xml-to-json-1.2.7.tgz", + "integrity": "sha512-mz9VXphOxQWX3eQ/uXCtm6upltoN0DLx8Zb5T4TFC4FHB7S9FDPGre8CfLWqPWQQH/GrQYd2AXhhVM5LDpYx6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.12.2" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", @@ -797,6 +2319,99 @@ "node": ">=0.10.0" } }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.codepointat": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz", + "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==", + "dev": true, + "license": "MIT" + }, + "node_modules/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-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strtok3": { + "version": "10.6.2", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.6.2.tgz", + "integrity": "sha512-BkdixFFUt7r7LsX6aGNPVyYMLjW+M1USLlKPNbZsrZEmLFh5tGqhdfckKo6xgsZIJqdDD0CZGkeCJJwNWQIKQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/stubborn-fs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/stubborn-fs/-/stubborn-fs-2.0.0.tgz", + "integrity": "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "stubborn-utils": "^1.0.1" + } + }, + "node_modules/stubborn-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stubborn-utils/-/stubborn-utils-1.0.2.tgz", + "integrity": "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", + "dev": true, + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.17", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", @@ -814,6 +2429,123 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/token-types": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@borewit/text-codec": "^0.2.1", + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "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/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/uint8array-extras": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.6.0.tgz", + "integrity": "sha512-8iAasVS4wUx0gPLjH8Xtz2PeDzTSiy8QiLGu2DT3X5GU+egFEQhpnbtkehbAwjvDcxIERmCYcysiODFmE3Ud0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/update-notifier": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-7.3.1.tgz", + "integrity": "sha512-+dwUY4L35XFYEzE+OAL3sarJdUioVovq+8f7lcIJ7wnmnYQV5UD1Y/lcwaMSyaQ6Bj3JMj1XSTjZbNLHn/19yA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boxen": "^8.0.1", + "chalk": "^5.3.0", + "configstore": "^7.0.0", + "is-in-ci": "^1.0.0", + "is-installed-globally": "^1.0.0", + "is-npm": "^6.0.0", + "latest-version": "^9.0.0", + "pupa": "^3.1.0", + "semver": "^7.6.3", + "xdg-basedir": "^5.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/yeoman/update-notifier?sponsor=1" + } + }, + "node_modules/update-notifier/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "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/utif2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/utif2/-/utif2-4.1.0.tgz", + "integrity": "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==", + "dev": true, + "license": "MIT", + "dependencies": { + "pako": "^1.0.11" + } + }, "node_modules/vite": { "version": "8.3.4", "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.4.tgz", @@ -891,6 +2623,230 @@ "optional": true } } + }, + "node_modules/when-exit": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/when-exit/-/when-exit-2.1.5.tgz", + "integrity": "sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg==", + "dev": true, + "license": "MIT" + }, + "node_modules/widest-line": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", + "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/widest-line/node_modules/ansi-regex": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/widest-line/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/widest-line/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/widest-line/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-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/xdg-basedir": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", + "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xml-parse-from-string": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", + "integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/benchmarks/package.json b/benchmarks/package.json index 6e750b6a..e9ee5284 100644 --- a/benchmarks/package.json +++ b/benchmarks/package.json @@ -11,6 +11,7 @@ }, "devDependencies": { "@lightningjs/blits": "file:..", + "@lightningjs/msdf-generator": "^1.4.0", "playwright": "^1.62.1", "vite": "^8.2.2" } diff --git a/package-lock.json b/package-lock.json index fab56b57..6207c1c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,6 @@ "@babel/plugin-syntax-import-assertions": "^7.28.6", "@eslint/eslintrc": "^3.3.4", "@eslint/js": "^9.39.3", - "@lightningjs/msdf-generator": "^1.4.0", "c8": "^12.0.0", "eslint": "^9.39.3", "eslint-config-prettier": "^10.1.8", @@ -393,17 +392,6 @@ "node": ">=18" } }, - "node_modules/@borewit/text-codec": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz", - "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, "node_modules/@bramus/specificity": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", @@ -808,446 +796,6 @@ "node": ">=8" } }, - "node_modules/@jimp/core": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@jimp/core/-/core-1.6.1.tgz", - "integrity": "sha512-+BoKC5G6hkrSy501zcJ2EpfnllP+avPevcBfRcZe/CW+EwEfY6X1EZ8QWyT7NpDIvEEJb1fdJnMMfUnFkxmw9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jimp/file-ops": "1.6.1", - "@jimp/types": "1.6.1", - "@jimp/utils": "1.6.1", - "await-to-js": "^3.0.0", - "exif-parser": "^0.1.12", - "file-type": "^21.3.3", - "mime": "3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/diff": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@jimp/diff/-/diff-1.6.1.tgz", - "integrity": "sha512-YkKDPdHjLgo1Api3+Bhc0GLAygldlpt97NfOKoNg1U6IUNXA6X2MgosCjPfSBiSvJvrrz1fsIR+/4cfYXBI/HQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jimp/plugin-resize": "1.6.1", - "@jimp/types": "1.6.1", - "@jimp/utils": "1.6.1", - "pixelmatch": "^5.3.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/file-ops": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@jimp/file-ops/-/file-ops-1.6.1.tgz", - "integrity": "sha512-T+gX6osHjprbDRad0/B71Evyre7ZdVY1z/gFGEG9Z8KOtZPKboWvPeP2UjbZYWQLy9UKCPQX1FNAnDiOPkJL7w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/js-bmp": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@jimp/js-bmp/-/js-bmp-1.6.1.tgz", - "integrity": "sha512-xzWzNT4/u5zGrTT3Tme9sGU7YzIKxi13+BCQwLqACbt5DXf9SAfdzRkopZQnmDko+6In5nqaT89Gjs43/WdnYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.1", - "@jimp/types": "1.6.1", - "@jimp/utils": "1.6.1", - "bmp-ts": "^1.0.9" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/js-gif": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@jimp/js-gif/-/js-gif-1.6.1.tgz", - "integrity": "sha512-YjY2W26rQa05XhanYhRZ7dingCiNN+T2Ymb1JiigIbABY0B28wHE3v3Cf1/HZPWGu0hOg36ylaKgV5KxF2M58w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.1", - "@jimp/types": "1.6.1", - "gifwrap": "^0.10.1", - "omggif": "^1.0.10" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/js-jpeg": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@jimp/js-jpeg/-/js-jpeg-1.6.1.tgz", - "integrity": "sha512-HT9H3yOmlOFzYmdI15IYdfy6ggQhSRIaHeA+OTJSEORXBqEo97sUZu/DsgHIcX5NJ7TkJBTgZ9BZXsV6UbsyMg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.1", - "@jimp/types": "1.6.1", - "jpeg-js": "^0.4.4" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/js-png": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@jimp/js-png/-/js-png-1.6.1.tgz", - "integrity": "sha512-SZ/KVhI5UjcSzzlXsXdIi/LhJ7UShf2NkMOtVrbZQcGzsqNtynAelrOXeoTxcanfVqmNhAoVHg8yR2cYoqrYjA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.1", - "@jimp/types": "1.6.1", - "pngjs": "^7.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/js-tiff": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@jimp/js-tiff/-/js-tiff-1.6.1.tgz", - "integrity": "sha512-jDG/eJquID1M4MBlKMmDRBmz2TpXMv7TUyu2nIRUxhlUc2ogC82T+VQUkca9GJH1BBJ9dx5sSE5dGkWNjIbZxw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.1", - "@jimp/types": "1.6.1", - "utif2": "^4.1.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-blit": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-1.6.1.tgz", - "integrity": "sha512-MwnI7C7K81uWddY9FLw1fCOIy6SsPIUftUz36Spt7jisCn8/40DhQMlSxpxTNelnZb/2SnloFimQfRZAmHLOqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jimp/types": "1.6.1", - "@jimp/utils": "1.6.1", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-blur": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-1.6.1.tgz", - "integrity": "sha512-lIo7Tzp5jQu30EFFSK/phXANK3citKVEjepDjQ6ljHoIFtuMRrnybnmI2Md24ulvWlDaz+hh3n6qrMb8ydwhZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.1", - "@jimp/utils": "1.6.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-circle": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-1.6.1.tgz", - "integrity": "sha512-kK1PavY6cKHNNKce37vdV4Tmpc1/zDKngGoeOV3j+EMatoHFZUinV3s6F9aWryPs3A0xhCLZgdJ6Zeea1d5LCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jimp/types": "1.6.1", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-color": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-1.6.1.tgz", - "integrity": "sha512-LtUN1vAP+LRlZAtTNVhDRSiXx+26Kbz3zJaG6a5k59gQ95jgT5mknnF8lxkHcqJthM4MEk3/tPxkdJpEybyF/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.1", - "@jimp/types": "1.6.1", - "@jimp/utils": "1.6.1", - "tinycolor2": "^1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-contain": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-1.6.1.tgz", - "integrity": "sha512-m0qhrfA8jkTqretGv4w+T/ADFR4GwBpE0sCOC2uJ0dzr44/ddOMsIdrpi89kabqYiPYIrxkgdCVCLm3zn1Vkkg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.1", - "@jimp/plugin-blit": "1.6.1", - "@jimp/plugin-resize": "1.6.1", - "@jimp/types": "1.6.1", - "@jimp/utils": "1.6.1", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-cover": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-1.6.1.tgz", - "integrity": "sha512-hZytnsth0zoll6cPf434BrT+p/v569Wr5tyO6Dp0dH1IDPhzhB5F38sZGMLDo7bzQiN9JFVB3fxkcJ/WYCJ3Mg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.1", - "@jimp/plugin-crop": "1.6.1", - "@jimp/plugin-resize": "1.6.1", - "@jimp/types": "1.6.1", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-crop": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-1.6.1.tgz", - "integrity": "sha512-EerRSLlclXyKDnYc/H9w/1amZW7b7v3OGi/VlerPd2M/pAu5X8TkyYWtfqYCXnNp1Ixtd8oCo9zGfY9zoXT4rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.1", - "@jimp/types": "1.6.1", - "@jimp/utils": "1.6.1", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-displace": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-1.6.1.tgz", - "integrity": "sha512-K07QVl7xQwIfD6KfxRV/c3E9e7ZBXxUXdWuvoTWcKHL2qV48MOF5Nqbz/aJW4ThnQARIsxvYlZjPFiqkCjlU+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jimp/types": "1.6.1", - "@jimp/utils": "1.6.1", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-dither": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-1.6.1.tgz", - "integrity": "sha512-+2V+GCV2WycMoX1/z977TkZ8Zq/4MVSKElHYatgUqtwXMi2fDK2gKYU2g9V39IqFvTJsTIsK0+58VFz/ROBVew==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jimp/types": "1.6.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-fisheye": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-1.6.1.tgz", - "integrity": "sha512-XtS5ZyoZ0vxZxJ6gkqI63SivhtI58vX95foMPM+cyzYkRsJXMOYCr8DScxF5bp4Xr003NjYm/P+7+08tibwzHA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jimp/types": "1.6.1", - "@jimp/utils": "1.6.1", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-flip": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-1.6.1.tgz", - "integrity": "sha512-ws38W/sGj7LobNRayQ83garxiktOyWxM5vO/y4a/2cy9v65SLEUzVkrj+oeAaUSSObdz4HcCEla7XtGlnAGAaA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jimp/types": "1.6.1", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-hash": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@jimp/plugin-hash/-/plugin-hash-1.6.1.tgz", - "integrity": "sha512-sZt6ZcMX6i8vFWb4GYnw0pR/o9++ef0dTVcboTB5B/g7nrxCODIB4wfEkJ/YqZM5wUvol77K1qeS0/rVO6z21A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.1", - "@jimp/js-bmp": "1.6.1", - "@jimp/js-jpeg": "1.6.1", - "@jimp/js-png": "1.6.1", - "@jimp/js-tiff": "1.6.1", - "@jimp/plugin-color": "1.6.1", - "@jimp/plugin-resize": "1.6.1", - "@jimp/types": "1.6.1", - "@jimp/utils": "1.6.1", - "any-base": "^1.1.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-mask": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-1.6.1.tgz", - "integrity": "sha512-SIG0/FcmEj3tkwFxc7fAGLO8o4uNzMpSOdQOhbCgxefQKq5wOVMk9BQx/sdMPBwtMLr9WLq0GzLA/rk6t2v20A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jimp/types": "1.6.1", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-print": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-1.6.1.tgz", - "integrity": "sha512-BYVz/X3Xzv8XYilVeDy11NOp0h7BTDjlOtu0BekIFHP1yHVd24AXNzbOy52XlzYZWQ0Dl36HOHEpl/nSNrzc6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.1", - "@jimp/js-jpeg": "1.6.1", - "@jimp/js-png": "1.6.1", - "@jimp/plugin-blit": "1.6.1", - "@jimp/types": "1.6.1", - "parse-bmfont-ascii": "^1.0.6", - "parse-bmfont-binary": "^1.0.6", - "parse-bmfont-xml": "^1.1.6", - "simple-xml-to-json": "^1.2.2", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-quantize": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@jimp/plugin-quantize/-/plugin-quantize-1.6.1.tgz", - "integrity": "sha512-J2En9PLURfP+vwYDtuZ9T8yBW6BWYZBScydAjRiPBmJfEhTcNQqiiQODrZf7EqbbX/Sy5H6dAeRiqkgoV9N6Ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "image-q": "^4.0.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-resize": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.6.1.tgz", - "integrity": "sha512-CLkrtJoIz2HdWnpYiN6p8KYcPc00rCH/SUu6o+lfZL05Q4uhecJlnvXuj9x+U6mDn3ldPmJj6aZqMHuUJzdVqg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.1", - "@jimp/types": "1.6.1", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-rotate": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-1.6.1.tgz", - "integrity": "sha512-nOjVjbbj705B02ksysKnh0POAwEBXZtJ9zQ5qC+X7Tavl3JNn+P3BzQovbBxLPSbUSld6XID9z5ijin4PtOAUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.1", - "@jimp/plugin-crop": "1.6.1", - "@jimp/plugin-resize": "1.6.1", - "@jimp/types": "1.6.1", - "@jimp/utils": "1.6.1", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-threshold": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-1.6.1.tgz", - "integrity": "sha512-JOKv9F8s6tnVLf4sB/2fF0F339EFnHvgEdFYugO6VhowKLsap0pEZmLyE/DlRnYtIj2RddHZVxVMp/eKJ04l2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.1", - "@jimp/plugin-color": "1.6.1", - "@jimp/plugin-hash": "1.6.1", - "@jimp/types": "1.6.1", - "@jimp/utils": "1.6.1", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/types": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@jimp/types/-/types-1.6.1.tgz", - "integrity": "sha512-leI7YbveTNi565m910XgIOwXyuu074H5qazAD1357HImJSv2hqxnWXpwxQbadGWZ7goZRYBDZy5lpqud0p7q5w==", - "dev": true, - "license": "MIT", - "dependencies": { - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/utils": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-1.6.1.tgz", - "integrity": "sha512-veFPRd93FCnS7AgmCkPgARVGoDRrJ9cm1ujuNyA+UfQ5VKbED2002sm5XfFLFwTsKC8j04heTrwe+tU1dluXOw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jimp/types": "1.6.1", - "tinycolor2": "^1.6.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -1299,35 +847,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@lightningjs/msdf-generator": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@lightningjs/msdf-generator/-/msdf-generator-1.4.0.tgz", - "integrity": "sha512-AVmaU9lWu+7L3vPHVLEzbkGY0QBElH6vr2zivK+3qXQYTJOAPcf320YFSK81Wuu+VxMPm0v5yJ7FYuYjD1lEWw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "chalk": "^6.0.1", - "fs-extra": "^11.2.0", - "msdf-bmfont-xml": "^2.8.0", - "opentype.js": "2.0.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@lightningjs/msdf-generator/node_modules/chalk": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-6.0.1.tgz", - "integrity": "sha512-/Ce6KNm3vIbWdMlNna6RVIZ/ICQxnJxCicet5LBKK9ZffBkqzDw0xh9EiKSljdRtiIQ1S1z4YgcscUUGzNCWrA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=22" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/@lightningjs/renderer": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/@lightningjs/renderer/-/renderer-3.6.0.tgz", @@ -1487,51 +1006,6 @@ "url": "https://opencollective.com/pkgr" } }, - "node_modules/@pnpm/config.env-replace": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", - "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.22.0" - } - }, - "node_modules/@pnpm/network.ca-file": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", - "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "4.2.10" - }, - "engines": { - "node": ">=12.22.0" - } - }, - "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true, - "license": "ISC" - }, - "node_modules/@pnpm/npm-conf": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-3.0.3.tgz", - "integrity": "sha512-//0sR/cow/s4ICQaYoAobOl4aU8cjU6x/V24V7XkKotb9+O+3zySIYp146vpaobYHnxa4pZX8NkV54Z5AwbDKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@pnpm/config.env-replace": "^1.1.0", - "@pnpm/network.ca-file": "^1.0.1", - "config-chain": "^1.1.11" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@sinonjs/commons": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", @@ -1573,31 +1047,6 @@ "node": ">=4" } }, - "node_modules/@tokenizer/inflate": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", - "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "token-types": "^6.1.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/@tokenizer/token": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", - "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", @@ -1619,13 +1068,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/node": { - "version": "16.9.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", - "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==", - "dev": true, - "license": "MIT" - }, "node_modules/acorn": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", @@ -1666,71 +1108,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.1.0" - } - }, - "node_modules/ansi-align/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-align/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/ansi-align/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-align/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-align/node_modules/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/ansi-escapes": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", @@ -1776,20 +1153,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/any-base": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", - "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/arabic-persian-reshaper": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arabic-persian-reshaper/-/arabic-persian-reshaper-1.0.1.tgz", - "integrity": "sha512-VYBjkhz6o4W1Xt4mD2LAReljJpLSw5CUZMqSBDIQRvFgUSlTKEYghapgBWvkeMWF4W+KF3Fm+/z8EywJU4PBeg==", - "dev": true, - "license": "MIT" - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -1885,17 +1248,6 @@ "node": ">= 0.4" } }, - "node_modules/atomically": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/atomically/-/atomically-2.1.1.tgz", - "integrity": "sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "stubborn-fs": "^2.0.0", - "when-exit": "^2.1.4" - } - }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -1912,16 +1264,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/await-to-js": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/await-to-js/-/await-to-js-3.0.0.tgz", - "integrity": "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1953,49 +1295,6 @@ "require-from-string": "^2.0.2" } }, - "node_modules/bmp-ts": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/bmp-ts/-/bmp-ts-1.0.9.tgz", - "integrity": "sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw==", - "dev": true, - "license": "MIT" - }, - "node_modules/boxen": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz", - "integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-align": "^3.0.1", - "camelcase": "^8.0.0", - "chalk": "^5.3.0", - "cli-boxes": "^3.0.0", - "string-width": "^7.2.0", - "type-fest": "^4.21.0", - "widest-line": "^5.0.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boxen/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "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/brace-expansion": { "version": "1.1.21", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", @@ -2149,19 +1448,6 @@ "node": ">=6" } }, - "node_modules/camelcase": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", - "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/caniuse-lite": { "version": "1.0.30001810", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", @@ -2201,19 +1487,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/cli-boxes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", - "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/cli-cursor": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", @@ -2230,74 +1503,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cli-progress": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", - "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "string-width": "^4.2.3" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cli-progress/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-progress/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/cli-progress/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-progress/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-progress/node_modules/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/cli-truncate": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", @@ -2374,43 +1579,6 @@ "dev": true, "license": "MIT" }, - "node_modules/config-chain": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", - "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "node_modules/config-chain/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC" - }, - "node_modules/configstore": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-7.1.0.tgz", - "integrity": "sha512-N4oog6YJWbR9kGyXvS7jEykLDXIE2C0ILYqNBZBp9iwiJpoCBWYsuAdW6PPFn6w06jjnC+3JstVvWHO4cZqvRg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "atomically": "^2.0.3", - "dot-prop": "^9.0.0", - "graceful-fs": "^4.2.11", - "xdg-basedir": "^5.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -2602,16 +1770,6 @@ "dev": true, "license": "MIT" }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -2675,22 +1833,6 @@ "node": ">=0.3.1" } }, - "node_modules/dot-prop": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-9.0.0.tgz", - "integrity": "sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^4.18.2" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/dotignore": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/dotignore/-/dotignore-0.1.2.tgz", @@ -2976,19 +2118,6 @@ "node": ">=6" } }, - "node_modules/escape-goat": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz", - "integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -3323,12 +2452,6 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/exif-parser": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", - "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==", - "dev": true - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3434,25 +2557,6 @@ "node": ">=16.0.0" } }, - "node_modules/file-type": { - "version": "21.3.4", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz", - "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tokenizer/inflate": "^0.4.1", - "strtok3": "^10.3.4", - "token-types": "^6.1.1", - "uint8array-extras": "^1.4.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sindresorhus/file-type?sponsor=1" - } - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -3537,21 +2641,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/fs-extra": { - "version": "11.4.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.1.tgz", - "integrity": "sha512-KYAb4c9BJQI6QqGKthV68OHe0badztdXJWKo0WtBA9IuCFPTKvE5ZdUBglP833aMjhaSPNO4A5j/EkzZtGlKjA==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -3727,17 +2816,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gifwrap": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.10.1.tgz", - "integrity": "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "image-q": "^4.0.0", - "omggif": "^1.0.10" - } - }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -3773,22 +2851,6 @@ "node": ">=10.13.0" } }, - "node_modules/global-directory": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", - "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ini": "4.1.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/global-jsdom": { "version": "30.0.0", "resolved": "https://registry.npmjs.org/global-jsdom/-/global-jsdom-30.0.0.tgz", @@ -3832,46 +2894,17 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/handlebars": { - "version": "4.7.10", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.10.tgz", - "integrity": "sha512-P5VJMVM7qgBn6vjXMw8WG9uVI+ncf2pi72j4de4yz5ZULLj2RGqLYaKOYGsgyrViQ0tePOVlN1tDCCXXtFqXKg==", + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, "license": "MIT", - "dependencies": { - "minimist": "^1.2.8", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, "engines": { - "node": ">=0.4.7" + "node": ">= 0.4" }, - "optionalDependencies": { - "uglify-js": "^3.1.4" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-ansi": { @@ -4055,27 +3088,6 @@ "url": "https://github.com/sponsors/typicode" } }, - "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", @@ -4086,16 +3098,6 @@ "node": ">= 4" } }, - "node_modules/image-q": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz", - "integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "16.9.1" - } - }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -4142,16 +3144,6 @@ "dev": true, "license": "ISC" }, - "node_modules/ini": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", - "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -4420,49 +3412,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-in-ci": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-1.0.0.tgz", - "integrity": "sha512-eUuAjybVTHMYWm/U+vBO1sY/JOCgoPCXRxzdju0K+K0BiGW0SChEL1MLC0PoCIR1OlPo5YAp8HuQoUlsWEICwg==", - "dev": true, - "license": "MIT", - "bin": { - "is-in-ci": "cli.js" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-installed-globally": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-1.0.1.tgz", - "integrity": "sha512-li88H4QGRoNMX27jEmY8nR515oJVspAgC/WFMlhHfFV8RJ4QyGpHhXnwAe9gH5RIgKmdYRUmrOhQobcgEjIdSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "global-directory": "^4.0.1", - "is-path-inside": "^4.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-invalid-path": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-invalid-path/-/is-invalid-path-1.0.2.tgz", - "integrity": "sha512-6KLcFrPCEP3AFXMfnWrIFkZpYNBVzZAoBJJDEZKtI3LXkaDjM3uFMJQjxiizUuZTZ9Oh9FNv/soXbx5TcpaDmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0" - } - }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -4489,19 +3438,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-npm": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-6.1.0.tgz", - "integrity": "sha512-O2z4/kNgyjhQwVR1Wpkbfc19JIhggF97NZNCpWTnjH7kVcZMUrnut9XSN7txI7VdyIYk5ZatOq3zvSuWpU8hoA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -4529,19 +3465,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-path-inside": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", - "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -4760,52 +3683,6 @@ "node": ">=8" } }, - "node_modules/jimp": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/jimp/-/jimp-1.6.1.tgz", - "integrity": "sha512-hNQh6rZtWfSVWSNVmvq87N5BPJsNH7k7I7qyrXf9DOma9xATQk3fsyHazCQe51nCjdkoWdTmh0vD7bjVSLoxxw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.1", - "@jimp/diff": "1.6.1", - "@jimp/js-bmp": "1.6.1", - "@jimp/js-gif": "1.6.1", - "@jimp/js-jpeg": "1.6.1", - "@jimp/js-png": "1.6.1", - "@jimp/js-tiff": "1.6.1", - "@jimp/plugin-blit": "1.6.1", - "@jimp/plugin-blur": "1.6.1", - "@jimp/plugin-circle": "1.6.1", - "@jimp/plugin-color": "1.6.1", - "@jimp/plugin-contain": "1.6.1", - "@jimp/plugin-cover": "1.6.1", - "@jimp/plugin-crop": "1.6.1", - "@jimp/plugin-displace": "1.6.1", - "@jimp/plugin-dither": "1.6.1", - "@jimp/plugin-fisheye": "1.6.1", - "@jimp/plugin-flip": "1.6.1", - "@jimp/plugin-hash": "1.6.1", - "@jimp/plugin-mask": "1.6.1", - "@jimp/plugin-print": "1.6.1", - "@jimp/plugin-quantize": "1.6.1", - "@jimp/plugin-resize": "1.6.1", - "@jimp/plugin-rotate": "1.6.1", - "@jimp/plugin-threshold": "1.6.1", - "@jimp/types": "1.6.1", - "@jimp/utils": "1.6.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/jpeg-js": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", - "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4837,16 +3714,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/js2xmlparser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-5.0.0.tgz", - "integrity": "sha512-ckXs0Fzd6icWurbeAXuqo+3Mhq2m8pOPygsQjTPh8K5UWgKaUgDSHrdDxAfexmT11xvBKOQ6sgYwPkYc5RW/bg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "xmlcreate": "^2.0.4" - } - }, "node_modules/jsdom": { "version": "30.1.1", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.1.1.tgz", @@ -4936,19 +3803,6 @@ "node": ">=6" } }, - "node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -4959,35 +3813,6 @@ "json-buffer": "3.0.1" } }, - "node_modules/ky": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/ky/-/ky-1.14.3.tgz", - "integrity": "sha512-9zy9lkjac+TR1c2tG+mkNSVlyOpInnWdSMiue4F+kq8TwJSgv6o8jhLRg8Ho6SnZ9wOYUq/yozts9qQCfk7bIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sindresorhus/ky?sponsor=1" - } - }, - "node_modules/latest-version": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-9.0.0.tgz", - "integrity": "sha512-7W0vV3rqv5tokqkBAFV1LbR7HPOWzXQDpDgEuib/aJ1jsZZx6x3c2mBI+TJhJzOhkGeaLbCKEHXEXLfirtG2JA==", - "dev": true, - "license": "MIT", - "dependencies": { - "package-json": "^10.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -5211,26 +4036,6 @@ "node": ">=10" } }, - "node_modules/map-limit": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/map-limit/-/map-limit-0.0.1.tgz", - "integrity": "sha512-pJpcfLPnIF/Sk3taPW21G/RQsEEirGaFpCW3oXRwH9dnFHPHNGjNyvh++rdmC2fNqEaTw2MhYJraoJWAHx8kEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "once": "~1.3.0" - } - }, - "node_modules/map-limit/node_modules/once": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", - "integrity": "sha512-6vaNInhu+CHxtONf3zw3vq4SP2DOQhjBvIa3rNcG0+P7eKWlYH6Peu7rHizSloRU2EwMz6GraLieis9Ac9+p1w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -5241,13 +4046,6 @@ "node": ">= 0.4" } }, - "node_modules/maxrects-packer": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/maxrects-packer/-/maxrects-packer-2.7.3.tgz", - "integrity": "sha512-bG6qXujJ1QgttZVIH4WDanhoJtvbud/xP/XPyf6A69C9RdA61BM4TomFALCq2nrTa+tARRIBB4LuIFsnUQU2wA==", - "dev": true, - "license": "MIT" - }, "node_modules/mdn-data": { "version": "2.27.1", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", @@ -5286,19 +4084,6 @@ "node": ">=8.6" } }, - "node_modules/mime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", - "dev": true, - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/mimic-fn": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", @@ -5395,56 +4180,6 @@ "dev": true, "license": "MIT" }, - "node_modules/msdf-bmfont-xml": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/msdf-bmfont-xml/-/msdf-bmfont-xml-2.8.0.tgz", - "integrity": "sha512-VK6US7QqNhY9K5sq6TKlpKNlbBch1M2P1vLirf8mZLHK3j7X86fM4sqEyVnwCBYwZ/xiTbJeUWMEv5Ji+jQQMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "arabic-persian-reshaper": "^1.0.1", - "cli-progress": "^3.12.0", - "commander": "^14.0.0", - "handlebars": "^4.7.8", - "is-invalid-path": "^1.0.2", - "jimp": "^1.6.0", - "js2xmlparser": "^5.0.0", - "map-limit": "0.0.1", - "maxrects-packer": "^2.7.3", - "opentype.js": "^1.3.4", - "update-notifier": "^7.3.1" - }, - "bin": { - "msdf-bmfont": "cli.js" - } - }, - "node_modules/msdf-bmfont-xml/node_modules/commander": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", - "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - } - }, - "node_modules/msdf-bmfont-xml/node_modules/opentype.js": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-1.3.4.tgz", - "integrity": "sha512-d2JE9RP/6uagpQAVtJoF0pJJA/fgai89Cc50Yp0EJHk+eLp6QQ7gBoblsnubRULNY132I0J1QKMJ+JTbMqz4sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "string.prototype.codepointat": "^0.2.1", - "tiny-inflate": "^1.0.3" - }, - "bin": { - "ot": "bin/ot" - }, - "engines": { - "node": ">= 8.0.0" - } - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -5452,13 +4187,6 @@ "dev": true, "license": "MIT" }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "license": "MIT" - }, "node_modules/node-exports-info": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.2.tgz", @@ -5605,13 +4333,6 @@ "node": ">= 0.4" } }, - "node_modules/omggif": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", - "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==", - "dev": true, - "license": "MIT" - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -5638,16 +4359,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/opentype.js": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-2.0.0.tgz", - "integrity": "sha512-kCyjv6xdDY1W/jLWZ/L3QhhTlKUqDZMQ5+Jdlw12b3dXkKNpYBqqlMMj0YDQPShWFTMwgZI1hG14kN3XUDSg/A==", - "dev": true, - "license": "MIT", - "bin": { - "ot": "bin/ot" - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -5717,45 +4428,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/package-json": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-10.0.1.tgz", - "integrity": "sha512-ua1L4OgXSBdsu1FPb7F3tYH0F48a6kxvod4pLUlGY9COeJAJQNX/sNH2IiEmsxw7lqYiAwrdHMjz1FctOsyDQg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ky": "^1.2.0", - "registry-auth-token": "^5.0.2", - "registry-url": "^6.0.1", - "semver": "^7.6.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/package-json/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "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", @@ -5769,31 +4441,6 @@ "node": ">=6" } }, - "node_modules/parse-bmfont-ascii": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", - "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==", - "dev": true, - "license": "MIT" - }, - "node_modules/parse-bmfont-binary": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", - "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/parse-bmfont-xml": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.6.tgz", - "integrity": "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "xml-parse-from-string": "^1.0.0", - "xml2js": "^0.5.0" - } - }, "node_modules/parse-ms": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-1.0.1.tgz", @@ -5889,43 +4536,20 @@ "node": ">=8.6" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pidtree": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.1.tgz", - "integrity": "sha512-e0F9AOF1JMrCfBsyJOwU9lNvQ0WtXTq0j/4jk0BQ5JSI9VAybPXmDpPRw/2FQ3e5d3ZFN1mLh7jW99m/jjaptw==", - "dev": true, - "license": "MIT", - "bin": { - "pidtree": "bin/pidtree.js" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/pixelmatch": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz", - "integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==", - "dev": true, - "license": "ISC", - "dependencies": { - "pngjs": "^6.0.0" - }, - "bin": { - "pixelmatch": "bin/pixelmatch" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pixelmatch/node_modules/pngjs": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", - "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", + "node_modules/pidtree": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.1.tgz", + "integrity": "sha512-e0F9AOF1JMrCfBsyJOwU9lNvQ0WtXTq0j/4jk0BQ5JSI9VAybPXmDpPRw/2FQ3e5d3ZFN1mLh7jW99m/jjaptw==", "dev": true, "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, "engines": { - "node": ">=12.13.0" + "node": ">=0.10" } }, "node_modules/plur": { @@ -5938,16 +4562,6 @@ "node": ">=0.10.0" } }, - "node_modules/pngjs": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", - "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.19.0" - } - }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -6019,13 +4633,6 @@ "dev": true, "license": "MIT" }, - "node_modules/proto-list": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", - "dev": true, - "license": "ISC" - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -6036,22 +4643,6 @@ "node": ">=6" } }, - "node_modules/pupa": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.3.0.tgz", - "integrity": "sha512-LjgDO2zPtoXP2wJpDjZrGdojii1uqO0cnwKoIoUzkfS98HDmbeiGmYiXo3lXeFlq2xvne1QFQhwYXSUCLKtEuA==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-goat": "^4.0.0" - }, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -6073,39 +4664,6 @@ ], "license": "MIT" }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC" - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -6166,35 +4724,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/registry-auth-token": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.1.tgz", - "integrity": "sha512-P7B4+jq8DeD2nMsAcdfaqHbssgHtZ7Z5+++a5ask90fvmJ8p5je4mOa+wzu+DB4vQ5tdJV/xywY+UnVFeQLV5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@pnpm/npm-conf": "^3.0.2" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/registry-url": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz", - "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "rc": "1.2.8" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -6390,16 +4919,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/sax": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", - "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=11.0.0" - } - }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -6584,16 +5103,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/simple-xml-to-json": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/simple-xml-to-json/-/simple-xml-to-json-1.2.7.tgz", - "integrity": "sha512-mz9VXphOxQWX3eQ/uXCtm6upltoN0DLx8Zb5T4TFC4FHB7S9FDPGre8CfLWqPWQQH/GrQYd2AXhhVM5LDpYx6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.12.2" - } - }, "node_modules/sinon": { "version": "22.1.0", "resolved": "https://registry.npmjs.org/sinon/-/sinon-22.1.0.tgz", @@ -6641,16 +5150,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -6730,13 +5229,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/string.prototype.codepointat": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz", - "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==", - "dev": true, - "license": "MIT" - }, "node_modules/string.prototype.trim": { "version": "1.2.11", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz", @@ -6839,40 +5331,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strtok3": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.4.0.tgz", - "integrity": "sha512-zzNQzrgQUNHyFFDzGCkzgWPgxiBcfwqg+/RNK9cZHKRH95KuCKxqXBXe+jC/1Xq+MRLAe/DkizyPe3kR37QV3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tokenizer/token": "^0.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/stubborn-fs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/stubborn-fs/-/stubborn-fs-2.0.0.tgz", - "integrity": "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "stubborn-utils": "^1.0.1" - } - }, - "node_modules/stubborn-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stubborn-utils/-/stubborn-utils-1.0.2.tgz", - "integrity": "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==", - "dev": true, - "license": "MIT" - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -7241,20 +5699,6 @@ "xtend": "~4.0.1" } }, - "node_modules/tiny-inflate": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", - "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinycolor2": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", - "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", - "dev": true, - "license": "MIT" - }, "node_modules/tldts": { "version": "7.4.14", "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.14.tgz", @@ -7288,25 +5732,6 @@ "node": ">=8.0" } }, - "node_modules/token-types": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", - "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@borewit/text-codec": "^0.2.1", - "@tokenizer/token": "^0.3.0", - "ieee754": "^1.2.1" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, "node_modules/tough-cookie": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz", @@ -7356,19 +5781,6 @@ "node": ">=4" } }, - "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/typed-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", @@ -7446,33 +5858,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/uglify-js": { - "version": "3.19.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", - "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/uint8array-extras": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.6.0.tgz", - "integrity": "sha512-8iAasVS4wUx0gPLjH8Xtz2PeDzTSiy8QiLGu2DT3X5GU+egFEQhpnbtkehbAwjvDcxIERmCYcysiODFmE3Ud0Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -7502,16 +5887,6 @@ "node": ">=22.19.0" } }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/update-browserslist-db": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz", @@ -7544,57 +5919,6 @@ "browserslist": ">= 4.21.0" } }, - "node_modules/update-notifier": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-7.3.1.tgz", - "integrity": "sha512-+dwUY4L35XFYEzE+OAL3sarJdUioVovq+8f7lcIJ7wnmnYQV5UD1Y/lcwaMSyaQ6Bj3JMj1XSTjZbNLHn/19yA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boxen": "^8.0.1", - "chalk": "^5.3.0", - "configstore": "^7.0.0", - "is-in-ci": "^1.0.0", - "is-installed-globally": "^1.0.0", - "is-npm": "^6.0.0", - "latest-version": "^9.0.0", - "pupa": "^3.1.0", - "semver": "^7.6.3", - "xdg-basedir": "^5.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/yeoman/update-notifier?sponsor=1" - } - }, - "node_modules/update-notifier/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "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/update-notifier/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -7605,16 +5929,6 @@ "punycode": "^2.1.0" } }, - "node_modules/utif2": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/utif2/-/utif2-4.1.0.tgz", - "integrity": "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==", - "dev": true, - "license": "MIT", - "dependencies": { - "pako": "^1.0.11" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -7685,13 +5999,6 @@ "node": "^22.14.0 || >=24.0.0" } }, - "node_modules/when-exit": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/when-exit/-/when-exit-2.1.5.tgz", - "integrity": "sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg==", - "dev": true, - "license": "MIT" - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -7804,22 +6111,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/widest-line": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", - "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==", - "dev": true, - "license": "MIT", - "dependencies": { - "string-width": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -7830,13 +6121,6 @@ "node": ">=0.10.0" } }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true, - "license": "MIT" - }, "node_modules/wrap-ansi": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", @@ -7875,19 +6159,6 @@ "dev": true, "license": "ISC" }, - "node_modules/xdg-basedir": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", - "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/xml-name-validator": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", @@ -7898,37 +6169,6 @@ "node": ">=18" } }, - "node_modules/xml-parse-from-string": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", - "integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==", - "dev": true, - "license": "MIT" - }, - "node_modules/xml2js": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", - "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0" - } - }, "node_modules/xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", @@ -7936,13 +6176,6 @@ "dev": true, "license": "MIT" }, - "node_modules/xmlcreate": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", - "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -8054,16 +6287,6 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } - }, - "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } } } } diff --git a/package.json b/package.json index 453d6ca6..c00f755b 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,6 @@ "@babel/plugin-syntax-import-assertions": "^7.28.6", "@eslint/eslintrc": "^3.3.4", "@eslint/js": "^9.39.3", - "@lightningjs/msdf-generator": "^1.4.0", "c8": "^12.0.0", "eslint": "^9.39.3", "eslint-config-prettier": "^10.1.8", From 6b67468a4e1ea4042573c0c31457295086938881 Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Mon, 19 Oct 2026 21:46:41 +0200 Subject: [PATCH 23/23] Bumped version to 2.14.0 and updated changelog. --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93c43b5a..a81696ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +# 2.14.0 + +_19 oct 2026_ + +- Added built-in profiler support to for both internal and app dev use +- Added benchmark for performance comparison (and GitHub workflow to run on PRs) + ## 2.13.0 _13 oct 2026_ diff --git a/package-lock.json b/package-lock.json index c050e125..8782e8c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@lightningjs/blits", - "version": "2.13.0", + "version": "2.14.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@lightningjs/blits", - "version": "2.13.0", + "version": "2.14.0", "license": "Apache-2.0", "dependencies": { "@lightningjs/renderer": "^3.7.0", diff --git a/package.json b/package.json index 6fbbbc25..b02c406a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lightningjs/blits", - "version": "2.13.0", + "version": "2.14.0", "description": "Blits: The Lightning 3 App Development Framework", "bin": "bin/index.js", "exports": {