From 114325590591fe1bc2855fdee6ce8a989a5d1e83 Mon Sep 17 00:00:00 2001 From: Shiva Krishna D Date: Wed, 1 Jul 2026 14:03:50 +0200 Subject: [PATCH 1/5] chore: upgrade Nx 21.6 -> 23.0, drop NX_ISOLATE_PLUGINS workaround MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - nx migrate 21.6.11 -> 23.0.1 (9 migrations applied clean). - Remove NX_ISOLATE_PLUGINS=false from all scripts — builds (single + run-many) work without it on Nx 23; the plugin-isolation workaround is obsolete. --- package-lock.json | 596 ++++++++++++++++++++++------------------------ package.json | 18 +- 2 files changed, 300 insertions(+), 314 deletions(-) diff --git a/package-lock.json b/package-lock.json index 663f634..47f5f4d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "devDependencies": { "@biomejs/biome": "^1.9.4", "@types/node": "^22.15.0", - "nx": "^21.0.0", + "nx": "23.0.1", "typescript": "^5.8.3", "vite": "^6.3.5", "vitest": "^3.1.3" @@ -671,38 +671,15 @@ } }, "node_modules/@jest/diff-sequences": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.4.0.tgz", - "integrity": "sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==", + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", + "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", "dev": true, "license": "MIT", "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/get-type": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", - "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/schemas": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", - "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.34.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -746,9 +723,9 @@ } }, "node_modules/@nx/nx-darwin-arm64": { - "version": "21.6.11", - "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-21.6.11.tgz", - "integrity": "sha512-4hXhV7ShXIlfPEjjm7dJY383xM2vTcnkKr5FUncAU08GKkkL67ib5CMlQADtdi32ewfCZntqiT8gUfFFSNvKtA==", + "version": "23.0.1", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-23.0.1.tgz", + "integrity": "sha512-gQJvgPnbI91DBe23Th2CqD9R/S54cPS3C1f0DhyQ8YEf9rR7EEc+sVGjhgVxlhfOk2W7I1Gy6EkXwpN4aDoW4w==", "cpu": [ "arm64" ], @@ -760,9 +737,9 @@ ] }, "node_modules/@nx/nx-darwin-x64": { - "version": "21.6.11", - "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-21.6.11.tgz", - "integrity": "sha512-VxjKkzyhdO47X7d4JPx/f6HslERKetFmouDUBIoqbKDPVWpRegGMsRKcMYh4l61usxI1Qa2U4Ec6MgO5Fnm41g==", + "version": "23.0.1", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-23.0.1.tgz", + "integrity": "sha512-e/lvzHKN6gpuD7MqEtfH1fOfnR75E55ytYNt8jaRxKI6EvpCq+Q3MunDuh9GQYAkqDrUqE7AhHrHc+eKATVEHw==", "cpu": [ "x64" ], @@ -774,9 +751,9 @@ ] }, "node_modules/@nx/nx-freebsd-x64": { - "version": "21.6.11", - "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-21.6.11.tgz", - "integrity": "sha512-3jDn7Tb3FMFfeFTM/XKAcI+J92kDLNmxUS/N/n/+kF/XYLPgMUZQqSeDpVifk4fgy0BCoH8DSMQIqTQau6dV/g==", + "version": "23.0.1", + "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-23.0.1.tgz", + "integrity": "sha512-f582OhSYN9qHpA9Ox9qnr3kZSZ7gQHs7crmBUutmbXmZQB2TDS/TlhvYSNnxudpwHR/tuWGi2IOQqa7zGOZj1Q==", "cpu": [ "x64" ], @@ -788,9 +765,9 @@ ] }, "node_modules/@nx/nx-linux-arm-gnueabihf": { - "version": "21.6.11", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-21.6.11.tgz", - "integrity": "sha512-37tpiVod5FN/EAuCGh+uad/6nsfDFze02OjYReUPKeYPs8Q7Ac/V9j4kn/y5uZhKSle+9+sa2QR/K79B0+lNxw==", + "version": "23.0.1", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-23.0.1.tgz", + "integrity": "sha512-VjhqPc6E7aiI0e+lowrkVbdyulsmP9fgMdcX1mCzXCEu/XZDcUbZ5qveR964cMhvm5qKn0ILJtJOUqZgmOT3Xg==", "cpu": [ "arm" ], @@ -802,9 +779,9 @@ ] }, "node_modules/@nx/nx-linux-arm64-gnu": { - "version": "21.6.11", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-21.6.11.tgz", - "integrity": "sha512-r+czH0OtldQqFm2B6BBBUPW8aO+sBMvpZCgN855vko30WaCeXo8Fkuk71rQMxByz0jnoCxSnzLtEWVZm1rmJYA==", + "version": "23.0.1", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-23.0.1.tgz", + "integrity": "sha512-zX2JdHQejZWB3DRgNsh77qOVYaSSjSLuBP2qIqc7EWVlCUnR7Aj3e65PTIps4LxMMmUp4twZA2ezS0rtyK2A4w==", "cpu": [ "arm64" ], @@ -816,9 +793,9 @@ ] }, "node_modules/@nx/nx-linux-arm64-musl": { - "version": "21.6.11", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-21.6.11.tgz", - "integrity": "sha512-0FAPyWEGPCukXxR1qowvFx6Q/cU906vwPlAQtcbrBU1e2H2aMUslk9EmL8iHb5MsHdmGcFyFemwr9oN8gxmz4g==", + "version": "23.0.1", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-23.0.1.tgz", + "integrity": "sha512-9lyhxRNBgNYwHt6paq0OLzoKNoEGF5LnNW2YYrgFY8Cjtsg/Q4pcfZ1vB5o9FX9OmUgUQs3t2d4tU8YDukRUWg==", "cpu": [ "arm64" ], @@ -830,9 +807,9 @@ ] }, "node_modules/@nx/nx-linux-x64-gnu": { - "version": "21.6.11", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-21.6.11.tgz", - "integrity": "sha512-+bWJWXJ8tdddl1L3bTKE0VmvTmdsk4zzUr6P9ts9hXQbwoWqMcuA6LNqOhUfzVOL3VioirRMtKMfFuUAUhi3Yg==", + "version": "23.0.1", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-23.0.1.tgz", + "integrity": "sha512-kVszY2xRyyrCXgdCdM1qG1WUhDjNPZxtdWq86a0TyIRJjfJTP9NHqpyhmvj9c2RdZxKVWHotx6fBJzY6Vn2ZrA==", "cpu": [ "x64" ], @@ -844,9 +821,9 @@ ] }, "node_modules/@nx/nx-linux-x64-musl": { - "version": "21.6.11", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-21.6.11.tgz", - "integrity": "sha512-Mh09mLc+yeJk7DKfx7x6XfB+bm2dP1/7gyUuRQj4WjkT+Il2ZponyTuH8LmX06Jpr7efAAFk+8lBXeleV7XvMw==", + "version": "23.0.1", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-23.0.1.tgz", + "integrity": "sha512-co71K2n4zcS1SYR8EBRlCvIko7M1YycO2tZL0nrCrga87AF5dzCwx+wEclpyCR/4tNOY3FrACk4gIkVskh3CdA==", "cpu": [ "x64" ], @@ -858,9 +835,9 @@ ] }, "node_modules/@nx/nx-win32-arm64-msvc": { - "version": "21.6.11", - "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-21.6.11.tgz", - "integrity": "sha512-d7ZeCCDwaeyKiWD2JLSSxMSuSHHwIdpbnMtZtzRE6tDdAgs6e9F36/OBNQB3IRXH8V6QKOy2OGDyWeOKr01X9A==", + "version": "23.0.1", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-23.0.1.tgz", + "integrity": "sha512-7oma7iy5fbnn+x5AP7SFGMuleAA2R5RZm26dn+faikyQ4PXjoRAikWJJNiOWAeCA0BaMAeVedI6fJeAsVeDUKg==", "cpu": [ "arm64" ], @@ -872,9 +849,9 @@ ] }, "node_modules/@nx/nx-win32-x64-msvc": { - "version": "21.6.11", - "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-21.6.11.tgz", - "integrity": "sha512-otHSkhyoGilttV4RRkVmLLGb2W+Ia4b90lWxLnEm9jAAKmA9O2FUG2vAvKDHNcqTyDwwCcHfHxslgnR1u/3OIg==", + "version": "23.0.1", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-23.0.1.tgz", + "integrity": "sha512-TE/wvBa2cpkVXmk/AXUQAneong4JReS2hyNpAUONKG1yXU7TDKe0wvn1xQXxAbyspudT9NuCnVtpVuEkRz8S+Q==", "cpu": [ "x64" ], @@ -1235,13 +1212,6 @@ "win32" ] }, - "node_modules/@sinclair/typebox": { - "version": "0.34.49", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", - "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", - "dev": true, - "license": "MIT" - }, "node_modules/@tybys/wasm-util": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", @@ -1409,20 +1379,6 @@ "dev": true, "license": "BSD-2-Clause" }, - "node_modules/@yarnpkg/parsers": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.2.tgz", - "integrity": "sha512-/HcYgtUSiJiot/XWGLOlGxPYUG65+/31V8oqk17vZLW1xlCoR4PampyePljOxY2n8/3jz9+tIFzICsyGujJZoA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "js-yaml": "^3.10.0", - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=18.12.0" - } - }, "node_modules/@zkochan/js-yaml": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/@zkochan/js-yaml/-/js-yaml-0.0.7.tgz", @@ -1509,11 +1465,14 @@ } }, "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.3.tgz", + "integrity": "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": "20 || >=22" + } }, "node_modules/base64-js": { "version": "1.5.1", @@ -1549,13 +1508,16 @@ } }, "node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/buffer": { @@ -1810,9 +1772,9 @@ } }, "node_modules/dotenv-expand": { - "version": "11.0.7", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", - "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.3.tgz", + "integrity": "sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -1840,6 +1802,19 @@ "node": ">= 0.4" } }, + "node_modules/ejs": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-5.0.1.tgz", + "integrity": "sha512-COqBPFMxuPTPspXl2DkVYaDS3HtrD1GpzOGkNTJ1IYkifq/r9h8SVEFrjA3D9/VJGOEoMQcrlhpntcSUrM8k6A==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.12.18" + } + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -1988,20 +1963,6 @@ "node": ">=0.8.0" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/estree-walker": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", @@ -2088,32 +2049,22 @@ } }, "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" + "hasown": "^2.0.4", + "mime-types": "^2.1.35" }, "engines": { "node": ">= 6" } }, - "node_modules/front-matter": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/front-matter/-/front-matter-4.0.2.tgz", - "integrity": "sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg==", - "dev": true, - "license": "MIT", - "dependencies": { - "js-yaml": "^3.13.1" - } - }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -2248,9 +2199,9 @@ } }, "node_modules/hasown": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", - "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "dev": true, "license": "MIT", "dependencies": { @@ -2313,9 +2264,9 @@ "license": "BSD-3-Clause" }, "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, "license": "MIT", "engines": { @@ -2391,45 +2342,12 @@ "node": ">=8" } }, - "node_modules/jest-diff": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.4.1.tgz", - "integrity": "sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/diff-sequences": "30.4.0", - "@jest/get-type": "30.1.0", - "chalk": "^4.1.2", - "pretty-format": "30.4.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/js-yaml/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } + "license": "ISC" }, "node_modules/json5": { "version": "2.2.3", @@ -2445,9 +2363,9 @@ } }, "node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", "dev": true, "license": "MIT" }, @@ -2538,16 +2456,16 @@ } }, "node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.5" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -2589,13 +2507,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/node-machine-id": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/node-machine-id/-/node-machine-id-1.1.12.tgz", - "integrity": "sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==", - "dev": true, - "license": "MIT" - }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -2610,68 +2521,144 @@ } }, "node_modules/nx": { - "version": "21.6.11", - "resolved": "https://registry.npmjs.org/nx/-/nx-21.6.11.tgz", - "integrity": "sha512-AAgJGhS+7xlsmZF6ArKX1vgONxf7IymUYZ1BxGXHVa5927rGfgKoMaPOgwwtvN0OL3o/QYaNGwlDfIzCvlpOLQ==", + "version": "23.0.1", + "resolved": "https://registry.npmjs.org/nx/-/nx-23.0.1.tgz", + "integrity": "sha512-HnK0Ke8FcPeVQffYm1oyzkLNn7khrI8SeDeC3iyLhw/UEMCB24hjI5JSs6Amlyeb0/GaeiuQuts8NkQKd/NpGA==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { + "@emnapi/core": "1.4.5", + "@emnapi/runtime": "1.4.5", + "@emnapi/wasi-threads": "1.0.4", + "@jest/diff-sequences": "30.0.1", "@napi-rs/wasm-runtime": "0.2.4", - "@yarnpkg/lockfile": "^1.1.0", - "@yarnpkg/parsers": "3.0.2", + "@tybys/wasm-util": "0.9.0", + "@yarnpkg/lockfile": "1.1.0", "@zkochan/js-yaml": "0.0.7", - "axios": "^1.12.0", - "chalk": "^4.1.0", + "ansi-colors": "4.1.3", + "ansi-regex": "5.0.1", + "ansi-styles": "4.3.0", + "argparse": "2.0.1", + "asynckit": "0.4.0", + "axios": "1.16.0", + "balanced-match": "4.0.3", + "base64-js": "1.5.1", + "bl": "4.1.0", + "brace-expansion": "5.0.6", + "buffer": "5.7.1", + "call-bind-apply-helpers": "1.0.2", + "chalk": "4.1.2", "cli-cursor": "3.1.0", "cli-spinners": "2.6.1", - "cliui": "^8.0.1", - "dotenv": "~16.4.5", - "dotenv-expand": "~11.0.6", - "enquirer": "~2.3.6", + "cliui": "8.0.1", + "clone": "1.0.4", + "color-convert": "2.0.1", + "color-name": "1.1.4", + "combined-stream": "1.0.8", + "defaults": "1.0.4", + "define-lazy-prop": "2.0.0", + "delayed-stream": "1.0.0", + "dotenv": "16.4.7", + "dotenv-expand": "12.0.3", + "dunder-proto": "1.0.1", + "ejs": "5.0.1", + "emoji-regex": "8.0.0", + "end-of-stream": "1.4.5", + "enquirer": "2.3.6", + "es-define-property": "1.0.1", + "es-errors": "1.3.0", + "es-object-atoms": "1.1.1", + "es-set-tostringtag": "2.1.0", + "escalade": "3.2.0", + "escape-string-regexp": "1.0.5", "figures": "3.2.0", - "flat": "^5.0.2", - "front-matter": "^4.0.2", - "ignore": "^5.0.4", - "jest-diff": "^30.0.2", - "jsonc-parser": "3.2.0", + "flat": "5.0.2", + "follow-redirects": "1.16.0", + "form-data": "4.0.6", + "fs-constants": "1.0.0", + "function-bind": "1.1.2", + "get-caller-file": "2.0.5", + "get-intrinsic": "1.3.0", + "get-proto": "1.0.1", + "gopd": "1.2.0", + "has-flag": "4.0.0", + "has-symbols": "1.1.0", + "has-tostringtag": "1.0.2", + "hasown": "2.0.4", + "ieee754": "1.2.1", + "ignore": "7.0.5", + "inherits": "2.0.4", + "is-docker": "2.2.1", + "is-fullwidth-code-point": "3.0.0", + "is-interactive": "1.0.0", + "is-unicode-supported": "0.1.0", + "is-wsl": "2.2.0", + "isexe": "2.0.0", + "json5": "2.2.3", + "jsonc-parser": "3.3.1", "lines-and-columns": "2.0.3", - "minimatch": "9.0.3", - "node-machine-id": "1.1.12", - "npm-run-path": "^4.0.1", - "open": "^8.4.0", - "ora": "5.3.0", + "log-symbols": "4.1.0", + "math-intrinsics": "1.1.0", + "mime-db": "1.52.0", + "mime-types": "2.1.35", + "mimic-fn": "2.1.0", + "minimatch": "10.2.5", + "minimist": "1.2.8", + "npm-run-path": "4.0.1", + "once": "1.4.0", + "onetime": "5.1.2", + "open": "8.4.2", + "ora": "5.4.1", + "path-key": "3.1.1", + "picocolors": "1.1.1", + "proxy-from-env": "2.1.0", + "readable-stream": "3.6.2", + "require-directory": "2.1.1", "resolve.exports": "2.0.3", - "semver": "^7.5.3", - "string-width": "^4.2.3", - "tar-stream": "~2.2.0", - "tmp": "~0.2.1", - "tree-kill": "^1.2.2", - "tsconfig-paths": "^4.1.2", - "tslib": "^2.3.0", - "yaml": "^2.6.0", - "yargs": "^17.6.2", + "restore-cursor": "3.1.0", + "safe-buffer": "5.2.1", + "semver": "7.7.4", + "signal-exit": "3.0.7", + "smol-toml": "1.6.1", + "string_decoder": "1.3.0", + "string-width": "4.2.3", + "strip-ansi": "6.0.1", + "strip-bom": "3.0.0", + "supports-color": "7.2.0", + "tar-stream": "2.2.0", + "tmp": "0.2.7", + "tsconfig-paths": "4.2.0", + "tslib": "2.8.1", + "util-deprecate": "1.0.2", + "wcwidth": "1.0.1", + "which": "3.0.1", + "wrap-ansi": "7.0.0", + "wrappy": "1.0.2", + "y18n": "5.0.8", + "yaml": "2.9.0", + "yargs": "17.7.2", "yargs-parser": "21.1.1" }, "bin": { - "nx": "bin/nx.js", - "nx-cloud": "bin/nx-cloud.js" + "nx": "dist/bin/nx.js", + "nx-cloud": "dist/bin/nx-cloud.js" }, "optionalDependencies": { - "@nx/nx-darwin-arm64": "21.6.11", - "@nx/nx-darwin-x64": "21.6.11", - "@nx/nx-freebsd-x64": "21.6.11", - "@nx/nx-linux-arm-gnueabihf": "21.6.11", - "@nx/nx-linux-arm64-gnu": "21.6.11", - "@nx/nx-linux-arm64-musl": "21.6.11", - "@nx/nx-linux-x64-gnu": "21.6.11", - "@nx/nx-linux-x64-musl": "21.6.11", - "@nx/nx-win32-arm64-msvc": "21.6.11", - "@nx/nx-win32-x64-msvc": "21.6.11" + "@nx/nx-darwin-arm64": "23.0.1", + "@nx/nx-darwin-x64": "23.0.1", + "@nx/nx-freebsd-x64": "23.0.1", + "@nx/nx-linux-arm-gnueabihf": "23.0.1", + "@nx/nx-linux-arm64-gnu": "23.0.1", + "@nx/nx-linux-arm64-musl": "23.0.1", + "@nx/nx-linux-x64-gnu": "23.0.1", + "@nx/nx-linux-x64-musl": "23.0.1", + "@nx/nx-win32-arm64-msvc": "23.0.1", + "@nx/nx-win32-x64-msvc": "23.0.1" }, "peerDependencies": { - "@swc-node/register": "^1.8.0", - "@swc/core": "^1.3.85" + "@swc-node/register": "^1.11.1", + "@swc/core": "^1.15.8" }, "peerDependenciesMeta": { "@swc-node/register": { @@ -2682,20 +2669,35 @@ } } }, - "node_modules/nx/node_modules/yaml": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.4.tgz", - "integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==", + "node_modules/nx/node_modules/@emnapi/core": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.5.tgz", + "integrity": "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==", "dev": true, - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" + "license": "MIT", + "dependencies": { + "@emnapi/wasi-threads": "1.0.4", + "tslib": "^2.4.0" + } + }, + "node_modules/nx/node_modules/@emnapi/runtime": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz", + "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/nx/node_modules/@emnapi/wasi-threads": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.4.tgz", + "integrity": "sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" } }, "node_modules/once": { @@ -2743,18 +2745,19 @@ } }, "node_modules/ora": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.3.0.tgz", - "integrity": "sha512-zAKMgGXUim0Jyd6CXK9lraBnD3H5yPGBPPOkC23a2BG6hsm4Zu6OQSjQuEtV0BHDf4aKHcUFvJiGRrFuW3MG8g==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", "dev": true, "license": "MIT", "dependencies": { - "bl": "^4.0.3", + "bl": "^4.1.0", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.5.0", "is-interactive": "^1.0.0", - "log-symbols": "^4.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" }, @@ -2841,35 +2844,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/pretty-format": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", - "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "30.4.1", - "ansi-styles": "^5.2.0", - "react-is-18": "npm:react-is@^18.3.1", - "react-is-19": "npm:react-is@^19.2.5" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/proxy-from-env": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", @@ -2880,22 +2854,6 @@ "node": ">=10" } }, - "node_modules/react-is-18": { - "name": "react-is", - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/react-is-19": { - "name": "react-is", - "version": "19.2.6", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.6.tgz", - "integrity": "sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==", - "dev": true, - "license": "MIT" - }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -3038,6 +2996,19 @@ "dev": true, "license": "ISC" }, + "node_modules/smol-toml": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.1.tgz", + "integrity": "sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -3048,13 +3019,6 @@ "node": ">=0.10.0" } }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -3242,25 +3206,15 @@ } }, "node_modules/tmp": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", - "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", "dev": true, "license": "MIT", "engines": { "node": ">=14.14" } }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true, - "license": "MIT", - "bin": { - "tree-kill": "cli.js" - } - }, "node_modules/tsconfig-paths": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", @@ -3492,6 +3446,22 @@ "defaults": "^1.0.3" } }, + "node_modules/which": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", + "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", @@ -3544,6 +3514,22 @@ "node": ">=10" } }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/package.json b/package.json index eec59ca..d8963c8 100644 --- a/package.json +++ b/package.json @@ -5,14 +5,14 @@ "description": "Configurable LightningJS Smart TV runtime platform for hospitality, IPTV and OTT.", "type": "module", "scripts": { - "dev:samsung": "NX_ISOLATE_PLUGINS=false nx serve samsung", - "dev:lg": "NX_ISOLATE_PLUGINS=false nx serve lg", - "dev:android": "NX_ISOLATE_PLUGINS=false nx serve android", - "build:samsung": "NX_ISOLATE_PLUGINS=false nx build samsung", - "build:lg": "NX_ISOLATE_PLUGINS=false nx build lg", - "build:android": "NX_ISOLATE_PLUGINS=false nx build android", - "build": "NX_ISOLATE_PLUGINS=false nx run-many -t build -p samsung,lg,android", - "build:ccl": "NX_ISOLATE_PLUGINS=false nx run-many -t build -p samsung,lg,android --customer=ccl", + "dev:samsung": "nx serve samsung", + "dev:lg": "nx serve lg", + "dev:android": "nx serve android", + "build:samsung": "nx build samsung", + "build:lg": "nx build lg", + "build:android": "nx build android", + "build": "nx run-many -t build -p samsung,lg,android", + "build:ccl": "nx run-many -t build -p samsung,lg,android --customer=ccl", "package:samsung": "node tools/packaging/package-tv.mjs --app=samsung", "package:lg": "node tools/packaging/package-tv.mjs --app=lg", "package:android": "node tools/packaging/package-tv.mjs --app=android", @@ -30,7 +30,7 @@ "devDependencies": { "@biomejs/biome": "^1.9.4", "@types/node": "^22.15.0", - "nx": "^21.0.0", + "nx": "23.0.1", "typescript": "^5.8.3", "vite": "^6.3.5", "vitest": "^3.1.3" From 2853643ce563de3b53edf57bb6caafd01a723f66 Mon Sep 17 00:00:00 2001 From: Shiva Krishna D Date: Wed, 1 Jul 2026 14:42:21 +0200 Subject: [PATCH 2/5] feat: tenant-driven fonts + state/storage foundation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fonts are tenant-declared (customers//config.json `fonts`: default + families) and tenant-owned (customers//public/fonts). build-tv materializes the active tenant's public/ into the app before Vite (Blits' msdf generator scans /public), so only the active brand's assets ship. runtime-config exposes FontSet; core drives Blits.Launch fonts from it. - New @x-tv/storage lib: createStorage(namespace) — namespaced (xtv..) persistence over Blits' storage plugin with in-memory fallback, usable anywhere (bootstrap, libs, components). device-info deviceId migrated to it. - Register Blits appState plugin in core (seeded from config) for global reactive state. Drop unused zustand dependency. --- .gitignore | 5 + CLAUDE.md | 10 +- apps/android-tv/public/.gitkeep | 0 apps/lg-tv/public/.gitkeep | 0 apps/samsung-tv/public/.gitkeep | 0 customers/ccl/config.json | 8 + .../public/fonts/TempoStd-HeavyCondensed.ttf | Bin 0 -> 25344 bytes .../public/fonts/opensans-bold-webfont.ttf | Bin 0 -> 46600 bytes .../fonts/opensans-semibold-webfont.ttf | Bin 0 -> 45232 bytes libs/core/src/index.ts | 21 +- libs/diagnostics/src/device-info.ts | 10 +- libs/runtime-config/src/index.ts | 22 +- libs/storage/project.json | 7 + libs/storage/src/index.ts | 78 + nx.json | 3 +- package-lock.json | 1790 ++++++++++++++++- package.json | 4 +- tools/executors/build-tv.mjs | 16 + tools/vite/xtv-aliases.ts | 1 + tsconfig.base.json | 1 + 20 files changed, 1936 insertions(+), 40 deletions(-) delete mode 100644 apps/android-tv/public/.gitkeep delete mode 100644 apps/lg-tv/public/.gitkeep delete mode 100644 apps/samsung-tv/public/.gitkeep create mode 100755 customers/ccl/public/fonts/TempoStd-HeavyCondensed.ttf create mode 100755 customers/ccl/public/fonts/opensans-bold-webfont.ttf create mode 100755 customers/ccl/public/fonts/opensans-semibold-webfont.ttf create mode 100644 libs/storage/project.json create mode 100644 libs/storage/src/index.ts diff --git a/.gitignore b/.gitignore index b7ac2e1..574d433 100644 --- a/.gitignore +++ b/.gitignore @@ -147,3 +147,8 @@ vite.config.ts.timestamp-* # Signing secrets — never commit cert paths/passwords. Use signing.example.json. signing/.signing.local.json + +# App public dirs are materialized per-build from the active tenant (generated) +apps/*/public/ + +.claude/worktrees \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index c499247..5a6cfb2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -48,7 +48,7 @@ remote override) · `layout` (server-driven layout + renderer) · `widget-regist · `navigation` (keymap → `xtv:action`) · `muting` (audio muting, ports & adapters) · `service-gateway` + `integrations/*` (backend adapters: xmm, liferay, remote-control) · `websocket` · `diagnostics` (overlay) · `player` (ports & -adapters: avplay / Android bridge / HTML5) · `feature-flags` · `themes` · `i18n`. +adapters: avplay / Android bridge / HTML5) · `feature-flags` · `themes` · `i18n` · `storage` (persistence + Blits appState). ## Bootstrap flow (`libs/core/src/index.ts`) @@ -108,6 +108,14 @@ Sign a build by exporting `XTV_CCL_*` env (see `docs/signing.md`) before `build` known widget (hero) from config; fully config-driven multi-widget + feature-gated Blits layout, keymap→Blits input, and Blits-reactive hot-apply (currently a soft reload) are follow-ups. +9. **State & storage.** Local/UI state = Blits component `state()`. Global reactive + state = Blits `appState` plugin (registered in core, seeded from config; read via + `this.$appState`). Persistence = `@x-tv/storage` `createStorage(namespace)` — a + namespaced (`xtv..`) wrapper over Blits' storage plugin with an in-memory + fallback, safe to import ANYWHERE (bootstrap, libs, components). **Durable state + (guest prefs, entitlements, resume points) lives on the head-end** via + service-gateway — localStorage is best-effort only (TV quota; wiped on + update/uninstall). zustand was removed (unused; Blits covers reactive state). ## How to… diff --git a/apps/android-tv/public/.gitkeep b/apps/android-tv/public/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/apps/lg-tv/public/.gitkeep b/apps/lg-tv/public/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/apps/samsung-tv/public/.gitkeep b/apps/samsung-tv/public/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/customers/ccl/config.json b/customers/ccl/config.json index cb06c69..3ba5f67 100644 --- a/customers/ccl/config.json +++ b/customers/ccl/config.json @@ -57,5 +57,13 @@ "actions": { "back": ["XF86Back", 10009, "Backspace"] } + }, + "fonts": { + "default": "Open Sans", + "families": [ + { "family": "Tempo Std", "type": "msdf", "file": "fonts/TempoStd-HeavyCondensed.ttf" }, + { "family": "Open Sans", "type": "msdf", "file": "fonts/opensans-semibold-webfont.ttf" }, + { "family": "Open Sans Bold", "type": "msdf", "file": "fonts/opensans-bold-webfont.ttf" } + ] } } diff --git a/customers/ccl/public/fonts/TempoStd-HeavyCondensed.ttf b/customers/ccl/public/fonts/TempoStd-HeavyCondensed.ttf new file mode 100755 index 0000000000000000000000000000000000000000..8cf64be4493366bd46f469ba1fe1462f7753fcb3 GIT binary patch literal 25344 zcmcJ%31C#!^*?^^eUq8&nSHWnmh1_WWG0!(9tcT*uqPqxkU##zJUI|Z|_>%JbBi_Vt_(>Mmn0;b<=SK{ZafT=FaAh z*8cIqvk95@JaG6&ch{=kkEiE+L`WmX%T^Lj3rfbQ)uGCTQKkPw!X&^YB;QSO@Q5p3QcINLH*ZPBwb9}?0MKbt z9cZCo+u1B!n99uPSVf_XEI*#rSei_7OXen9vxZv7*HL^ul`l;u<0z7&hV90FDVFda{9dX&$Yl^|YC;qZ{a* z>@D^-`$T$7Ix4+v)|+F^iDs)g)tqZCHdmPsS#%bwCDmfLGON-WWz|{@)>vzzHOo5Q z+GK4_dFNjY`km{ahXHjAt)oqVdJ~|&38)|Wpz6%g=6JJNKrI>pss>PF2BEe9D)oB3 zfA+rWy~DfNyUM%NJKa0kTkKJJlmp)lj2eg=h(7n`x!2CUdhV5TFP(e#+!N>aoh$p} z@BjGrAK(1rZ_a*w_RF(>I(zQy*|TTPesuQu*{9ClcXrnqN6;0SGQjr#@gIf87s?oL zDZ>{pfq%FuDT0)$;E>R;@QBDLwMMJc8;qvtnAo`Zgv2DX#cE4VNli=7$jr(fWzWgY zb2#%0T<*f6;*!#`@`}-8D#v1F9yfkM?Ziowr%atTy{^7tM&ry`v**m6H-Evxre?C` zC);kl>)r<+*}HH5&mVp4@q>q+c=GU5zj)@^BhNkm%cH-3@uffYw6re!^SYmPulQ=s z@_%mK^X;nd|Fv}OrN4di(LdVPe|X@9O^ZAK=lFMj?YQaFMVo)T`;9mMaQf^Y&wTu= zE3dqL;mgmz81S6G;gfTp_1$*M?c0C4^Nt<&>>_vfKXCu6|MkJjlfON6eu zqw)D+Yn)ED$%(BYsHL7yv$GEq5RxkP!la=-Fb<%cST zDpBQ9)vD&HmaA@5-KN^3I-oio%!19qHNl<1cL%={d?AE{B!`rSj1O5CvMc0B$mviL z8XY=5bb093&^@8ghn@`mJoLM;%&@|+H&3@icR=^4K3JcvuhaMG_v&BOe`m-w zv>EmpzA+XWHyTfv)TVM%yXisG$EHisB-$8V7~L7YKl+{MOEJcnDKVR4_Q!k>^L?y2 zwl;QS?1Qnd$9@xgCC(UUkDC*BZ`{fF(D=gm#`xa&eeuu5AB+Dm{!#)-NK7bBn3K?% z@L{gjij^a zOd4tlqZ%E({QW`Cp<@R z2yshJNty3*81+hPVc5OlyPP#nXU!#g*%SV2e&*y|GprS-s7tg_5u(#L(YI6cX1C{j zH*Tms-|)igg#yptqh1_I!kNmZvbjZZJKZ)Er%DuDPqV3<^r+Mj+ZZz^c4kbSG*?|G z&54~AI|o-LO7-e-bJTNa-h%h>x8UXlAK-7n%{Sl7HL=!v8+}ho235jH8n7vdqz1jo z;3~*Z4RRYz1*rx3E_ZkY+b&lfdi<-ci}TRyhBq)7kL ziu&@E^u$({7VCU>M_5U0>d`gD%17OM=5+=&&JIrPGzZ;Za-d-YyYWV@c@#wF{g(ZM zeNAFW3dtjGjL}JLG)-xnDx40NR-Z^6@wyyK&MaGm#;lEq(3&-#(!o>u(eB;5*w+K0tcS+vGD|+C$D-7U z>ZoE*6I!+*~(x5jcQJ34KRGDlXZ&1t4 zDdu!=N=a#)m`=wfS6q5@bp7=B$WB|~XKsHS!5#HOOEMtxk3g??`-(8ngl zXzZKU+mj2M3vN%gFJ5}{{OJp;=hoMj%<-JQLz5pp1q6ake2P3Oct>!K+s87XC-q+O zE+>D(Jy`e`&%JcD~&@%d*(D z82^@8R?!FcMNAJ|Ar(5XkJDfS+t>=eYddnJ?FdbH_SrW41D7E0F7i9F2NWhIol^>$ z)Yrb8Dm+&>p3&q{@)gbE_jG9e)%!H7uMgLG4=2bWK2DcU+N?s-MS(q=h zrIJlYvp!t*p_uh=`PdjS;{H3hA4YWeZH=UZeC05zmDW@>RoP>8x>){o{|DXOA9S;} z*p(lxjOAYs9`$TKdX#PeE**fM4;o=@)fKp%8k?r0b%4mB|S@CK|N^Ohso*Mf3<;$L}ot<~na~?Gxrx|#?igp<15MEZ5&SJ2sd?I19n)4h5qsG)v zOpCM9^dn)Bx>@V`Iu~YcwwJp~s#SFDj@8oUWj)yD`se68(ZV|99L<3wG64_~ZT4paP0-jLA z^bGLCN}wySfIFP@z*j$ei_O`JIh(`hjO)d3=}EyKR+5E!;UJ-8jdVf^K@w#e>quRL~y~5O9mm&pw#L2_@xa zjq^i{cFUTk^2mf;d6tw>xz?0XtghX@vC&dmo<9+Y!E5xUoU$z2sEW+PVRIdk#T}PQtvOgC1WBS?2bMGtHLf)Y)J!*q&bI zVw3uxyCsHS9xv2jE>MMu=b_et>s3vIyzpD&l1k5HG9IpJ-HI{<044k32=|PDSpw-g5jF(4FJ8|sCip3PtJct0)dhsAQf?*(3mkKrkd0kok`O=OU)iu}WX zOcvNEc`C>ls#ixx?7X*sU2&e(Hf4NgHr?vk%#>0PW5=I-?38wwv1iAL9Wq_GU-6QR zAH@Q~KhU%qdZ~YVfB$xNejt(NdQQTrc8c0j75)`M@DsDe@pPW-@5c|UdEWuIFr0!q zX$2sts{l|qy9ett6XN?DCsb;-YLYWq_`quobLH_eULQV94oBmJNb78p?Kl9gd1(97 zkKN5ZdC$}Fo(DY}X@j7p10BJh;JPz|@-}q2=NLy|;DET#ARvcuAKFC+c=!OI{F!^8 zp4A_iy=N3_Y;R13fgnK0j<#kT5D{%Tc2Wg@`6Tl=zcWX z?}4TkYXnnZK7>w6R$J9XtllmMZQ4(3x18Uy<@{E9%44UGcy4>;725lXkB>enD0~I-t+9=y8qR;jnA^U-<97)o^qCrL4P*vy=Cx&7!}TMSr2NPO!?Epn2Vy9S6+By0fy|u z=QH3F4zH(}2?2u>PGfN7AMZca*1mG%{*?@L?^(LX>UoA%QT^+$3wlLj&bMLwW=OX@ z=lOOR6@I}o=o2NeNF`+#rMa@oE3#J38&mEcRh2!uB75WV+L-&Zvl0@EoJ9?J^VcTs zwpB#u<(14YTDFq&8Rn3#6?7t0uw{XTu3gf<1VSEHd_kQ4xFFAa^-x0^`67mUuD>Flrx@9+PH)*efW(#euXKnqld8+ zpGUomAkcR>gH0hhjark_rE~Icts6~JS(;7BR5o3|R20h86LwBev(O^xf!ps2s>2?* zF6b_HUh8>gW*eR2dAe;Tova;5jHPW&O`dyW0sAy?xD43fHOe_Z4Ui zTJ-oYA3ab9WcGU2pFBzX=z@c2^E=di1#O@s&<2t!0eKp?|8W1_{ZqNPT{bMwg!woT^OxD_^_w{=dAIzfrGcPMqZDk!FGRNu4e)#8c^Op^jJaR zE@@rk02g?`Itp6P5xfpsP+L_Sb+vJumbcGE`;YzY3m`QG&W{fj7Cg4AksWWu{aN4= zg8SS~g~5ZtzSz2d-u~7ux{c2EJo?`p2Jzkkuko{7uLM~z*Vwkw%Qr#d(f4~yO^&j? z{GKk&X_~d|{yL_<^G@7j*c*LES7MDr<>+%uoc=oEs2YIJm1@0Q%fsbvrOqwj=Yal^szyV50;UZ z^BERoF0&dZ?}ux-G~2?r*A^)X9$k7uCe+M~~LWrDV)#Y@Crnk8d(uGHg<_Z6`SqNemya9~X`X z#_KTo1}a1kSX~sj+||eQl^m4<3+sDapt9ao4m5IUp%|ADusVTM6My`U==07*{a9{{Y*+v%@mKTnShL0;eEL81w_tEY}ca_zK-zG^xlxibaEb{E+ zG>`E%ig^vi{v6sxwzZ)xp_T=`o!kN~qXF~l{5nAwlM$(eChSC zpC-6uxN&v3ua7ovUAghLCaw@_*rAs^U;j78I10W00`-0CHn77FdF-qNJYeS77@Xx4 z<^=!Z(xVYnA3A1iVPQ^lQC7G+$(^&+9HH$)!3yV2u$IIpjh*0mnx+ zd>?HMTw+G1Ej{H7BrRyg<%IL6yk8Jfr_?h+-PHUi({*qV+7C819&B9V+T(KVq2=Jn zZ6{?qr+S;W9FkarPi7$}v88@F!8V`E?7HM^E-1&uoW%f^tYZRT@%^@} zUSz3zT*#brQ2t9}{qo;CutP{cH?_pP#Q`taNt{~*#SNUz4)@*6Gu;c^E?n@~q5t7Fp&r!1I*(nBs3W!! zsJ}0-Hjmjiw5-Ty`72sB*uOh{`n1P>>=-?T1s2Uj+nqjN5JUv7GI%o6oUi5Hv1aof zrKNXlUULUs-uBtW&d!UUiTcnx>rfxM489vURk2>tb^mp1#j-7#Y!W>+(Dyuj-Q%Tl z+?rr+1n$sxVu8J-ohIC}-SY-*>Gbq>qJIz8#@|BJY_K5;9Az}$RYLiEC(uZWO^uX5 z^+s_e757+ra`s%EUQM0msOWf&TCba%olHsN*e?n*8%=4iI7cyw!WV9SCC${BS$JWR z7$Yh1D#;`4l2{)>|B|*gcIgU~For-J@V5oY{ZoS#y}`&V5UMl3oVg3^G!5&bxMajl z8sx)36CWLAc2XY(jg6Bo$QYQj^}$qPqnt7ZUyS8_%p$uOcD};bWQt^14Z*0w8DtU+ z^hb*}+8&ouSm?+}v}Dv;t0rn=l4)suOnhvjxw3w0g2f74`QEmPoM4r*E{&r$oeREf zu1|k8=+CAN+%6b&Xj9VV-2;=*F7`xxEd|fI#cGO8cWRp7gY2MfSXFHa`ndS$;_4Jj zO?9z>p18@dwzQZUHiXmm_K2~g=g!*{-q3*YC}8WH#X1Jlhp%Hkr>aS1#|E0aT1=1) zZfTob(34oZvASW=FvfD#!96~6U*`SLHi%ZsT33r7W=9WszNWJks5i;=OR@l08 zASdw06W_Y8GpUR~1LnV#J4aLVxqsBox26el#=Ge9NJ1&)7-cRG0V5zH;OM!}^9nNz{5k9hX(^6W*O zSjgUK(0vpsax4* z8an8XCGM$H-P7{d%%0QTA0HeaQ)Uw1>gf1rQ-VGV>@_ueael^_h8YWLRn8QHvuMnm zyt>ky2@UgF=zF?^a9x}(N9Q|&^zduE$<#EO^SQ}icDccqF7ut!!E!w>e=(;0_C2?^ zj~Q6IJJ;HjP^pHZy#Bo~t;gA@tqnUB*<&%U2; zxOtyv-^3Xk7B;P)KC!a8qvSTMi5x@JSZcj!br1Kc|02V{iN1rp$@v#L1&3r%O# z|GXwUyC(bAb@lb@>RD)RO>S;YJ5KA@$!&*X8)zFC-O!*HUxec%ZR+W1>+NmxJPhY0 zMUG4sHKIRQF9BXje+&ar}hTW<^6raqhfg zT^8@35{afz9bj>g2>J{NmqIfgU3 zvT^}yo$ByZ(;1xR40&g7kWU4_fC^f%<8$SwiN1K$&$T%=A|f_C#$D*{E-YjRElpNy z)8hKkW9sY2jIKxfY{=dvzwD*J`Z1|2;+uHatX{izo##1msP#$TLp(k@8h++S&?k?> zX!&S)EXFhxiwUw(XON6AGmWj@Zl8D8j%K?jyp}#*JMcM%KvV9z>(7;yv#Hth`+oNA zPX-4h5jSA(6|h4gqr@}>iuyxTY%oM+vu@%rJ$epv>b0C2ItbhI+K4=>Afba}F+&gK z$YV+QA?^(tK2ScwfxnHw(QJut-02)gK4kl~EFM4wc|J8*=PFnwG?W?M4wI~4xi#j5 z`sDjsiEk-<0UoNjPM|Bh$l5N>um0vGym9+u{>Tc~Z(Jf*fma2m21ZFFaH=m-@@ww&lcOa*CY56{Bew&G z`r0Y@TH4iJ_XpqZ_x>7sgVNYUE;YHNFX27i-A!aPVIom4TwHGdXwnWjXwl~ z&&Cjp98Ah^7*CA^16&x0hi|rqz&s^S+SQP_j`ZO&s3V8N@$E|kbmS1wkZY+nyn}s1 zY7#Rn(=9+x2KJPE71s~-;`uy)bB4noH;jwGII#H#^L8pIcCx;#pQ@kztY+`t8u2~; zXFnUicdz^{WYOq-9P??z{#cFdDEMq(lffh_Q0ZWnA)8FXwpi9(fF>`gX>|Tgy4ZU9 zv(5UXdNy@x#kegitDKori$-r*S>~XJJ%5k>GAuKA2aU0OX|sE7HE(JC^$V{g&$ZsT z_?Z`8;SOKUt337uK8oV{m-}&f0){{O#8=qC2-J&0dWT1$SdGjF{{{|UX$RrZULoRF z0s2S)3WNhs-Zl96^qM~?c8^~RTmww-lsvXw7@J9!#Nng6dQ}|6Fe_lLua3L1uE6PL zax4q<3*ny7p=g$l%a1>zwMPtiD5^zY1j{M57pU*3)3VBd^MvujiA zMY&r-W2I<~IwmqaNMo?F+BxHAH;=6dVv70g=M>#EbHZ#FTSP;x>BY%uyf2=+$Ky47 zv8S-`{{DT0iW}zFjZzhXmqw0M?H7FTf6*VO_;vgH1~V{nf3%QyIK02~|73iy6!=mu>|waW222xSLoNxpC5^SxU5hAfCHVT=d- z1VQdyT=Ka;PwDeJrN~yjFPoJcoW9*PKNT@a_@oe~q}_FOeIk-eDGyENPtqn&@rQDw z(-GaBk&|2@2P0M6}@A-v5$fHz_`T>M8sJzf_45qsc5 z&utLiN57yAm#*u_tgm*;j?mqffW>NrK0}8|62tRX#Fv8*V4EX~IvWJ1r^-PC#4o_l zvLDM=hg*ZFvjBvnr%v)Zc$SJ#z3_Dnx6M&)lS}|!laE)zvsFa+ndSZCh_*SJeHAbe zdTQx#yyX}w)=D`)!slz`aRUSL*SYVR2YgdS^Z;8vlN?p;OK-UG@fnTQsl_TuWtova z%VKn<+vF%KD=jR1@O97Mx3!I3R!x)Lg@wg=`W$l##t#oN=_ z-cxlTJrzDW$Z-u}`CZ+ke@Ey)2KL4R*l<`c7lv^pr*{>epw!cU;!Id@DRA%mcMyKT z^=06`B~l=G_C!wAvu(>n`h({eaux*7zk3GyKo9!Bx8lcD@U}=1;Sn*}^q2A6S(%;P zI3pz?qcYdtI3q35hvho!yfN)<+oBq*Efp)|)zI7-gR)#y=Qo z=ZaIziOA^?x-1|MLmHNa^+P!w$bvoY%j%GPc=U^XBSPhavHq)#v7y-i#bM(d#BP|v z_2tXXgiIH-5yIP zeCyuMt9!5D`@GoaAmi9J*y0y`d3k+#bR+U&Ym94*H1%ua8r0%J4 zM3uD->FG$_Nw!hHJ@%rUQid^!98>HQ&HM?mAIvgE_LCD?P}fN|ee$Ly-TC=F*UdSV z^nB4ph8<*K68KZ}_d^*sJewWeT|edy&1BICup{{${$K?}L9 zFT=%0W`Cl~$G%aDd>3S8$~pycLdf|i;6*cfMbbbyo~4t$$2^@t;#)B}P2BxUSDh-_ zm|B`{OIMrX&Bme`jka-P(h~3>U~~kXw#cw({Io3nt(tMIi#+e&SZ}ypZ^?mdyn~n_ zx1*GR@AGcRY7T)z9}dIn$RmEjH_|~oe;`had|UJpf<6$j);WzxMZp6VMu+R|(RAk4 znRGOB);ZbU@`i?TOA5C3$Rk9a>a$|+he*1d1t`b0o1>;RI8OdditWL-; zMZQ8bHNh?uo~mni!N0%OOklf)ECYt;`_+h7?}a9SUCS3M3(D;?7iQ8%Xlfp*%q$GbY#aMX$8+k!`P) zHFvmX?ah~Z8xr=cU2;p`ijIfk8+tkX|1Ys%_^6qVr3(+WO`}InMS;eK^zCx$7O`s! z3oQ=cq+S}4wb7WsoN-ZEopcRy4$jYWBrl^$p6~W8{AWXO6L&F!ribZen#^@Pw_W61 z<-=SDi+j?Ap?lJWE78&g-@Qm?B*&o7(N2e5(5+>yGuZj#$3;5@?!Dx@hghu4LPxwXMGnqY1NJ$ckbMICkx#$ z@D6irKuBY#F8B{w#)#shrl>8F44w4Uowsoo;$M!BKfdwn8&99U@oTiJB+t`lSO(8` zhos06QEO%Uq{;0a_tNA&EBF~-pQht$H{s7@a*yR;?A~uv`j6AaQ(`nW@E|{K7WfT~ zf@gyj)5O?P5HEIjCP z1G5He(u0Gi7Tlr6!Doc&?hzg)_V5RX*ALe5JTQ2=On#&ow9P?IK%&SA@MYs17|g&C z{1|W_zIbE|Mtz~1I6}lj$F{o*ic~i{4nnI$rq0Rr|{H?~@N?w%Gke31xO%Yl#tw4#4Jm&U$ zc)5eu>5#`pc#@(ox7$B;OxU2nq}|@vIR)@mv$12dH&9rsMT%~@9TRLkLi{NwPG-|h+Ry5sJSP$dfu zqDvGfB15~!mQ+psgchGY`+)jnWN3R=sQO*3%Aip){Oo6>(b!eMcJpT*Vdj0&WFsD> z5&k_Lg7n~MO3O`VySt0te)ZHB z6FbW`?oKx!x|gJS4uoV?9>@jED};(ZOFNee(Awy zJnJZ#qD)aR$)>7fy9PQNd#K8Mn?b+9%;zH3dq3-8XM}DOGbv`%!g}g|^7AeAY!;qC z8p~efx zQz~98AWRN$jLGFLm%undPP`()ZAa#>(x5b^*sPIw

qIQS9ja(e|lX8FjAmx$|n~ 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 literal 0 HcmV?d00001 diff --git a/customers/ccl/public/fonts/opensans-bold-webfont.ttf b/customers/ccl/public/fonts/opensans-bold-webfont.ttf new file mode 100755 index 0000000000000000000000000000000000000000..f801c97e65ed2d0a164eb4182eab8c63037676ca GIT binary patch literal 46600 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�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 Hen|9hc?HA? literal 0 HcmV?d00001 diff --git a/libs/core/src/index.ts b/libs/core/src/index.ts index a8c61bc..a4bc8d9 100644 --- a/libs/core/src/index.ts +++ b/libs/core/src/index.ts @@ -8,6 +8,7 @@ import { import { createAudioController, createMutingController } from "@x-tv/muting"; import { createRuntimeConfigLoader } from "@x-tv/runtime-config"; import { createServiceGateway } from "@x-tv/service-gateway"; +import { appStatePlugin } from "@x-tv/storage"; import { createWebsocketEventBus } from "@x-tv/websocket"; import App from "./app"; import { setBootConfig } from "./boot-config"; @@ -90,8 +91,24 @@ export async function bootstrapTvPlatform( const runtime: TvPlatformRuntime = { appId: options.appId, async start() { - // Launch the Blits (LightningJS canvas) app into #app. - Blits.Launch(App, "app", { w: 1920, h: 1080, debugLevel: 1 }); + // Global reactive app state (Blits appState plugin), seeded from config. + // Components read/write via this.$appState. + Blits.Plugin(appStatePlugin, { + customer: runtimeConfig.customer, + platform: runtimeConfig.platform.platform, + locale: runtimeConfig.locale, + }); + + // Launch the Blits (LightningJS canvas) app into #app. The font set is + // tenant-driven (customers//config.json `fonts`), served from the + // tenant public dir with relative paths so they resolve under file://. + Blits.Launch(App, "app", { + w: 1920, + h: 1080, + debugLevel: 1, + defaultFont: runtimeConfig.fonts.default, + fonts: runtimeConfig.fonts.families, + } as Parameters[2]); if (runtimeConfig.diagnostics.enabled) { diagnostics.mount(); } diff --git a/libs/diagnostics/src/device-info.ts b/libs/diagnostics/src/device-info.ts index 3fc698f..e623ec9 100644 --- a/libs/diagnostics/src/device-info.ts +++ b/libs/diagnostics/src/device-info.ts @@ -1,3 +1,7 @@ +import { createStorage } from "@x-tv/storage"; + +const deviceStore = createStorage("device"); + export interface DeviceInfo { platform: string; profile: string; @@ -89,15 +93,13 @@ function readAndroidDeviceInfo(): Partial { } function fallbackDeviceId(): string { - const storageKey = "xtv.deviceId"; - const existing = window.localStorage.getItem(storageKey); - + const existing = deviceStore.get("id"); if (existing) { return existing; } const generated = globalThis.crypto?.randomUUID?.() ?? `preview-${Date.now()}`; - window.localStorage.setItem(storageKey, generated); + deviceStore.set("id", generated); return generated; } diff --git a/libs/runtime-config/src/index.ts b/libs/runtime-config/src/index.ts index 91275bd..7da4011 100644 --- a/libs/runtime-config/src/index.ts +++ b/libs/runtime-config/src/index.ts @@ -21,6 +21,19 @@ export interface PlatformProfile { capabilities: Record; } +// Tenant-declared font set (Blits font descriptors). `file` is relative to the +// tenant public dir so it resolves under file:// on-device. +export interface FontFace { + family: string; + type: "msdf" | "sdf" | "web"; + file: string; +} + +export interface FontSet { + default: string; + families: FontFace[]; +} + export interface RuntimeConfig { customer: string; locale: string; @@ -37,6 +50,7 @@ export interface RuntimeConfig { services: ServiceGatewayConfig; keymapOverride: KeymapConfig; realtime: { websocketUrl?: string; mutingUrl?: string }; + fonts: FontSet; } export interface RuntimeConfigLoader { @@ -52,10 +66,14 @@ interface TenantIntegrations extends ServiceGatewayConfig { } interface TenantConfigFile { - runtime: Omit; + runtime: Omit< + RuntimeConfig, + "layout" | "platform" | "services" | "keymapOverride" | "realtime" | "fonts" + >; integrations: TenantIntegrations; identity?: unknown; keymap?: KeymapConfig; + fonts?: FontSet; } const bundledConfig = tenantConfig as unknown as TenantConfigFile; @@ -121,6 +139,8 @@ export function createRuntimeConfigLoader(options: { websocketUrl: integrations.websocket?.url, mutingUrl: integrations.mutingService?.url, }, + // Built-in "sans-serif" renderer default when a tenant declares no fonts. + fonts: merged.fonts ?? { default: "sans-serif", families: [] }, }; }, }; diff --git a/libs/storage/project.json b/libs/storage/project.json new file mode 100644 index 0000000..d77fdd9 --- /dev/null +++ b/libs/storage/project.json @@ -0,0 +1,7 @@ +{ + "name": "storage", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "projectType": "library", + "sourceRoot": "libs/storage/src", + "tags": ["scope:platform", "type:util"] +} diff --git a/libs/storage/src/index.ts b/libs/storage/src/index.ts new file mode 100644 index 0000000..7a70745 --- /dev/null +++ b/libs/storage/src/index.ts @@ -0,0 +1,78 @@ +import appStatePlugin from "@lightningjs/blits/plugins/appstate"; +import storagePlugin from "@lightningjs/blits/plugins/storage"; + +// Reusable persistence built on Blits' storage plugin (localStorage under the +// hood), but usable ANYWHERE — including at bootstrap before Blits.Launch and +// inside components — because the plugin's factory returns a plain API object. +export interface Storage { + get(key: string): T | null; + set(key: string, value: unknown): boolean; + remove(key: string): void; +} + +type Backing = { + get(key: string): T | null; + set(key: string, value: unknown): boolean; + remove(key: string): void; + clear(): void; +}; + +// localStorage-backed when available, else an in-memory Map — so calls never +// throw on a TV with no/limited storage (file://, quota, private mode). +function createBacking(): Backing { + try { + if (typeof globalThis.localStorage !== "undefined") { + return storagePlugin.plugin(); + } + } catch { + // fall through to in-memory + } + const mem = new Map(); + return { + get: (key: string) => (mem.has(key) ? (JSON.parse(mem.get(key) as string) as T) : null), + set: (key: string, value: unknown) => { + mem.set(key, JSON.stringify(value)); + return true; + }, + remove: (key: string) => { + mem.delete(key); + }, + clear: () => mem.clear(), + }; +} + +const backing = createBacking(); + +// Namespaced storage. Keys are prefixed `xtv..` so features never +// collide. Import and reuse anywhere: `const s = createStorage("device")`. +export function createStorage(namespace: string): Storage { + const prefix = `xtv.${namespace}.`; + return { + get(key: string): T | null { + try { + return backing.get(prefix + key); + } catch { + return null; + } + }, + set(key: string, value: unknown): boolean { + try { + return backing.set(prefix + key, value); + } catch { + return false; + } + }, + remove(key: string): void { + try { + backing.remove(prefix + key); + } catch { + // ignore + } + }, + }; +} + +// Blits-shipped plugins re-exported for the composition root to register. +// - appState: global reactive app state (accessed in components as this.$appState) +// - storage: the persistence backing (also used internally by createStorage) +export { appStatePlugin, storagePlugin }; diff --git a/nx.json b/nx.json index 9fa3310..22a79f5 100644 --- a/nx.json +++ b/nx.json @@ -18,5 +18,6 @@ "appsDir": "apps", "libsDir": "libs" }, - "useInferencePlugins": false + "useInferencePlugins": false, + "analytics": false } diff --git a/package-lock.json b/package-lock.json index 47f5f4d..3a288c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,11 +9,11 @@ "version": "0.1.0", "dependencies": { "@lightningjs/blits": "^2.6.0", - "i18next": "^25.2.0", - "zustand": "^5.0.5" + "i18next": "^25.2.0" }, "devDependencies": { "@biomejs/biome": "^1.9.4", + "@lightningjs/msdf-generator": "^1.3.0", "@types/node": "^22.15.0", "nx": "23.0.1", "typescript": "^5.8.3", @@ -197,6 +197,17 @@ "node": ">=14.21.3" } }, + "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/@emnapi/core": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", @@ -680,6 +691,446 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "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/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -699,6 +1150,35 @@ "blits": "bin/index.js" } }, + "node_modules/@lightningjs/msdf-generator": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@lightningjs/msdf-generator/-/msdf-generator-1.3.0.tgz", + "integrity": "sha512-r978kJ2/JyPQ3HjEgaNKqD3JkY0O7zZ6GC4kBhzu6BwkCyHPOxk8rxCWwffMlE1hVpiuuvpa7tvIJPfcjphvog==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "chalk": "^5.3.0", + "fs-extra": "^11.2.0", + "msdf-bmfont-xml": "^2.8.0", + "opentype.js": "1.3.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@lightningjs/msdf-generator/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/@lightningjs/renderer": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@lightningjs/renderer/-/renderer-3.1.0.tgz", @@ -862,6 +1342,51 @@ "win32" ] }, + "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/@rollup/rollup-android-arm-eabi": { "version": "4.60.3", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.3.tgz", @@ -1212,6 +1737,31 @@ "win32" ] }, + "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/@tybys/wasm-util": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", @@ -1392,6 +1942,16 @@ "js-yaml": "bin/js-yaml.js" } }, + "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-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -1428,6 +1988,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", @@ -1452,6 +2026,27 @@ "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/axios": { "version": "1.16.0", "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz", @@ -1507,6 +2102,134 @@ "readable-stream": "^3.4.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.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/boxen/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/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/boxen/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/brace-expansion": { "version": "5.0.6", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", @@ -1569,6 +2292,19 @@ "node": ">= 0.4" } }, + "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/chai": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", @@ -1613,6 +2349,19 @@ "node": ">= 16" } }, + "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": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -1626,6 +2375,19 @@ "node": ">=8" } }, + "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-spinners": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz", @@ -1697,6 +2459,53 @@ "node": ">= 0.8" } }, + "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", @@ -1725,6 +2534,16 @@ "node": ">=6" } }, + "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/defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", @@ -1758,6 +2577,22 @@ "node": ">=0.4.0" } }, + "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/dotenv": { "version": "16.4.7", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", @@ -1950,7 +2785,20 @@ "dev": true, "license": "MIT", "engines": { - "node": ">=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": { @@ -1973,6 +2821,12 @@ "@types/estree": "^1.0.0" } }, + "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/expect-type": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", @@ -2017,6 +2871,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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/flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", @@ -2072,6 +2945,21 @@ "dev": true, "license": "MIT" }, + "node_modules/fs-extra": { + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.6.tgz", + "integrity": "sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==", + "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", @@ -2107,6 +2995,19 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -2146,6 +3047,33 @@ "node": ">= 0.4" } }, + "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/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -2159,6 +3087,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.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "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-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2273,6 +3230,23 @@ "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/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/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -2280,6 +3254,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/is-docker": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", @@ -2306,6 +3290,39 @@ "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.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-1.0.0.tgz", + "integrity": "sha512-K55T22lfpQ63N4KEN57jZUAaAYqYHEe8veb/TycJRk9DdSCLLcovXz/mL6mOnhQaZsQGwPhuFopdQIlqGSEjiQ==", + "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-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", @@ -2316,6 +3333,42 @@ "node": ">=8" } }, + "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/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -2349,6 +3402,62 @@ "dev": true, "license": "ISC" }, + "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/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -2369,6 +3478,48 @@ "dev": true, "license": "MIT" }, + "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/lines-and-columns": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.3.tgz", @@ -2412,6 +3563,26 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "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", @@ -2422,6 +3593,26 @@ "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/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/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -2488,6 +3679,29 @@ "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/nanoid": { "version": "3.3.12", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", @@ -2507,6 +3721,13 @@ "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/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -2700,6 +3921,13 @@ "tslib": "^2.4.0" } }, + "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", @@ -2744,6 +3972,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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/ora": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", @@ -2768,6 +4013,57 @@ "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/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/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -2815,6 +4111,39 @@ "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/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.14", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", @@ -2844,6 +4173,13 @@ "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/proxy-from-env": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", @@ -2854,6 +4190,45 @@ "node": ">=10" } }, + "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/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -2869,6 +4244,35 @@ "node": ">= 6" } }, + "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-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -2969,6 +4373,16 @@ ], "license": "MIT" }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, "node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", @@ -2996,6 +4410,16 @@ "dev": true, "license": "ISC" }, + "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/smol-toml": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.1.tgz", @@ -3009,6 +4433,16 @@ "url": "https://github.com/sponsors/cyyynthia" } }, + "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", @@ -3058,6 +4492,13 @@ "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", @@ -3081,6 +4522,16 @@ "node": ">=4" } }, + "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/strip-literal": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", @@ -3101,6 +4552,40 @@ "dev": true, "license": "MIT" }, + "node_modules/strtok3": { + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.5.tgz", + "integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==", + "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", @@ -3131,6 +4616,13 @@ "node": ">=6" } }, + "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/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -3138,6 +4630,13 @@ "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/tinyexec": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", @@ -3215,6 +4714,25 @@ "node": ">=14.14" } }, + "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/tsconfig-paths": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", @@ -3237,6 +4755,19 @@ "dev": true, "license": "0BSD" }, + "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/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -3251,6 +4782,33 @@ "node": ">=14.17" } }, + "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.5.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -3258,6 +4816,64 @@ "dev": true, "license": "MIT" }, + "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/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -3446,6 +5062,13 @@ "defaults": "^1.0.3" } }, + "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": "3.0.1", "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", @@ -3479,6 +5102,83 @@ "node": ">=8" } }, + "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.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "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": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -3504,6 +5204,57 @@ "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/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -3559,33 +5310,14 @@ "node": ">=12" } }, - "node_modules/zustand": { - "version": "5.0.13", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.13.tgz", - "integrity": "sha512-efI2tVaVQPqtOh114loML/Z80Y4NP3yc+Ff0fYiZJPauNeWZeIp/bRFD7I9bfmCOYBh/PHxlglQ9+wvlwnPikQ==", + "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", - "engines": { - "node": ">=12.20.0" - }, - "peerDependencies": { - "@types/react": ">=18.0.0", - "immer": ">=9.0.6", - "react": ">=18.0.0", - "use-sync-external-store": ">=1.2.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "immer": { - "optional": true - }, - "react": { - "optional": true - }, - "use-sync-external-store": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/colinhacks" } } } diff --git a/package.json b/package.json index d8963c8..3843aed 100644 --- a/package.json +++ b/package.json @@ -24,11 +24,11 @@ }, "dependencies": { "@lightningjs/blits": "^2.6.0", - "i18next": "^25.2.0", - "zustand": "^5.0.5" + "i18next": "^25.2.0" }, "devDependencies": { "@biomejs/biome": "^1.9.4", + "@lightningjs/msdf-generator": "^1.3.0", "@types/node": "^22.15.0", "nx": "23.0.1", "typescript": "^5.8.3", diff --git a/tools/executors/build-tv.mjs b/tools/executors/build-tv.mjs index b9506ba..b28d835 100644 --- a/tools/executors/build-tv.mjs +++ b/tools/executors/build-tv.mjs @@ -1,12 +1,28 @@ import { spawnSync } from "node:child_process"; +import { cpSync, existsSync, rmSync } from "node:fs"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; import { resolveCustomerSlug } from "../packaging/customer-slug.mjs"; +const workspaceRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../.."); const args = parseArgs(process.argv.slice(2)); const app = required(args.app, "app"); // Resolve alias -> slug ONCE here; everything downstream (Vite tenant alias, // packager) receives the resolved slug so only one tenant is ever compiled in. const customer = resolveCustomerSlug(args.customer); const profile = args.profile ?? defaultProfileFor(app); + +// Materialize the active tenant's public/ (fonts, brand assets) into the app's +// public dir. Blits' msdf generator scans /public (hard-coded), so the +// tenant's assets must live there at build time. Cleaned + recopied each build, +// so only the active cruiseline's assets are ever present (isolation). The app +// public dir is gitignored (generated). +const appPublic = resolve(workspaceRoot, "apps", `${app}-tv`, "public"); +const tenantPublic = resolve(workspaceRoot, "customers", customer, "public"); +rmSync(appPublic, { recursive: true, force: true }); +if (existsSync(tenantPublic)) { + cpSync(tenantPublic, appPublic, { recursive: true }); +} const command = args.serve ? "vite" : "vite"; const devHost = process.env.XTV_DEV_HOST ?? "127.0.0.1"; const viteArgs = args.serve diff --git a/tools/vite/xtv-aliases.ts b/tools/vite/xtv-aliases.ts index a356405..0cdc4db 100644 --- a/tools/vite/xtv-aliases.ts +++ b/tools/vite/xtv-aliases.ts @@ -30,5 +30,6 @@ export function createXtvAliases(workspaceRoot: string, customer = "ccl"): Recor "@x-tv/feature-flags": resolve(workspaceRoot, "libs/feature-flags/src/index.ts"), "@x-tv/themes": resolve(workspaceRoot, "libs/themes/src/index.ts"), "@x-tv/muting": resolve(workspaceRoot, "libs/muting/src/index.ts"), + "@x-tv/storage": resolve(workspaceRoot, "libs/storage/src/index.ts"), }; } diff --git a/tsconfig.base.json b/tsconfig.base.json index eff9254..51c52ca 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -34,6 +34,7 @@ "@x-tv/feature-flags": ["libs/feature-flags/src/index.ts"], "@x-tv/themes": ["libs/themes/src/index.ts"], "@x-tv/muting": ["libs/muting/src/index.ts"], + "@x-tv/storage": ["libs/storage/src/index.ts"], "@x-tv/tenant/config": ["customers/ccl/config.json"], "@x-tv/tenant/layout": ["customers/ccl/layouts/home.json"] } From 562738593cf9e37bbb360711765ffe085a37e5d6 Mon Sep 17 00:00:00 2001 From: Shiva Krishna D Date: Wed, 1 Jul 2026 14:42:30 +0200 Subject: [PATCH 3/5] feat(app): Blits Hello World screen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root Blits app renders a HelloWorld component (Tempo Std headline, Open Sans caption echoing customer · platform · profile) — a deployable screen to confirm the .wgt/.ipk pipeline on a real TV. Uses the tenant brand fonts. --- libs/core/src/app.ts | 25 ++++++----------- .../src/components/hello-world.component.ts | 28 +++++++++++++++++++ libs/widgets/src/index.ts | 1 + 3 files changed, 37 insertions(+), 17 deletions(-) create mode 100644 libs/widgets/src/components/hello-world.component.ts diff --git a/libs/core/src/app.ts b/libs/core/src/app.ts index 69eb433..fba9ef3 100644 --- a/libs/core/src/app.ts +++ b/libs/core/src/app.ts @@ -1,33 +1,24 @@ import Blits from "@lightningjs/blits"; -import type { CustomerLayout } from "@x-tv/layout"; import { cclTheme } from "@x-tv/themes"; -import { HeroBanner } from "@x-tv/widgets"; +import { HelloWorld } from "@x-tv/widgets"; import { getBootConfig } from "./boot-config"; -// Root Blits Application. Reads the resolved tenant config once and renders the -// hero from the active layout. This is the foundation: a single known widget. -// The fully config-driven, feature-gated, multi-widget Blits layout engine -// (porting @x-tv/layout to resolve widgets by type) is the next step. +// Root Blits Application. For now it renders a Hello World screen so a build +// (.wgt / .ipk / .apk) can be signed and deployed to a real TV to confirm the +// end-to-end pipeline. The caption echoes the baked-in tenant + platform. +// Next: a config-driven, feature-gated multi-widget layout engine. export default Blits.Application({ - components: { HeroBanner }, + components: { HelloWorld }, template: ` - + `, state() { const config = getBootConfig(); - const hero = findHeroProps(config.layout); return { - title: hero.title, - subtitle: hero.subtitle, background: cclTheme.colors.background, + caption: `${config.customer} · ${config.platform.platform} · ${config.platform.id}`, }; }, }); - -function findHeroProps(layout: CustomerLayout): { title: string; subtitle: string } { - const node = layout.root.children?.find((child) => child.widget === "hero-banner"); - const props = (node?.props ?? {}) as { title?: string; subtitle?: string }; - return { title: props.title ?? "", subtitle: props.subtitle ?? "" }; -} diff --git a/libs/widgets/src/components/hello-world.component.ts b/libs/widgets/src/components/hello-world.component.ts new file mode 100644 index 0000000..9858c32 --- /dev/null +++ b/libs/widgets/src/components/hello-world.component.ts @@ -0,0 +1,28 @@ +import Blits from "@lightningjs/blits"; + +// Minimal Hello World screen. Uses the renderer's built-in sans-serif default +// font (no font asset needed). The caption shows tenant + platform so an +// on-device deploy confirms the right cruiseline/profile was built in. +export default Blits.Component("HelloWorld", { + template: ` + + + + + `, + props: ["background", "caption"], +}); diff --git a/libs/widgets/src/index.ts b/libs/widgets/src/index.ts index 35e60b8..91f75b7 100644 --- a/libs/widgets/src/index.ts +++ b/libs/widgets/src/index.ts @@ -2,6 +2,7 @@ import { createHeroBannerElement } from "./components/hero-banner"; // Blits (canvas) components — the production render path used by the Blits app. export { default as HeroBanner } from "./components/hero-banner.component"; +export { default as HelloWorld } from "./components/hello-world.component"; // NOTE: the DOM WidgetDefinition/registry/layout path below is retained (still // consumed by @x-tv/layout + @x-tv/widget-registry types) but is NOT the render From 1ebdf4173aaa33d632462150166a92db28339e1f Mon Sep 17 00:00:00 2001 From: Shiva Krishna D Date: Wed, 1 Jul 2026 14:44:07 +0200 Subject: [PATCH 4/5] feat(app): drive Hello World caption from Blits appState MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HelloWorld now reads global reactive state (this.$appState — customer, platform, locale, seeded in core) via a computed, instead of a passed prop. Demonstrates the appState wiring end to end. --- libs/core/src/app.ts | 12 ++++------- .../src/components/hello-world.component.ts | 21 +++++++++++++++---- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/libs/core/src/app.ts b/libs/core/src/app.ts index fba9ef3..f74e7d1 100644 --- a/libs/core/src/app.ts +++ b/libs/core/src/app.ts @@ -1,24 +1,20 @@ import Blits from "@lightningjs/blits"; import { cclTheme } from "@x-tv/themes"; import { HelloWorld } from "@x-tv/widgets"; -import { getBootConfig } from "./boot-config"; -// Root Blits Application. For now it renders a Hello World screen so a build -// (.wgt / .ipk / .apk) can be signed and deployed to a real TV to confirm the -// end-to-end pipeline. The caption echoes the baked-in tenant + platform. -// Next: a config-driven, feature-gated multi-widget layout engine. +// Root Blits Application. Renders the Hello World screen; its caption reads +// global reactive state (this.$appState, seeded in core). Next: a config-driven, +// feature-gated multi-widget layout engine. export default Blits.Application({ components: { HelloWorld }, template: ` - + `, state() { - const config = getBootConfig(); return { background: cclTheme.colors.background, - caption: `${config.customer} · ${config.platform.platform} · ${config.platform.id}`, }; }, }); diff --git a/libs/widgets/src/components/hello-world.component.ts b/libs/widgets/src/components/hello-world.component.ts index 9858c32..9b1c5fa 100644 --- a/libs/widgets/src/components/hello-world.component.ts +++ b/libs/widgets/src/components/hello-world.component.ts @@ -1,8 +1,15 @@ import Blits from "@lightningjs/blits"; -// Minimal Hello World screen. Uses the renderer's built-in sans-serif default -// font (no font asset needed). The caption shows tenant + platform so an -// on-device deploy confirms the right cruiseline/profile was built in. +// Shape of the global reactive state seeded in core (Blits appState plugin). +interface AppState { + customer: string; + platform: string; + locale: string; +} + +// Minimal Hello World screen. The headline uses the tenant brand font; the +// caption is derived from global reactive state (this.$appState) rather than a +// passed prop — so an on-device deploy confirms the baked-in tenant/platform. export default Blits.Component("HelloWorld", { template: ` @@ -24,5 +31,11 @@ export default Blits.Component("HelloWorld", { /> `, - props: ["background", "caption"], + props: ["background"], + computed: { + caption(): string { + const app = (this as unknown as { $appState: AppState }).$appState; + return `${app.customer} · ${app.platform} · ${app.locale}`; + }, + }, }); From aade352189e3fc2f201701b8a0921b51b0090dd2 Mon Sep 17 00:00:00 2001 From: Shiva Krishna D Date: Wed, 1 Jul 2026 14:45:11 +0200 Subject: [PATCH 5/5] docs: add state-and-storage.md (state layers + persistence) --- CLAUDE.md | 3 +- README.md | 7 ++++ docs/state-and-storage.md | 87 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 docs/state-and-storage.md diff --git a/CLAUDE.md b/CLAUDE.md index 5a6cfb2..aef5692 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -39,7 +39,7 @@ A build is one **cell**: `nx build samsung --customer=ccl --profile=tizen6`. | `platforms//` | Packaging inputs: `profiles/*.json` (capabilities) + `templates/` (container manifests). **Not app code.** | | `customers//` | Per-cruiseline content: `config.json` (sectioned), `layouts/`, `i18n/`, `assets/`. | | `tools/` | Build tooling: `executors/build-tv.mjs`, `packaging/` (package-tv, signing, customer-slug), `vite/xtv-aliases.ts`. | -| `docs/` | `signing.md` (manual signing), `config-hot-reload.md` (live config, no reboot), `tv-platform-reference.md` (keycodes + device APIs), `DEV-PLAYBOOK.md` (skills/workflow). | +| `docs/` | `signing.md` (manual signing), `config-hot-reload.md` (live config, no reboot), `state-and-storage.md` (state layers + persistence), `tv-platform-reference.md` (keycodes + device APIs), `DEV-PLAYBOOK.md` (skills/workflow). | | `signing/` | `signing.example.json` (template). Real creds in gitignored `.signing.local.json`. | ### Key libs @@ -116,6 +116,7 @@ Sign a build by exporting `XTV_CCL_*` env (see `docs/signing.md`) before `build` (guest prefs, entitlements, resume points) lives on the head-end** via service-gateway — localStorage is best-effort only (TV quota; wiped on update/uninstall). zustand was removed (unused; Blits covers reactive state). + Full model: `docs/state-and-storage.md`. ## How to… diff --git a/README.md b/README.md index 40aaa0a..74d3b80 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,13 @@ aliases `@x-tv/tenant/*` to that one tenant's files. A CCL bundle contains **zer tokens from any other brand. **Never** reintroduce `import.meta.glob("customers/*")` or a tenant alias map in shipped `libs/` — that leaks rival brands into an artifact. +## State & storage + +Component state (Blits `state()`), global reactive state (Blits `appState` → +`this.$appState`), device-local persistence (`@x-tv/storage` — namespaced +localStorage with in-memory fallback), and durable state (head-end via +service-gateway). Details + decision guide: **[docs/state-and-storage.md](docs/state-and-storage.md)**. + ## TV diagnostics The runtime mounts a diagnostics overlay (platform, profile, customer, device diff --git a/docs/state-and-storage.md b/docs/state-and-storage.md new file mode 100644 index 0000000..c102fce --- /dev/null +++ b/docs/state-and-storage.md @@ -0,0 +1,87 @@ +# State & Storage + +How the app holds state in memory and persists data. Four layers, each with a +clear job — don't reach for a heavier one than the data needs. + +## The layers + +| Layer | Use for | Where | Persists? | +| ---------------------------- | ------------------------------------------------------------------------ | ------------------------------------------ | ----------------------------------- | +| **Component state** | Local UI state for one screen/widget | Blits `state()` | No (in-memory, per-component) | +| **Global reactive state** | Cross-screen app state that UI reacts to | Blits `appState` plugin → `this.$appState` | No (in-memory, app lifetime) | +| **Boot config snapshot** | The full resolved `RuntimeConfig` (layout, services, features, platform) | `@x-tv/core` `getBootConfig()` | No (set once at launch) | +| **Device-local persistence** | Small device-scoped values (deviceId, last focus, volume) | `@x-tv/storage` `createStorage(ns)` | Yes — localStorage, **best-effort** | +| **Durable / authoritative** | Guest prefs, entitlements, resume points, messages | **Head-end** via `@x-tv/service-gateway` | Yes — server of record | + +## Global reactive state — Blits `appState` + +Registered once in `libs/core` before launch, seeded from config: + +```ts +Blits.Plugin(appStatePlugin, { + customer: runtimeConfig.customer, + platform: runtimeConfig.platform.platform, + locale: runtimeConfig.locale, +}); +``` + +Read/write in any component via `this.$appState`: + +```ts +computed: { + caption() { + const app = this.$appState; // { customer, platform, locale, ... } + return `${app.customer} · ${app.platform}`; + }, +} +``` + +Use it for state the UI must react to across screens. It is **not** persisted — +it resets on reload/relaunch (which is fine; the boot config re-seeds it). + +## Persistence — `@x-tv/storage` + +A reusable, namespaced wrapper over Blits' `storage` plugin (localStorage), with +an **in-memory fallback** so calls never throw on a TV with no/limited storage. +Safe to import **anywhere** — at bootstrap, in libs, or inside components. + +```ts +import { createStorage } from "@x-tv/storage"; + +const store = createStorage("device"); // keys are prefixed xtv.device. +store.set("id", "abc-123"); +const id = store.get("id"); // "abc-123" | null +store.remove("id"); +``` + +- **Namespaced** `xtv..` so features never collide. +- **Fallback**: if `localStorage` is unavailable (file://, private mode, quota), + it transparently uses an in-memory Map for the session. +- Backed by the Blits `storage` plugin — one dependency, one convention. + +## TV reality — treat localStorage as best-effort + +localStorage works on Tizen/webOS WebViews (and under `file://` in a bundled +widget, scoped to that origin), **but**: + +- quota is small, +- it can be **wiped on app update, uninstall, or memory pressure**. + +So `@x-tv/storage` is only for **device-local, non-critical** values. Anything +that must survive — guest preferences, entitlements, watch history, room +messaging — is **authoritative on the head-end**, fetched/written through +`@x-tv/service-gateway`, keyed by device/cabin. The TV is a cache, not the record. + +## Decision guide + +- Screen-only toggle/scroll position → **component `state()`**. +- Something other screens read and react to → **`this.$appState`**. +- Need the full config (layout, services, features) in code → **`getBootConfig()`**. +- Remember a small value on this device between sessions → **`createStorage()`**. +- Must never be lost / shared across devices → **head-end via service-gateway**. + +## Notes + +- Blits' own reactivity + `appState` cover global state. +- `appState` seeds the reactive subset; `getBootConfig()` remains the full + static snapshot (layout, services, keymap, fonts) for code that needs it.