diff --git a/application/client/package.json b/application/client/package.json index 9f8e80a6a8..13a16ca567 100644 --- a/application/client/package.json +++ b/application/client/package.json @@ -5,6 +5,7 @@ "license": "MPL-2.0", "author": "CyberAgent, Inc.", "scripts": { + "analyze": "NODE_ENV=development ANALYZE=true webpack", "build": "NODE_ENV=development webpack", "typecheck": "tsc" }, @@ -85,6 +86,7 @@ "react-markdown": "10.1.0", "typescript": "5.9.3", "webpack": "5.102.1", + "webpack-bundle-analyzer": "4.10.2", "webpack-cli": "6.0.1", "webpack-dev-server": "5.2.2" }, diff --git a/application/client/src/auth/validation.ts b/application/client/src/auth/validation.ts index 2a83bbfb15..12425260af 100644 --- a/application/client/src/auth/validation.ts +++ b/application/client/src/auth/validation.ts @@ -13,7 +13,7 @@ export const validate = (values: AuthFormData): FormErrors => { errors.name = "名前を入力してください"; } - if (/^(?:[^\P{Letter}&&\P{Number}]*){16,}$/v.test(normalizedPassword)) { + if (/^[\p{Letter}\p{Number}]+$/v.test(normalizedPassword)) { errors.password = "パスワードには記号を含める必要があります"; } if (normalizedPassword.length === 0) { diff --git a/application/client/src/components/foundation/CoveredImage.tsx b/application/client/src/components/foundation/CoveredImage.tsx index 8ad9cc1f7d..445438df13 100644 --- a/application/client/src/components/foundation/CoveredImage.tsx +++ b/application/client/src/components/foundation/CoveredImage.tsx @@ -1,70 +1,29 @@ -import classNames from "classnames"; -import sizeOf from "image-size"; -import { load, ImageIFD } from "piexifjs"; -import { MouseEvent, RefCallback, useCallback, useId, useMemo, useState } from "react"; +import { MouseEvent, useCallback, useId } from "react"; import { Button } from "@web-speed-hackathon-2026/client/src/components/foundation/Button"; import { Modal } from "@web-speed-hackathon-2026/client/src/components/modal/Modal"; -import { useFetch } from "@web-speed-hackathon-2026/client/src/hooks/use_fetch"; -import { fetchBinary } from "@web-speed-hackathon-2026/client/src/utils/fetchers"; interface Props { + alt: string; src: string; } /** * アスペクト比を維持したまま、要素のコンテンツボックス全体を埋めるように画像を拡大縮小します */ -export const CoveredImage = ({ src }: Props) => { +export const CoveredImage = ({ alt, src }: Props) => { const dialogId = useId(); // ダイアログの背景をクリックしたときに投稿詳細ページに遷移しないようにする const handleDialogClick = useCallback((ev: MouseEvent) => { ev.stopPropagation(); }, []); - const { data, isLoading } = useFetch(src, fetchBinary); - - const imageSize = useMemo(() => { - return data != null ? sizeOf(Buffer.from(data)) : { height: 0, width: 0 }; - }, [data]); - - const alt = useMemo(() => { - const exif = data != null ? load(Buffer.from(data).toString("binary")) : null; - const raw = exif?.["0th"]?.[ImageIFD.ImageDescription]; - return raw != null ? new TextDecoder().decode(Buffer.from(raw, "binary")) : ""; - }, [data]); - - const blobUrl = useMemo(() => { - return data != null ? URL.createObjectURL(new Blob([data])) : null; - }, [data]); - - const [containerSize, setContainerSize] = useState({ height: 0, width: 0 }); - const callbackRef = useCallback>((el) => { - setContainerSize({ - height: el?.clientHeight ?? 0, - width: el?.clientWidth ?? 0, - }); - }, []); - - if (isLoading || data === null || blobUrl === null) { - return null; - } - - const containerRatio = containerSize.height / containerSize.width; - const imageRatio = imageSize?.height / imageSize?.width; - return ( -
+
{alt} imageRatio, - "w-full h-auto": containerRatio <= imageRatio, - }, - )} - src={blobUrl} + className="h-full w-full object-cover" + src={src} />
); })} diff --git a/application/client/src/containers/AuthModalContainer.tsx b/application/client/src/containers/AuthModalContainer.tsx index 8d159f3528..5cdddb699e 100644 --- a/application/client/src/containers/AuthModalContainer.tsx +++ b/application/client/src/containers/AuthModalContainer.tsx @@ -43,8 +43,10 @@ export const AuthModalContainer = ({ id, onUpdateActiveUser }: Props) => { const element = ref.current; const handleToggle = () => { - // モーダル開閉時にkeyを更新することでフォームの状態をリセットする - setResetKey((key) => key + 1); + // ダイアログが閉じた時だけフォームをリセット(開いた時はリセットしない) + if (!element.open) { + setResetKey((key) => key + 1); + } }; element.addEventListener("toggle", handleToggle); return () => { @@ -79,11 +81,7 @@ export const AuthModalContainer = ({ id, onUpdateActiveUser }: Props) => { return ( - + ); }; diff --git a/application/client/src/hooks/use_infinite_fetch.ts b/application/client/src/hooks/use_infinite_fetch.ts index 394fccd9ea..9612fe2c76 100644 --- a/application/client/src/hooks/use_infinite_fetch.ts +++ b/application/client/src/hooks/use_infinite_fetch.ts @@ -9,10 +9,7 @@ interface ReturnValues { fetchMore: () => void; } -export function useInfiniteFetch( - apiPath: string, - fetcher: (apiPath: string) => Promise, -): ReturnValues { +export function useInfiniteFetch(apiPath: string, fetcher: (apiPath: string) => Promise): ReturnValues { const internalRef = useRef({ isLoading: false, offset: 0 }); const [result, setResult] = useState, "fetchMore">>({ @@ -36,11 +33,12 @@ export function useInfiniteFetch( offset, }; - void fetcher(apiPath).then( - (allData) => { + const separator = apiPath.includes("?") ? "&" : "?"; + void fetcher(`${apiPath}${separator}limit=${LIMIT}&offset=${offset}`).then( + (pageData) => { setResult((cur) => ({ ...cur, - data: [...cur.data, ...allData.slice(offset, offset + LIMIT)], + data: [...cur.data, ...pageData], isLoading: false, })); internalRef.current = { diff --git a/application/client/src/search/services.ts b/application/client/src/search/services.ts index 77f9de5de9..e8713b9bb0 100644 --- a/application/client/src/search/services.ts +++ b/application/client/src/search/services.ts @@ -10,8 +10,8 @@ export const sanitizeSearchText = (input: string): string => { }; export const parseSearchQuery = (query: string) => { - const sincePattern = /since:((\d|\d\d|\d\d\d\d-\d\d-\d\d)+)+$/; - const untilPattern = /until:((\d|\d\d|\d\d\d\d-\d\d-\d\d)+)+$/; + const sincePattern = /since:(\d{4}-\d{2}-\d{2})/; + const untilPattern = /until:(\d{4}-\d{2}-\d{2})/; const sincePart = query.match(/since:[^\s]*/)?.[0] || ""; const untilPart = query.match(/until:[^\s]*/)?.[0] || ""; @@ -38,7 +38,7 @@ export const parseSearchQuery = (query: string) => { }; export const isValidDate = (dateStr: string): boolean => { - const slowDateLike = /^(\d+)+-(\d+)+-(\d+)+$/; + const slowDateLike = /^\d+-\d+-\d+$/; if (!slowDateLike.test(dateStr)) return false; const date = new Date(dateStr); diff --git a/application/client/src/utils/convert_image.ts b/application/client/src/utils/convert_image.ts index 9fce086d9c..1c4584ca74 100644 --- a/application/client/src/utils/convert_image.ts +++ b/application/client/src/utils/convert_image.ts @@ -1,5 +1,4 @@ import { initializeImageMagick, ImageMagick, MagickFormat } from "@imagemagick/magick-wasm"; -import magickWasm from "@imagemagick/magick-wasm/magick.wasm?binary"; import { dump, insert, ImageIFD } from "piexifjs"; interface Options { @@ -7,7 +6,7 @@ interface Options { } export async function convertImage(file: File, options: Options): Promise { - await initializeImageMagick(magickWasm); + await initializeImageMagick(new URL("/static/magick.wasm", location.origin)); const byteArray = new Uint8Array(await file.arrayBuffer()); diff --git a/application/client/src/utils/fetchers.ts b/application/client/src/utils/fetchers.ts index 92a14f408f..c4615071b6 100644 --- a/application/client/src/utils/fetchers.ts +++ b/application/client/src/utils/fetchers.ts @@ -3,7 +3,6 @@ import { gzip } from "pako"; export async function fetchBinary(url: string): Promise { const result = await $.ajax({ - async: false, dataType: "binary", method: "GET", responseType: "arraybuffer", @@ -14,7 +13,6 @@ export async function fetchBinary(url: string): Promise { export async function fetchJSON(url: string): Promise { const result = await $.ajax({ - async: false, dataType: "json", method: "GET", url, @@ -24,7 +22,6 @@ export async function fetchJSON(url: string): Promise { export async function sendFile(url: string, file: File): Promise { const result = await $.ajax({ - async: false, data: file, dataType: "json", headers: { @@ -43,7 +40,6 @@ export async function sendJSON(url: string, data: object): Promise { const compressed = gzip(uint8Array); const result = await $.ajax({ - async: false, data: compressed, dataType: "json", headers: { diff --git a/application/client/src/utils/load_ffmpeg.ts b/application/client/src/utils/load_ffmpeg.ts index f923a3d5a4..985c7ed952 100644 --- a/application/client/src/utils/load_ffmpeg.ts +++ b/application/client/src/utils/load_ffmpeg.ts @@ -4,12 +4,8 @@ export async function loadFFmpeg(): Promise { const ffmpeg = new FFmpeg(); await ffmpeg.load({ - coreURL: await import("@ffmpeg/core?binary").then(({ default: b }) => { - return URL.createObjectURL(new Blob([b], { type: "text/javascript" })); - }), - wasmURL: await import("@ffmpeg/core/wasm?binary").then(({ default: b }) => { - return URL.createObjectURL(new Blob([b], { type: "application/wasm" })); - }), + coreURL: "/static/ffmpeg-core.js", + wasmURL: "/static/ffmpeg-core.wasm", }); return ffmpeg; diff --git a/application/client/webpack.config.js b/application/client/webpack.config.js index 9fae72647f..2464d97001 100644 --- a/application/client/webpack.config.js +++ b/application/client/webpack.config.js @@ -1,6 +1,7 @@ /// const path = require("path"); +const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer"); const CopyWebpackPlugin = require("copy-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); @@ -88,12 +89,27 @@ const config = { from: path.resolve(__dirname, "node_modules/katex/dist/fonts"), to: path.resolve(DIST_PATH, "styles/fonts"), }, + { + from: path.resolve(__dirname, "node_modules/@ffmpeg/core/dist/umd/ffmpeg-core.js"), + to: path.resolve(DIST_PATH, "static/ffmpeg-core.js"), + }, + { + from: path.resolve(__dirname, "node_modules/@ffmpeg/core/dist/umd/ffmpeg-core.wasm"), + to: path.resolve(DIST_PATH, "static/ffmpeg-core.wasm"), + }, + { + from: path.resolve(__dirname, "node_modules/@imagemagick/magick-wasm/dist/magick.wasm"), + to: path.resolve(DIST_PATH, "static/magick.wasm"), + }, ], }), new HtmlWebpackPlugin({ inject: false, template: path.resolve(SRC_PATH, "./index.html"), }), + ...(process.env.ANALYZE === "true" + ? [new BundleAnalyzerPlugin({ analyzerMode: "static", reportFilename: "bundle-report.html", openAnalyzer: false })] + : []), ], resolve: { extensions: [".tsx", ".ts", ".mjs", ".cjs", ".jsx", ".js"], @@ -105,21 +121,6 @@ const config = { "node_modules", "@ffmpeg/ffmpeg/dist/esm/index.js", ), - "@ffmpeg/core$": path.resolve( - __dirname, - "node_modules", - "@ffmpeg/core/dist/umd/ffmpeg-core.js", - ), - "@ffmpeg/core/wasm$": path.resolve( - __dirname, - "node_modules", - "@ffmpeg/core/dist/umd/ffmpeg-core.wasm", - ), - "@imagemagick/magick-wasm/magick.wasm$": path.resolve( - __dirname, - "node_modules", - "@imagemagick/magick-wasm/dist/magick.wasm", - ), }, fallback: { fs: false, diff --git a/application/pnpm-lock.yaml b/application/pnpm-lock.yaml index 510570f5c9..6e721a40f8 100644 --- a/application/pnpm-lock.yaml +++ b/application/pnpm-lock.yaml @@ -62,7 +62,7 @@ importers: version: 9.5.0 gifler: specifier: github:themadcreator/gifler#v0.3.0 - version: https://codeload.github.com/themadcreator/gifler/tar.gz/c3259b071c7782f85d4928a5f03d0b378ed003b5 + version: https://codeload.github.com/themadcreator/gifler/tar.gz/89484cb3db174c584a3138e89664f0167a7760c1 image-size: specifier: 2.0.2 version: 2.0.2 @@ -244,9 +244,12 @@ importers: webpack: specifier: 5.102.1 version: 5.102.1(webpack-cli@6.0.1) + webpack-bundle-analyzer: + specifier: 4.10.2 + version: 4.10.2 webpack-cli: specifier: 6.0.1 - version: 6.0.1(webpack-dev-server@5.2.2)(webpack@5.102.1) + version: 6.0.1(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@5.2.2)(webpack@5.102.1) webpack-dev-server: specifier: 5.2.2 version: 5.2.2(webpack-cli@6.0.1)(webpack@5.102.1) @@ -301,6 +304,9 @@ importers: serve-static: specifier: 2.2.0 version: 2.2.0 + sharp: + specifier: ^0.34.5 + version: 0.34.5 sqlite3: specifier: 5.1.7 version: 5.1.7 @@ -1181,6 +1187,10 @@ packages: peerDependencies: postcss: ^8.4 + '@discoveryjs/json-ext@0.5.7': + resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} + engines: {node: '>=10.0.0'} + '@discoveryjs/json-ext@0.6.3': resolution: {integrity: sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==} engines: {node: '>=14.17.0'} @@ -1190,6 +1200,9 @@ packages: peerDependencies: react: '19' + '@emnapi/runtime@1.9.1': + resolution: {integrity: sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==} + '@esbuild/aix-ppc64@0.25.11': resolution: {integrity: sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==} engines: {node: '>=18'} @@ -1368,6 +1381,159 @@ packages: '@imagemagick/magick-wasm@0.0.37': resolution: {integrity: sha512-tVs9hcWu9u7I3Jz/XvUYVvCEniuxAR+JjZEzI+yKtQmYAtNsLF1WjoH1HZGCKPumaB9jAHZlcf2RGT9+1l3nxQ==} + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -1608,6 +1774,9 @@ packages: engines: {node: '>=18'} hasBin: true + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + '@tokenizer/inflate@0.4.1': resolution: {integrity: sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==} engines: {node: '>=18'} @@ -1888,6 +2057,10 @@ packages: peerDependencies: acorn: ^8.14.0 + acorn-walk@8.3.5: + resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==} + engines: {node: '>=0.4.0'} + acorn@8.15.0: resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} engines: {node: '>=0.4.0'} @@ -2147,6 +2320,10 @@ packages: commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + commander@8.3.0: resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} engines: {node: '>= 12'} @@ -2293,6 +2470,9 @@ packages: csv-parse@1.3.3: resolution: {integrity: sha512-byxnDBxM1AVF3YfmsK7Smop9/usNz7gAZYSo9eYp61TGcNXraJby1rAiLyJSt1/8Iho2qaxZOtZCOvQMXogPtg==} + debounce@1.2.1: + resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} + debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -2395,6 +2575,9 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + duplexer@0.1.2: + resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} + ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -2478,6 +2661,10 @@ packages: escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + escape-string-regexp@5.0.0: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} @@ -2667,8 +2854,8 @@ packages: get-tsconfig@4.13.0: resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} - gifler@https://codeload.github.com/themadcreator/gifler/tar.gz/c3259b071c7782f85d4928a5f03d0b378ed003b5: - resolution: {tarball: https://codeload.github.com/themadcreator/gifler/tar.gz/c3259b071c7782f85d4928a5f03d0b378ed003b5} + gifler@https://codeload.github.com/themadcreator/gifler/tar.gz/89484cb3db174c584a3138e89664f0167a7760c1: + resolution: {tarball: https://codeload.github.com/themadcreator/gifler/tar.gz/89484cb3db174c584a3138e89664f0167a7760c1} version: 0.3.0 github-from-package@0.0.0: @@ -2702,6 +2889,10 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + gzip-size@6.0.0: + resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==} + engines: {node: '>=10'} + handle-thing@2.0.1: resolution: {integrity: sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==} @@ -2766,6 +2957,9 @@ packages: hpack.js@2.1.6: resolution: {integrity: sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==} + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + html-minifier-terser@6.1.0: resolution: {integrity: sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==} engines: {node: '>=12'} @@ -3384,6 +3578,10 @@ packages: moment@2.30.1: resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} @@ -3515,6 +3713,10 @@ packages: resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} engines: {node: '>=18'} + opener@1.5.2: + resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} + hasBin: true + oxfmt@0.36.0: resolution: {integrity: sha512-/ejJ+KoSW6J9bcNT9a9UtJSJNWhJ3yOLSBLbkoFHJs/8CZjmaZVZAJe4YgO1KMJlKpNQasrn/G9JQUEZI3p0EQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4209,6 +4411,10 @@ packages: resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==} engines: {node: '>=8'} + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -4246,6 +4452,10 @@ packages: simple-get@4.0.1: resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + sirv@2.0.4: + resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==} + engines: {node: '>= 10'} + smart-buffer@4.2.0: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} @@ -4419,6 +4629,10 @@ packages: toposort-class@1.0.1: resolution: {integrity: sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg==} + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + tree-dump@1.1.0: resolution: {integrity: sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==} engines: {node: '>=10.0'} @@ -4575,6 +4789,11 @@ packages: web-namespaces@2.0.1: resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + webpack-bundle-analyzer@4.10.2: + resolution: {integrity: sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==} + engines: {node: '>= 10.13.0'} + hasBin: true + webpack-cli@6.0.1: resolution: {integrity: sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==} engines: {node: '>=18.12.0'} @@ -4654,6 +4873,18 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@7.5.10: + resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + ws@8.18.3: resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} engines: {node: '>=10.0.0'} @@ -5710,6 +5941,8 @@ snapshots: dependencies: postcss: 8.5.6 + '@discoveryjs/json-ext@0.5.7': {} + '@discoveryjs/json-ext@0.6.3': {} '@dr.pogodin/react-helmet@3.0.4(react@19.2.0)': @@ -5717,6 +5950,11 @@ snapshots: '@babel/runtime': 7.28.4 react: 19.2.0 + '@emnapi/runtime@1.9.1': + dependencies: + tslib: 2.8.1 + optional: true + '@esbuild/aix-ppc64@0.25.11': optional: true @@ -5810,6 +6048,102 @@ snapshots: '@imagemagick/magick-wasm@0.0.37': {} + '@img/colour@1.1.0': {} + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.9.1 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -5972,6 +6306,8 @@ snapshots: dependencies: playwright: 1.50.1 + '@polka/url@1.0.0-next.29': {} + '@tokenizer/inflate@0.4.1': dependencies: debug: 4.4.3 @@ -6260,17 +6596,17 @@ snapshots: '@webpack-cli/configtest@3.0.1(webpack-cli@6.0.1)(webpack@5.102.1)': dependencies: webpack: 5.102.1(webpack-cli@6.0.1) - webpack-cli: 6.0.1(webpack-dev-server@5.2.2)(webpack@5.102.1) + webpack-cli: 6.0.1(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@5.2.2)(webpack@5.102.1) '@webpack-cli/info@3.0.1(webpack-cli@6.0.1)(webpack@5.102.1)': dependencies: webpack: 5.102.1(webpack-cli@6.0.1) - webpack-cli: 6.0.1(webpack-dev-server@5.2.2)(webpack@5.102.1) + webpack-cli: 6.0.1(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@5.2.2)(webpack@5.102.1) '@webpack-cli/serve@3.0.1(webpack-cli@6.0.1)(webpack-dev-server@5.2.2)(webpack@5.102.1)': dependencies: webpack: 5.102.1(webpack-cli@6.0.1) - webpack-cli: 6.0.1(webpack-dev-server@5.2.2)(webpack@5.102.1) + webpack-cli: 6.0.1(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@5.2.2)(webpack@5.102.1) optionalDependencies: webpack-dev-server: 5.2.2(webpack-cli@6.0.1)(webpack@5.102.1) @@ -6295,6 +6631,10 @@ snapshots: dependencies: acorn: 8.15.0 + acorn-walk@8.3.5: + dependencies: + acorn: 8.15.0 + acorn@8.15.0: {} agent-base@6.0.2: @@ -6605,6 +6945,8 @@ snapshots: commander@2.20.3: {} + commander@7.2.0: {} + commander@8.3.0: {} common-tags@1.8.2: {} @@ -6743,6 +7085,8 @@ snapshots: csv-parse@1.3.3: {} + debounce@1.2.1: {} + debug@2.6.9: dependencies: ms: 2.0.0 @@ -6830,6 +7174,8 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + duplexer@0.1.2: {} + ee-first@1.1.1: {} electron-to-chromium@1.5.233: {} @@ -6916,6 +7262,8 @@ snapshots: escape-html@1.0.3: {} + escape-string-regexp@4.0.0: {} + escape-string-regexp@5.0.0: {} eslint-scope@5.1.1: @@ -7161,7 +7509,7 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 - gifler@https://codeload.github.com/themadcreator/gifler/tar.gz/c3259b071c7782f85d4928a5f03d0b378ed003b5: + gifler@https://codeload.github.com/themadcreator/gifler/tar.gz/89484cb3db174c584a3138e89664f0167a7760c1: dependencies: bluebird: 3.7.2 omggif: 1.0.10 @@ -7196,6 +7544,10 @@ snapshots: graceful-fs@4.2.11: {} + gzip-size@6.0.0: + dependencies: + duplexer: 0.1.2 + handle-thing@2.0.1: {} has-flag@4.0.0: {} @@ -7306,6 +7658,8 @@ snapshots: readable-stream: 2.3.8 wbuf: 1.7.3 + html-escaper@2.0.2: {} + html-minifier-terser@6.1.0: dependencies: camel-case: 4.1.2 @@ -8139,6 +8493,8 @@ snapshots: moment@2.30.1: {} + mrmime@2.0.1: {} + ms@2.0.0: {} ms@2.1.3: {} @@ -8277,6 +8633,8 @@ snapshots: is-inside-container: 1.0.0 wsl-utils: 0.1.0 + opener@1.5.2: {} + oxfmt@0.36.0: dependencies: tinypool: 2.1.0 @@ -9144,6 +9502,37 @@ snapshots: dependencies: kind-of: 6.0.3 + sharp@0.34.5: + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.7.3 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -9191,6 +9580,12 @@ snapshots: once: 1.4.0 simple-concat: 1.0.1 + sirv@2.0.4: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + smart-buffer@4.2.0: optional: true @@ -9393,6 +9788,8 @@ snapshots: toposort-class@1.0.1: {} + totalist@3.0.1: {} + tree-dump@1.1.0(tslib@2.8.1): dependencies: tslib: 2.8.1 @@ -9553,7 +9950,25 @@ snapshots: web-namespaces@2.0.1: {} - webpack-cli@6.0.1(webpack-dev-server@5.2.2)(webpack@5.102.1): + webpack-bundle-analyzer@4.10.2: + dependencies: + '@discoveryjs/json-ext': 0.5.7 + acorn: 8.15.0 + acorn-walk: 8.3.5 + commander: 7.2.0 + debounce: 1.2.1 + escape-string-regexp: 4.0.0 + gzip-size: 6.0.0 + html-escaper: 2.0.2 + opener: 1.5.2 + picocolors: 1.1.1 + sirv: 2.0.4 + ws: 7.5.10 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@5.2.2)(webpack@5.102.1): dependencies: '@discoveryjs/json-ext': 0.6.3 '@webpack-cli/configtest': 3.0.1(webpack-cli@6.0.1)(webpack@5.102.1) @@ -9570,6 +9985,7 @@ snapshots: webpack: 5.102.1(webpack-cli@6.0.1) webpack-merge: 6.0.1 optionalDependencies: + webpack-bundle-analyzer: 4.10.2 webpack-dev-server: 5.2.2(webpack-cli@6.0.1)(webpack@5.102.1) webpack-dev-middleware@7.4.5(webpack@5.102.1): @@ -9615,7 +10031,7 @@ snapshots: ws: 8.18.3 optionalDependencies: webpack: 5.102.1(webpack-cli@6.0.1) - webpack-cli: 6.0.1(webpack-dev-server@5.2.2)(webpack@5.102.1) + webpack-cli: 6.0.1(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@5.2.2)(webpack@5.102.1) transitivePeerDependencies: - bufferutil - debug @@ -9658,7 +10074,7 @@ snapshots: watchpack: 2.4.4 webpack-sources: 3.3.3 optionalDependencies: - webpack-cli: 6.0.1(webpack-dev-server@5.2.2)(webpack@5.102.1) + webpack-cli: 6.0.1(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@5.2.2)(webpack@5.102.1) transitivePeerDependencies: - '@swc/core' - esbuild @@ -9689,6 +10105,8 @@ snapshots: wrappy@1.0.2: {} + ws@7.5.10: {} + ws@8.18.3: {} wsl-utils@0.1.0: diff --git a/application/server/package.json b/application/server/package.json index 9482575df7..155632ed62 100644 --- a/application/server/package.json +++ b/application/server/package.json @@ -25,6 +25,7 @@ "music-metadata": "11.10.3", "sequelize": "6.37.7", "serve-static": "2.2.0", + "sharp": "^0.34.5", "sqlite3": "5.1.7", "tsx": "4.20.6", "uuid": "13.0.0", diff --git a/application/server/pnpm-lock.yaml b/application/server/pnpm-lock.yaml new file mode 100644 index 0000000000..8a54e70d33 --- /dev/null +++ b/application/server/pnpm-lock.yaml @@ -0,0 +1,315 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@web-speed-hackathon-2026/server': + specifier: workspace:* + version: 'link:' + sharp: + specifier: ^0.34.5 + version: 0.34.5 + +packages: + + '@emnapi/runtime@1.9.1': + resolution: {integrity: sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==} + + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + +snapshots: + + '@emnapi/runtime@1.9.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@img/colour@1.1.0': {} + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.9.1 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + + detect-libc@2.1.2: {} + + semver@7.7.4: {} + + sharp@0.34.5: + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.7.4 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + + tslib@2.8.1: + optional: true diff --git a/application/server/src/app.ts b/application/server/src/app.ts index 671fb424cc..7244eb0dc4 100644 --- a/application/server/src/app.ts +++ b/application/server/src/app.ts @@ -21,5 +21,9 @@ app.use((_req, res, next) => { return next(); }); +app.use((req, _res, next) => { + console.log(`[req] ${req.method} ${req.url} content-type=${req.headers["content-type"]} content-encoding=${req.headers["content-encoding"]}`); + next(); +}); app.use("/api/v1", apiRouter); app.use(staticRouter); diff --git a/application/server/src/routes/api/auth.ts b/application/server/src/routes/api/auth.ts index f313e87bbb..0a0c5f3a66 100644 --- a/application/server/src/routes/api/auth.ts +++ b/application/server/src/routes/api/auth.ts @@ -25,20 +25,26 @@ authRouter.post("/signup", async (req, res) => { }); authRouter.post("/signin", async (req, res) => { + console.log("[signin] body:", req.body); const user = await User.findOne({ where: { username: req.body.username, }, }); + console.log("[signin] user found:", user?.username ?? null); if (user === null) { + console.log("[signin] user not found"); throw new httpErrors.BadRequest(); } - if (!user.validPassword(req.body.password)) { + const valid = user.validPassword(req.body.password); + console.log("[signin] password valid:", valid); + if (!valid) { throw new httpErrors.BadRequest(); } req.session.userId = user.id; + console.log("[signin] success"); return res.status(200).type("application/json").send(user); }); diff --git a/application/server/src/routes/static.ts b/application/server/src/routes/static.ts index b5820c986e..9c91012131 100644 --- a/application/server/src/routes/static.ts +++ b/application/server/src/routes/static.ts @@ -1,35 +1,51 @@ +import { promises as fs } from "fs"; +import path from "path"; + import history from "connect-history-api-fallback"; import { Router } from "express"; +import sharp from "sharp"; import serveStatic from "serve-static"; -import { - CLIENT_DIST_PATH, - PUBLIC_PATH, - UPLOAD_PATH, -} from "@web-speed-hackathon-2026/server/src/paths"; +import { CLIENT_DIST_PATH, PUBLIC_PATH, UPLOAD_PATH } from "@web-speed-hackathon-2026/server/src/paths"; export const staticRouter = Router(); // SPA 対応のため、ファイルが存在しないときに index.html を返す staticRouter.use(history()); -staticRouter.use( - serveStatic(UPLOAD_PATH, { - etag: false, - lastModified: false, - }), -); - -staticRouter.use( - serveStatic(PUBLIC_PATH, { - etag: false, - lastModified: false, - }), -); - -staticRouter.use( - serveStatic(CLIENT_DIST_PATH, { - etag: false, - lastModified: false, - }), -); +const IMAGE_CACHE_PATH = path.resolve(UPLOAD_PATH, "cache/images"); + +staticRouter.get("/images/*relativePath", async (req, res, next) => { + const relativePathParam = (req.params as Record)["relativePath"]; + if (relativePathParam === undefined) return next(); + const relativePath = Array.isArray(relativePathParam) ? relativePathParam.join("/") : relativePathParam; + const originalPath = path.resolve(UPLOAD_PATH, "images", relativePath); + + try { + await fs.access(originalPath); + } catch { + return next(); + } + + const cachedPath = path.resolve(IMAGE_CACHE_PATH, relativePath.replace(/\.jpg$/, ".webp")); + + try { + await fs.access(cachedPath); + } catch { + await fs.mkdir(path.dirname(cachedPath), { recursive: true }); + await sharp(originalPath) + .resize({ width: 800, withoutEnlargement: true }) + .webp({ quality: 80 }) + .toFile(cachedPath); + } + + res.setHeader("Content-Type", "image/webp"); + res.setHeader("Cache-Control", "max-age=31536000, immutable"); + return res.sendFile(cachedPath); +}); + +staticRouter.use(serveStatic(UPLOAD_PATH)); + +staticRouter.use(serveStatic(PUBLIC_PATH)); + +staticRouter.use(serveStatic(CLIENT_DIST_PATH)); diff --git a/scoring-tool/signin-failure.png b/scoring-tool/signin-failure.png new file mode 100644 index 0000000000..4bb9866eea Binary files /dev/null and b/scoring-tool/signin-failure.png differ diff --git a/scoring-tool/src/scoring/calculate_user_auth_flow_action.ts b/scoring-tool/src/scoring/calculate_user_auth_flow_action.ts index da1858e3b0..f2d31554a1 100644 --- a/scoring-tool/src/scoring/calculate_user_auth_flow_action.ts +++ b/scoring-tool/src/scoring/calculate_user_auth_flow_action.ts @@ -126,13 +126,21 @@ export async function calculateUserAuthFlowAction({ } catch (err) { throw new Error("パスワードの入力に失敗しました", { cause: err }); } + const signinRequests: string[] = []; + playwrightPage.on("request", (req) => { + if (req.url().includes("/api/")) signinRequests.push(`${req.method()} ${req.url()}`); + }); try { const button = playwrightPage.getByRole("dialog").getByRole("button", { name: "サインイン" }); + const isDisabled = await button.isDisabled(); + console.log("[debug] signin button disabled:", isDisabled); await button.click(); await playwrightPage .getByRole("link", { name: "マイページ" }) .waitFor({ timeout: 10 * 1000 }); } catch (err) { + console.log("[debug] requests made:", signinRequests); + await playwrightPage.screenshot({ path: "signin-failure.png" }); throw new Error("サインインに失敗しました", { cause: err }); } }