diff --git a/package-lock.json b/package-lock.json index e9c588fd..f5de2e37 100644 --- a/package-lock.json +++ b/package-lock.json @@ -881,16 +881,21 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", - "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", - "dev": true, + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.3.tgz", + "integrity": "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==", "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.10.0" + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" } }, "node_modules/@noble/hashes": { @@ -2270,24 +2275,6 @@ "node": ">=14.0.0" } }, - "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.2.tgz", - "integrity": "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==", - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" - } - }, "node_modules/@rolldown/binding-win32-arm64-msvc": { "version": "1.0.0-rc.12", "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.12.tgz", @@ -2719,64 +2706,6 @@ "node": ">=14.0.0" } }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { - "version": "1.8.1", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.1.0", - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { - "version": "1.8.1", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.1", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1", - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { - "version": "2.8.1", - "inBundle": true, - "license": "0BSD", - "optional": true - }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.2.tgz", @@ -2939,14 +2868,14 @@ "license": "MIT" }, "node_modules/@typescript-eslint/project-service": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.2.tgz", - "integrity": "sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.0.tgz", + "integrity": "sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.57.2", - "@typescript-eslint/types": "^8.57.2", + "@typescript-eslint/tsconfig-utils": "^8.58.0", + "@typescript-eslint/types": "^8.58.0", "debug": "^4.4.3" }, "engines": { @@ -2957,18 +2886,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.2.tgz", - "integrity": "sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.0.tgz", + "integrity": "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.57.2", - "@typescript-eslint/visitor-keys": "8.57.2" + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2979,9 +2908,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.2.tgz", - "integrity": "sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.0.tgz", + "integrity": "sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==", "dev": true, "license": "MIT", "engines": { @@ -2992,21 +2921,21 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.2.tgz", - "integrity": "sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.0.tgz", + "integrity": "sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.57.2", - "@typescript-eslint/typescript-estree": "8.57.2", - "@typescript-eslint/utils": "8.57.2", + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0", + "@typescript-eslint/utils": "8.58.0", "debug": "^4.4.3", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3017,13 +2946,13 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.2.tgz", - "integrity": "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.0.tgz", + "integrity": "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==", "dev": true, "license": "MIT", "engines": { @@ -3035,21 +2964,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.2.tgz", - "integrity": "sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.0.tgz", + "integrity": "sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.57.2", - "@typescript-eslint/tsconfig-utils": "8.57.2", - "@typescript-eslint/types": "8.57.2", - "@typescript-eslint/visitor-keys": "8.57.2", + "@typescript-eslint/project-service": "8.58.0", + "@typescript-eslint/tsconfig-utils": "8.58.0", + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3059,20 +2988,20 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/utils": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.2.tgz", - "integrity": "sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.0.tgz", + "integrity": "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.57.2", - "@typescript-eslint/types": "8.57.2", - "@typescript-eslint/typescript-estree": "8.57.2" + "@typescript-eslint/scope-manager": "8.58.0", + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3083,17 +3012,17 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.2.tgz", - "integrity": "sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.0.tgz", + "integrity": "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/types": "8.58.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -3130,6 +3059,7 @@ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -3143,6 +3073,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -3169,6 +3100,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -3182,6 +3114,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -3195,6 +3128,7 @@ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -3208,6 +3142,7 @@ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -3221,6 +3156,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -3234,6 +3170,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -3247,6 +3184,7 @@ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -3260,6 +3198,7 @@ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -3273,6 +3212,7 @@ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -3286,6 +3226,7 @@ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -3299,6 +3240,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -3312,6 +3254,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -3325,6 +3268,7 @@ "wasm32" ], "dev": true, + "license": "MIT", "optional": true, "dependencies": { "@napi-rs/wasm-runtime": "^0.2.11" @@ -3333,6 +3277,19 @@ "node": ">=14.0.0" } }, + "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { "version": "1.9.2", "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.9.2.tgz", @@ -3341,6 +3298,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -3354,6 +3312,7 @@ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -3367,6 +3326,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -3617,9 +3577,9 @@ "license": "ISC" }, "node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4648,9 +4608,9 @@ } }, "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true, "license": "ISC" }, @@ -5873,9 +5833,9 @@ } }, "node_modules/lru-cache": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", - "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -6781,13 +6741,13 @@ } }, "node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^5.0.5" }, "engines": { "node": "18 || 20 || >=22" @@ -7067,9 +7027,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "license": "MIT", "engines": { "node": ">=8.6" @@ -7339,25 +7299,25 @@ } }, "node_modules/react": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", - "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", + "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", - "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", + "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", "license": "MIT", "peer": true, "dependencies": { - "scheduler": "^0.26.0" + "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.1.0" + "react": "^19.2.5" } }, "node_modules/react-markdown": { @@ -7779,9 +7739,9 @@ } }, "node_modules/scheduler": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", - "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT", "peer": true }, @@ -8213,9 +8173,9 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", "engines": { "node": ">=12" @@ -8274,9 +8234,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", - "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", "engines": { @@ -8353,9 +8313,9 @@ } }, "node_modules/undici": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz", - "integrity": "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==", + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz", + "integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==", "license": "MIT", "engines": { "node": ">=18.17" diff --git a/src/generators/web/index.mjs b/src/generators/web/index.mjs index 2bb92841..ca0bb6a9 100644 --- a/src/generators/web/index.mjs +++ b/src/generators/web/index.mjs @@ -34,6 +34,7 @@ export default createLazyGenerator({ useAbsoluteURLs: false, editURL: `${GITHUB_EDIT_URL}/doc/api{path}.md`, pageURL: '{baseURL}/latest-{version}/api{path}.html', + remoteConfig: 'https://nodejs.org/site.json', imports: { '#theme/Logo': '@node-core/ui-components/Common/NodejsLogo', '#theme/Navigation': join(import.meta.dirname, './ui/components/NavBar'), diff --git a/src/generators/web/ui/components/AnnouncementBanner/AnnouncementBanner.jsx b/src/generators/web/ui/components/AnnouncementBanner/AnnouncementBanner.jsx new file mode 100644 index 00000000..e6be507a --- /dev/null +++ b/src/generators/web/ui/components/AnnouncementBanner/AnnouncementBanner.jsx @@ -0,0 +1,26 @@ +import { ArrowUpRightIcon } from '@heroicons/react/24/outline'; +import Banner from '@node-core/ui-components/Common/Banner'; + +import styles from './index.module.css'; + +/** + * @param {import('./types.d.ts').AnnouncementBannerProps} props + */ +const AnnouncementBanner = ({ banners }) => ( +
+ {banners.map(banner => ( + + {banner.link ? ( + + {banner.text} + + ) : ( + banner.text + )} + {banner.link && } + + ))} +
+); + +export default AnnouncementBanner; diff --git a/src/generators/web/ui/components/AnnouncementBanner/__tests__/loadBanners.test.mjs b/src/generators/web/ui/components/AnnouncementBanner/__tests__/loadBanners.test.mjs new file mode 100644 index 00000000..cb0560c4 --- /dev/null +++ b/src/generators/web/ui/components/AnnouncementBanner/__tests__/loadBanners.test.mjs @@ -0,0 +1,174 @@ +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; + +import { loadBanners } from '../loadBanners.mjs'; + +const PAST = new Date(Date.now() - 86_400_000).toISOString(); // yesterday +const FUTURE = new Date(Date.now() + 86_400_000).toISOString(); // tomorrow + +const makeResponse = (banners, ok = true) => ({ + ok, + json: async () => ({ websiteBanners: banners }), +}); + +describe('loadBanners', () => { + describe('fetch behavior', () => { + it('fetches from the given URL', async t => { + t.mock.method(global, 'fetch', () => Promise.resolve(makeResponse({}))); + + await loadBanners('https://example.com/site.json', null); + + assert.equal(global.fetch.mock.calls.length, 1); + assert.equal( + global.fetch.mock.calls[0].arguments[0], + 'https://example.com/site.json' + ); + }); + + it('returns an empty array on non-ok response', async t => { + t.mock.method(global, 'fetch', () => + Promise.resolve(makeResponse({}, false)) + ); + + const result = await loadBanners('https://example.com/site.json', null); + + assert.deepEqual(result, []); + }); + + it('handles fetch errors silently', async t => { + t.mock.method(global, 'fetch', () => + Promise.reject(new Error('Network error')) + ); + + const result = await loadBanners('https://example.com/site.json', null); + + assert.deepEqual(result, []); + }); + + it('returns an empty array when remoteConfig is absent', async t => { + t.mock.method(global, 'fetch', () => Promise.resolve(makeResponse({}))); + + const result = await loadBanners(undefined, null); + + assert.deepEqual(result, []); + assert.equal(global.fetch.mock.calls.length, 0); + }); + }); + + describe('banner selection', () => { + it('returns the active global (index) banner', async t => { + const banner = { text: 'Global banner', type: 'warning' }; + t.mock.method(global, 'fetch', () => + Promise.resolve(makeResponse({ index: banner })) + ); + + const result = await loadBanners('https://example.com/site.json', null); + + assert.deepEqual(result, [banner]); + }); + + it('returns the active version-specific banner', async t => { + const banner = { text: 'v20 banner', type: 'warning' }; + t.mock.method(global, 'fetch', () => + Promise.resolve(makeResponse({ v20: banner })) + ); + + const result = await loadBanners('https://example.com/site.json', 20); + + assert.deepEqual(result, [banner]); + }); + + it('returns both global and version banners when both are active', async t => { + const globalBanner = { text: 'Global banner', type: 'warning' }; + const versionBanner = { text: 'v20 banner', type: 'error' }; + t.mock.method(global, 'fetch', () => + Promise.resolve( + makeResponse({ index: globalBanner, v20: versionBanner }) + ) + ); + + const result = await loadBanners('https://example.com/site.json', 20); + + assert.deepEqual(result, [globalBanner, versionBanner]); + }); + + it('returns global banner first, version banner second', async t => { + const globalBanner = { text: 'Global', type: 'warning' }; + const versionBanner = { text: 'v22', type: 'error' }; + t.mock.method(global, 'fetch', () => + Promise.resolve( + makeResponse({ index: globalBanner, v22: versionBanner }) + ) + ); + + const result = await loadBanners('https://example.com/site.json', 22); + + assert.equal(result[0], globalBanner); + assert.equal(result[1], versionBanner); + }); + + it('does not include the version banner when versionMajor is null', async t => { + const globalBanner = { text: 'Global banner', type: 'warning' }; + const versionBanner = { text: 'v20 banner', type: 'error' }; + t.mock.method(global, 'fetch', () => + Promise.resolve( + makeResponse({ index: globalBanner, v20: versionBanner }) + ) + ); + + const result = await loadBanners('https://example.com/site.json', null); + + assert.deepEqual(result, [globalBanner]); + }); + + it('returns an empty array when websiteBanners is absent', async t => { + t.mock.method(global, 'fetch', () => + Promise.resolve({ ok: true, json: async () => ({}) }) + ); + + const result = await loadBanners('https://example.com/site.json', null); + + assert.deepEqual(result, []); + }); + }); + + describe('date filtering', () => { + it('excludes a banner whose endDate has passed', async t => { + const banner = { text: 'Expired', type: 'warning', endDate: PAST }; + t.mock.method(global, 'fetch', () => + Promise.resolve(makeResponse({ index: banner })) + ); + + const result = await loadBanners('https://example.com/site.json', null); + + assert.deepEqual(result, []); + }); + + it('excludes a banner whose startDate is in the future', async t => { + const banner = { text: 'Upcoming', type: 'warning', startDate: FUTURE }; + t.mock.method(global, 'fetch', () => + Promise.resolve(makeResponse({ index: banner })) + ); + + const result = await loadBanners('https://example.com/site.json', null); + + assert.deepEqual(result, []); + }); + + it('includes a banner within its active date range', async t => { + const banner = { + text: 'Active', + type: 'warning', + startDate: PAST, + endDate: FUTURE, + }; + t.mock.method(global, 'fetch', () => + Promise.resolve(makeResponse({ index: banner })) + ); + + const result = await loadBanners('https://example.com/site.json', null); + + assert.deepEqual(result, [banner]); + }); + }); +}); diff --git a/src/generators/web/ui/components/AnnouncementBanner/index.jsx b/src/generators/web/ui/components/AnnouncementBanner/index.jsx new file mode 100644 index 00000000..932037e2 --- /dev/null +++ b/src/generators/web/ui/components/AnnouncementBanner/index.jsx @@ -0,0 +1,31 @@ +import { lazy, Suspense } from 'preact/compat'; + +import AnnouncementBanner from './AnnouncementBanner.jsx'; +import { loadBanners } from './loadBanners.mjs'; + +import { remoteConfig, versionMajor } from '#theme/config'; + +// TODO: Revisit SERVER global usage. +const LazyBanners = SERVER + ? null + : lazy(async () => { + const active = await loadBanners(remoteConfig, versionMajor); + + if (!active.length) { + return { default: () => null }; + } + + return { default: () => }; + }); + +const RemoteLoadableBanner = SERVER + ? () =>
+ : () => ( +
+ + + +
+ ); + +export default RemoteLoadableBanner; diff --git a/src/generators/web/ui/components/AnnouncementBanner/index.module.css b/src/generators/web/ui/components/AnnouncementBanner/index.module.css new file mode 100644 index 00000000..d8a38d10 --- /dev/null +++ b/src/generators/web/ui/components/AnnouncementBanner/index.module.css @@ -0,0 +1,14 @@ +.banners { + animation: slideDown 300ms ease-out 400ms backwards; +} + +@keyframes slideDown { + from { + opacity: 0; + transform: translateY(-0.5rem); + } + to { + opacity: 1; + transform: translateY(0); + } +} diff --git a/src/generators/web/ui/components/AnnouncementBanner/loadBanners.mjs b/src/generators/web/ui/components/AnnouncementBanner/loadBanners.mjs new file mode 100644 index 00000000..818cab8f --- /dev/null +++ b/src/generators/web/ui/components/AnnouncementBanner/loadBanners.mjs @@ -0,0 +1,33 @@ +import { isBannerActive } from '../../utils/banner.mjs'; + +/** + * Fetches and returns active banners for the given version. + * Returns an empty array when remoteConfig is absent, the response is not ok, + * or on any fetch/parse failure. + * + * @param {string | undefined} remoteConfig + * @param {number | null} versionMajor + * @returns {Promise>} + */ +export const loadBanners = async (remoteConfig, versionMajor) => { + if (!remoteConfig) { + return []; + } + + try { + const res = await fetch(remoteConfig); + + /** @type {import('./types.d.ts').RemoteConfig} */ + const { websiteBanners = {} } = await res.json(); + + const keys = ['index', versionMajor != null && `v${versionMajor}`].filter( + Boolean + ); + + return keys + .map(key => websiteBanners[key]) + .filter(banner => banner && isBannerActive(banner)); + } catch { + return []; + } +}; diff --git a/src/generators/web/ui/components/AnnouncementBanner/types.d.ts b/src/generators/web/ui/components/AnnouncementBanner/types.d.ts new file mode 100644 index 00000000..5423b6df --- /dev/null +++ b/src/generators/web/ui/components/AnnouncementBanner/types.d.ts @@ -0,0 +1,22 @@ +import type { BannerProps } from '@node-core/ui-components/Common/Banner'; + +export type BannerEntry = { + startDate?: string; + endDate?: string; + text: string; + link?: string; + type?: BannerProps['type']; +}; + +export type RemoteConfig = { + websiteBanners?: Record; +}; + +export type AnnouncementBannerProps = { + banners: BannerEntry[]; +}; + +export type RemoteLoadableBannerProps = { + remoteConfig: string; + versionMajor: number | null; +}; diff --git a/src/generators/web/ui/components/Layout/index.jsx b/src/generators/web/ui/components/Layout/index.jsx index 9978fc0c..b4416363 100644 --- a/src/generators/web/ui/components/Layout/index.jsx +++ b/src/generators/web/ui/components/Layout/index.jsx @@ -1,6 +1,8 @@ import TableOfContents from '@node-core/ui-components/Common/TableOfContents'; import Article from '@node-core/ui-components/Containers/Article'; +import RemoteLoadableBanner from '../AnnouncementBanner'; + import Footer from '#theme/Footer'; import MetaBar from '#theme/Metabar'; import NavBar from '#theme/Navigation'; @@ -17,6 +19,7 @@ import SideBar from '#theme/Sidebar'; */ export default ({ metadata, headings, readingTime, children }) => ( <> +
diff --git a/src/generators/web/ui/types.d.ts b/src/generators/web/ui/types.d.ts index 081061db..e200135a 100644 --- a/src/generators/web/ui/types.d.ts +++ b/src/generators/web/ui/types.d.ts @@ -24,6 +24,7 @@ declare module '#theme/config' { // From config generation export const version: string; + export const versionMajor: number; export const versions: Array<{ url: string; label: string; @@ -32,6 +33,7 @@ declare module '#theme/config' { export const editURL: string; export const pages: Array<[string, string]>; export const languageDisplayNameMap: Map; + export const remoteConfig: string; } // Omit Primitives from Metadata diff --git a/src/generators/web/ui/utils/__tests__/banner.test.mjs b/src/generators/web/ui/utils/__tests__/banner.test.mjs new file mode 100644 index 00000000..262d2c9d --- /dev/null +++ b/src/generators/web/ui/utils/__tests__/banner.test.mjs @@ -0,0 +1,63 @@ +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; + +import { isBannerActive } from '../banner.mjs'; + +const PAST = new Date(Date.now() - 86_400_000).toISOString(); // yesterday +const FUTURE = new Date(Date.now() + 86_400_000).toISOString(); // tomorrow + +const banner = (overrides = {}) => ({ + text: 'Test banner', + ...overrides, +}); + +describe('isBannerActive', () => { + describe('no startDate, no endDate', () => { + it('is always active', () => { + assert.equal(isBannerActive(banner()), true); + }); + }); + + describe('startDate only', () => { + it('is active when startDate is in the past', () => { + assert.equal(isBannerActive(banner({ startDate: PAST })), true); + }); + + it('is not active when startDate is in the future', () => { + assert.equal(isBannerActive(banner({ startDate: FUTURE })), false); + }); + }); + + describe('endDate only', () => { + it('is active when endDate is in the future', () => { + assert.equal(isBannerActive(banner({ endDate: FUTURE })), true); + }); + + it('is not active when endDate is in the past', () => { + assert.equal(isBannerActive(banner({ endDate: PAST })), false); + }); + }); + + describe('startDate and endDate', () => { + it('is active when now is within the range', () => { + assert.equal( + isBannerActive(banner({ startDate: PAST, endDate: FUTURE })), + true + ); + }); + + it('is not active when now is before the range', () => { + assert.equal( + isBannerActive(banner({ startDate: FUTURE, endDate: FUTURE })), + false + ); + }); + + it('is not active when now is after the range', () => { + assert.equal( + isBannerActive(banner({ startDate: PAST, endDate: PAST })), + false + ); + }); + }); +}); diff --git a/src/generators/web/ui/utils/banner.mjs b/src/generators/web/ui/utils/banner.mjs new file mode 100644 index 00000000..4afa820a --- /dev/null +++ b/src/generators/web/ui/utils/banner.mjs @@ -0,0 +1,15 @@ +/** + * Checks whether a banner should be displayed based on its date range. + * Both `startDate` and `endDate` are optional; if omitted the banner is + * considered open-ended in that direction. + * + * @param {import('../components/AnnouncementBanner/types').BannerEntry} banner + * @returns {boolean} + */ +export const isBannerActive = ({ startDate, endDate }) => { + const now = Date.now(); + return ( + (!startDate || now >= new Date(startDate)) && + (!endDate || now <= new Date(endDate)) + ); +}; diff --git a/src/generators/web/utils/config.mjs b/src/generators/web/utils/config.mjs index 00d7ae15..414e6f8f 100644 --- a/src/generators/web/utils/config.mjs +++ b/src/generators/web/utils/config.mjs @@ -86,6 +86,7 @@ export default function createConfigSource(input) { ['changelog', 'index', 'imports', 'virtualImports'] ), version, + versionMajor: configVersion.major ?? null, versions: buildVersionEntries(config.changelog, pageURL), editURL, pages: buildPageList(input), diff --git a/src/generators/web/utils/generate.mjs b/src/generators/web/utils/generate.mjs index ee10ec3d..3c4069f6 100644 --- a/src/generators/web/utils/generate.mjs +++ b/src/generators/web/utils/generate.mjs @@ -80,11 +80,15 @@ export default () => { // Import all JSX components ...baseImports, - // Import Preact's SSR render function (named import) - createImportDeclaration('render', 'preact-render-to-string', false), + // Import Preact's async SSR render function (named import) + createImportDeclaration( + 'renderToStringAsync', + 'preact-render-to-string', + false + ), // Render component to HTML string and return it - `return render(${componentCode});`, + `return renderToStringAsync(${componentCode});`, ].join('\n'); }; diff --git a/src/generators/web/utils/processing.mjs b/src/generators/web/utils/processing.mjs index e4a66f23..184163ea 100644 --- a/src/generators/web/utils/processing.mjs +++ b/src/generators/web/utils/processing.mjs @@ -84,7 +84,7 @@ async function executeServerCode(serverCodeMap, requireFn, virtualImports) { // Execute each bundled entry and collect dehydrated HTML results for (const chunk of entryChunks) { const executedFunction = new Function('require', chunk.code); - pages.set(chunk.fileName, executedFunction(enhancedRequire)); + pages.set(chunk.fileName, await executedFunction(enhancedRequire)); } return { pages, css };