From 07d6e48b3e5972f7e5bc396b13d93610f1f43bd5 Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Fri, 21 Nov 2025 16:13:16 +0100 Subject: [PATCH 01/31] test: set up BDD/E2E test support --- cucumber.js | 13 + package.json | 5 + pnpm-lock.yaml | 1041 +++++++++++++++++++++++++++++- tests/bdd/features/login.feature | 8 + tests/bdd/steps/global.steps.ts | 41 ++ tests/bdd/support/hooks.ts | 27 + tests/bdd/support/world.ts | 14 + tests/bdd/ts-node.js | 7 + tsconfig.json | 2 +- 9 files changed, 1141 insertions(+), 17 deletions(-) create mode 100644 cucumber.js create mode 100644 tests/bdd/features/login.feature create mode 100644 tests/bdd/steps/global.steps.ts create mode 100644 tests/bdd/support/hooks.ts create mode 100644 tests/bdd/support/world.ts create mode 100644 tests/bdd/ts-node.js diff --git a/cucumber.js b/cucumber.js new file mode 100644 index 00000000..d4db2fc4 --- /dev/null +++ b/cucumber.js @@ -0,0 +1,13 @@ +module.exports = { + default: { + requireModule: ["ts-node/register/transpile-only"], + require: [ + "tests/bdd/support/world.ts", + "tests/bdd/support/hooks.ts", + "tests/bdd/steps/**/*.ts", + ], + paths: ["tests/bdd/features/**/*.feature"], + publishQuiet: true, + format: ["progress"], + }, +}; diff --git a/package.json b/package.json index 1410567c..eba60530 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "lint": "biome check", "format": "biome format --write", "test": "vitest", + "test:bdd": "cucumber-js", "type-check": "tsc --noEmit", "prepare": "husky", "oidc": "node dev-auth/oidc-provider.mjs", @@ -43,9 +44,12 @@ }, "devDependencies": { "@biomejs/biome": "2.3.6", + "@cucumber/cucumber": "^12.2.0", + "@cucumber/messages": "^31.0.0", "@hey-api/client-next": "0.5.1", "@hey-api/openapi-ts": "0.87.5", "@mswjs/http-middleware": "^0.10.2", + "@playwright/test": "^1.56.1", "@tailwindcss/postcss": "^4", "@testing-library/dom": "^10.4.1", "@testing-library/jest-dom": "^6.9.1", @@ -63,6 +67,7 @@ "lint-staged": "^16.0.0", "oidc-provider": "^9.5.2", "tailwindcss": "^4", + "ts-node": "^10.9.2", "tsx": "4.20.6", "typescript": "^5", "vite": "^7.2.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 35ddde1e..a7e93970 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -31,7 +31,7 @@ importers: version: 3.0.1(ajv@8.17.1) better-auth: specifier: 1.4.0-beta.25 - version: 1.4.0-beta.25(next@16.0.3(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 1.4.0-beta.25(next@16.0.3(@babel/core@7.28.5)(@playwright/test@1.56.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0) class-variance-authority: specifier: 0.7.1 version: 0.7.1 @@ -52,7 +52,7 @@ importers: version: 2.12.2(@types/node@24.10.1)(typescript@5.9.3) next: specifier: 16.0.3 - version: 16.0.3(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 16.0.3(@babel/core@7.28.5)(@playwright/test@1.56.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) react: specifier: 19.2.0 version: 19.2.0 @@ -69,6 +69,12 @@ importers: '@biomejs/biome': specifier: 2.3.6 version: 2.3.6 + '@cucumber/cucumber': + specifier: ^12.2.0 + version: 12.2.0 + '@cucumber/messages': + specifier: ^31.0.0 + version: 31.0.0 '@hey-api/client-next': specifier: 0.5.1 version: 0.5.1(@hey-api/openapi-ts@0.87.5(typescript@5.9.3)) @@ -78,6 +84,9 @@ importers: '@mswjs/http-middleware': specifier: ^0.10.2 version: 0.10.3(msw@2.12.2(@types/node@24.10.1)(typescript@5.9.3)) + '@playwright/test': + specifier: ^1.56.1 + version: 1.56.1 '@tailwindcss/postcss': specifier: ^4 version: 4.1.17 @@ -129,6 +138,9 @@ importers: tailwindcss: specifier: ^4 version: 4.1.17 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@24.10.1)(typescript@5.9.3) tsx: specifier: 4.20.6 version: 4.20.6 @@ -327,6 +339,14 @@ packages: cpu: [x64] os: [win32] + '@colors/colors@1.5.0': + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} + engines: {node: '>=0.1.90'} + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + '@csstools/color-helpers@5.1.0': resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} engines: {node: '>=18'} @@ -359,6 +379,76 @@ packages: resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} engines: {node: '>=18'} + '@cucumber/ci-environment@10.0.1': + resolution: {integrity: sha512-/+ooDMPtKSmvcPMDYnMZt4LuoipfFfHaYspStI4shqw8FyKcfQAmekz6G+QKWjQQrvM+7Hkljwx58MEwPCwwzg==} + + '@cucumber/cucumber-expressions@18.0.1': + resolution: {integrity: sha512-NSid6bI+7UlgMywl5octojY5NXnxR9uq+JisjOrO52VbFsQM6gTWuQFE8syI10KnIBEdPzuEUSVEeZ0VFzRnZA==} + + '@cucumber/cucumber@12.2.0': + resolution: {integrity: sha512-b7W4snvXYi1T2puUjxamASCCNhNzVSzb/fQUuGSkdjm/AFfJ24jo8kOHQyOcaoArCG71sVQci4vkZaITzl/V1w==} + engines: {node: 20 || 22 || >=24} + hasBin: true + + '@cucumber/gherkin-streams@5.0.1': + resolution: {integrity: sha512-/7VkIE/ASxIP/jd4Crlp4JHXqdNFxPGQokqWqsaCCiqBiu5qHoKMxcWNlp9njVL/n9yN4S08OmY3ZR8uC5x74Q==} + hasBin: true + peerDependencies: + '@cucumber/gherkin': '>=22.0.0' + '@cucumber/message-streams': '>=4.0.0' + '@cucumber/messages': '>=17.1.1' + + '@cucumber/gherkin-utils@9.2.0': + resolution: {integrity: sha512-3nmRbG1bUAZP3fAaUBNmqWO0z0OSkykZZotfLjyhc8KWwDSOrOmMJlBTd474lpA8EWh4JFLAX3iXgynBqBvKzw==} + hasBin: true + + '@cucumber/gherkin@31.0.0': + resolution: {integrity: sha512-wlZfdPif7JpBWJdqvHk1Mkr21L5vl4EfxVUOS4JinWGf3FLRV6IKUekBv5bb5VX79fkDcfDvESzcQ8WQc07Wgw==} + + '@cucumber/gherkin@34.0.0': + resolution: {integrity: sha512-659CCFsrsyvuBi/Eix1fnhSheMnojSfnBcqJ3IMPNawx7JlrNJDcXYSSdxcUw3n/nG05P+ptCjmiZY3i14p+tA==} + + '@cucumber/html-formatter@21.14.0': + resolution: {integrity: sha512-vQqbmQZc0QiN4c+cMCffCItpODJlOlYtPG7pH6We096dBOa7u0ttDMjT6KrMAnQlcln54rHL46r408IFpuznAw==} + peerDependencies: + '@cucumber/messages': '>=18' + + '@cucumber/junit-xml-formatter@0.8.1': + resolution: {integrity: sha512-FT1Y96pyd9/ifbE9I7dbkTCjkwEdW9C0MBobUZoKD13c8EnWAt0xl1Yy/v/WZLTk4XfCLte1DATtLx01jt+YiA==} + peerDependencies: + '@cucumber/messages': '*' + + '@cucumber/message-streams@4.0.1': + resolution: {integrity: sha512-Kxap9uP5jD8tHUZVjTWgzxemi/0uOsbGjd4LBOSxcJoOCRbESFwemUzilJuzNTB8pcTQUh8D5oudUyxfkJOKmA==} + peerDependencies: + '@cucumber/messages': '>=17.1.1' + + '@cucumber/messages@26.0.1': + resolution: {integrity: sha512-DIxSg+ZGariumO+Lq6bn4kOUIUET83A4umrnWmidjGFl8XxkBieUZtsmNbLYgH/gnsmP07EfxxdTr0hOchV1Sg==} + + '@cucumber/messages@27.2.0': + resolution: {integrity: sha512-f2o/HqKHgsqzFLdq6fAhfG1FNOQPdBdyMGpKwhb7hZqg0yZtx9BVqkTyuoNk83Fcvk3wjMVfouFXXHNEk4nddA==} + + '@cucumber/messages@28.1.0': + resolution: {integrity: sha512-2LzZtOwYKNlCuNf31ajkrekoy2M4z0Z1QGiPH40n4gf5t8VOUFb7m1ojtR4LmGvZxBGvJZP8voOmRqDWzBzYKA==} + + '@cucumber/messages@31.0.0': + resolution: {integrity: sha512-Dqhatp4AjMsH9SREfWz3Q8nlGuwJMTW7YAW5L3OzRId86ZUEu/a8vIL1RO2c0agQefuBS2SVH9fEZ66ovrMYRA==} + + '@cucumber/pretty-formatter@1.0.1': + resolution: {integrity: sha512-A1lU4VVP0aUWdOTmpdzvXOyEYuPtBDI0xYwYJnmoMDplzxMdhcHk86lyyvYDoMoPzzq6OkOE3isuosvUU4X7IQ==} + peerDependencies: + '@cucumber/cucumber': '>=7.0.0' + '@cucumber/messages': '*' + + '@cucumber/query@13.6.0': + resolution: {integrity: sha512-tiDneuD5MoWsJ9VKPBmQok31mSX9Ybl+U4wqDoXeZgsXHDURqzM3rnpWVV3bC34y9W6vuFxrlwF/m7HdOxwqRw==} + peerDependencies: + '@cucumber/messages': '*' + + '@cucumber/tag-expressions@6.2.0': + resolution: {integrity: sha512-KIF0eLcafHbWOuSDWFw0lMmgJOLdDRWjEL1kfXEWrqHmx2119HxVAr35WuEd9z542d3Yyg+XNqSr+81rIKqEdg==} + '@emnapi/runtime@1.7.0': resolution: {integrity: sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q==} @@ -728,6 +818,18 @@ packages: '@types/node': optional: true + '@isaacs/balanced-match@4.0.1': + resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} + engines: {node: 20 || >=22} + + '@isaacs/brace-expansion@5.0.0': + resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} + engines: {node: 20 || >=22} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -744,6 +846,9 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@jsdevtools/ono@7.1.3': resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} @@ -845,6 +950,11 @@ packages: '@open-draft/until@2.1.0': resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + '@playwright/test@1.56.1': + resolution: {integrity: sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==} + engines: {node: '>=18'} + hasBin: true + '@radix-ui/primitive@1.1.3': resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} @@ -1397,6 +1507,10 @@ packages: '@tailwindcss/postcss@4.1.17': resolution: {integrity: sha512-+nKl9N9mN5uJ+M7dBOOCzINw94MPstNR/GtIhz1fpZysxL/4a+No64jCBD6CPN+bIHWFx3KWuu8XJRrj/572Dw==} + '@teppeis/multimaps@3.0.0': + resolution: {integrity: sha512-ID7fosbc50TbT0MK0EG12O+gAP3W3Aa/Pz4DaTtQtEvlc9Odaqi0de+xuZ7Li2GtK4HzEX7IuRWS/JmZLksR3Q==} + engines: {node: '>=14'} + '@testing-library/dom@10.4.1': resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} engines: {node: '>=18'} @@ -1426,6 +1540,18 @@ packages: peerDependencies: '@testing-library/dom': '>=7.21.4' + '@tsconfig/node10@1.0.12': + resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} @@ -1456,6 +1582,9 @@ packages: '@types/node@24.10.1': resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} + '@types/normalize-package-data@2.4.4': + resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + '@types/react-dom@19.2.3': resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} peerDependencies: @@ -1467,6 +1596,9 @@ packages: '@types/statuses@2.0.6': resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} + '@types/uuid@10.0.0': + resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} + '@vitejs/plugin-react@5.1.1': resolution: {integrity: sha512-WQfkSw0QbQ5aJ2CHYw23ZGkqnRwqKHD/KYsMeTkZzPT4Jcf0DcBxBtwMJxnu6E7oxw5+JC6ZAiePgh28uJ1HBA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1506,6 +1638,15 @@ packages: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} + acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + agent-base@7.1.4: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} @@ -1529,6 +1670,10 @@ packages: resolution: {integrity: sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==} engines: {node: '>=18'} + ansi-regex@4.1.1: + resolution: {integrity: sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==} + engines: {node: '>=6'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -1549,6 +1694,12 @@ packages: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -1565,6 +1716,9 @@ packages: array-flatten@1.1.1: resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + assertion-error-formatter@3.0.0: + resolution: {integrity: sha512-6YyAVLrEze0kQ7CmJfUgrLHb+Y7XghmL2Ie7ijVa2Y9ynP3LV+VDiwFk62Dn0qtqbmY0BT0ss6p1xxpiF2PYbQ==} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -1624,6 +1778,9 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + bundle-name@4.1.0: resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} engines: {node: '>=18'} @@ -1654,6 +1811,9 @@ packages: caniuse-lite@1.0.30001754: resolution: {integrity: sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==} + capital-case@1.0.4: + resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==} + chai@6.2.1: resolution: {integrity: sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==} engines: {node: '>=18'} @@ -1669,6 +1829,9 @@ packages: citty@0.1.6: resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + class-transformer@0.5.1: + resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==} + class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} @@ -1676,6 +1839,10 @@ packages: resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} engines: {node: '>=18'} + cli-table3@0.6.5: + resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} + engines: {node: 10.* || >= 12.*} + cli-truncate@5.1.1: resolution: {integrity: sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==} engines: {node: '>=20'} @@ -1709,6 +1876,10 @@ packages: colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + commander@13.1.0: + resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} + engines: {node: '>=18'} + commander@14.0.1: resolution: {integrity: sha512-2JkV3gUZUVrbNA+1sjBOYLsMZ5cEEl8GTFP2a4AVz5hvasAMCQ1D2l2le/cX+pV4N6ZU17zjUahLpIXRrnWL8A==} engines: {node: '>=20'} @@ -1717,6 +1888,10 @@ packages: resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==} engines: {node: '>=20'} + commander@9.1.0: + resolution: {integrity: sha512-i0/MaqBtdbnJ4XQs4Pmyb+oFQl+q0lsAmokVUH92SlSw4fkeAcG3bVon+Qt7hmtF+u3Het6o4VgrcY3qAoEB6w==} + engines: {node: ^12.20.0 || >=14} + concurrently@9.2.1: resolution: {integrity: sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==} engines: {node: '>=18'} @@ -1755,6 +1930,13 @@ packages: resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==} engines: {node: '>= 0.8'} + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + css-tree@3.1.0: resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} @@ -1840,6 +2022,10 @@ packages: detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} @@ -1854,6 +2040,9 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -1866,6 +2055,9 @@ packages: emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + encodeurl@1.0.2: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} @@ -1886,6 +2078,9 @@ packages: resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} engines: {node: '>=18'} + error-stack-parser@2.1.4: + resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==} + es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -1913,6 +2108,10 @@ packages: escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} @@ -1958,6 +2157,10 @@ packages: picomatch: optional: true + figures@3.2.0: + resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} + engines: {node: '>=8'} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -1966,6 +2169,14 @@ packages: resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} engines: {node: '>= 0.8'} + find-up-simple@1.0.1: + resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} + engines: {node: '>=18'} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + format-util@1.0.5: resolution: {integrity: sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg==} @@ -1977,6 +2188,11 @@ packages: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -2016,6 +2232,15 @@ packages: resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} hasBin: true + glob@11.1.0: + resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==} + engines: {node: 20 || >=22} + hasBin: true + + global-dirs@3.0.1: + resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} + engines: {node: '>=10'} + globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} @@ -2030,6 +2255,10 @@ packages: resolution: {integrity: sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + has-ansi@4.0.1: + resolution: {integrity: sha512-Qr4RtTm30xvEdqUXbSBVWDu+PrTokJOwe/FU+VdfJPk+MXAPoeOzKpRyrDTnZIJwAkQ4oBLTU53nu0HrkF/Z2A==} + engines: {node: '>=8'} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -2045,6 +2274,10 @@ packages: headers-polyfill@4.0.3: resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==} + hosted-git-info@7.0.2: + resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} + engines: {node: ^16.14.0 || >=18.0.0} + html-encoding-sniffer@4.0.0: resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} engines: {node: '>=18'} @@ -2090,9 +2323,17 @@ packages: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} + index-to-position@1.2.0: + resolution: {integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==} + engines: {node: '>=18'} + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + ini@2.0.0: + resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==} + engines: {node: '>=10'} + ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} @@ -2115,6 +2356,10 @@ packages: engines: {node: '>=14.16'} hasBin: true + is-installed-globally@0.4.0: + resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==} + engines: {node: '>=10'} + is-node-process@1.2.0: resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} @@ -2122,13 +2367,28 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + is-wsl@3.1.0: resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} engines: {node: '>=16'} + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jackspeak@4.1.1: + resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} + engines: {node: 20 || >=22} + jiti@2.6.1: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true @@ -2191,6 +2451,9 @@ packages: engines: {node: '>= 0.6'} deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + knuth-shuffle-seeded@1.0.6: + resolution: {integrity: sha512-9pFH0SplrfyKyojCLxZfMcvkhf5hH0d+UwR9nTVJ/DDQJGuzcXjTwB7TP7sDfehSudlGGaOLblmEWqv04ERVWg==} + koa-compose@4.1.0: resolution: {integrity: sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==} @@ -2281,6 +2544,15 @@ packages: resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==} engines: {node: '>=20.0.0'} + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash.mergewith@4.6.2: + resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==} + + lodash.sortby@4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -2288,6 +2560,12 @@ packages: resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} engines: {node: '>=18'} + lower-case@2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@11.2.2: resolution: {integrity: sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==} engines: {node: 20 || >=22} @@ -2300,6 +2578,10 @@ packages: peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + luxon@3.7.1: + resolution: {integrity: sha512-RkRWjA926cTvz5rAb1BqyWkKbbjzCGchDUIKMCUvNi17j6f6j8uHGDV82Aqcqtzd+icoYpELmG3ksgGiFNNcNg==} + engines: {node: '>=12'} + lz-string@1.5.0: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true @@ -2307,6 +2589,9 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -2354,6 +2639,11 @@ packages: engines: {node: '>=4'} hasBin: true + mime@3.0.0: + resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} + engines: {node: '>=10.0.0'} + hasBin: true + mimic-function@5.0.1: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} @@ -2362,6 +2652,19 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} + minimatch@10.1.1: + resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} + engines: {node: 20 || >=22} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} + hasBin: true + ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} @@ -2382,6 +2685,9 @@ packages: resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} engines: {node: ^18.17.0 || >=20.5.0} + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + nano-spawn@2.0.0: resolution: {integrity: sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==} engines: {node: '>=20.17'} @@ -2425,17 +2731,28 @@ packages: sass: optional: true + no-case@3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + node-fetch-native@1.6.7: resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + normalize-package-data@6.0.2: + resolution: {integrity: sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==} + engines: {node: ^16.14.0 || >=18.0.0} + nypm@0.6.2: resolution: {integrity: sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==} engines: {node: ^14.16.0 || >=16.10.0} hasBin: true + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + object-inspect@1.13.4: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} @@ -2464,6 +2781,17 @@ packages: outvariant@1.4.3: resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + pad-right@0.2.2: + resolution: {integrity: sha512-4cy8M95ioIGolCoMmm2cMntGR1lPLEbOMzOKu8bzjuJP6JpzEMQcDHmh7hHLYGgob+nKe1YHFMaG4V59HQa89g==} + engines: {node: '>=0.10.0'} + + parse-json@8.3.0: + resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} + engines: {node: '>=18'} + parse5@8.0.0: resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==} @@ -2471,6 +2799,14 @@ packages: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-scurry@2.0.1: + resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} + engines: {node: 20 || >=22} + path-to-regexp@0.1.12: resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} @@ -2505,6 +2841,16 @@ packages: pkg-types@2.3.0: resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + playwright-core@1.56.1: + resolution: {integrity: sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.56.1: + resolution: {integrity: sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==} + engines: {node: '>=18'} + hasBin: true + postcss@8.4.31: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} @@ -2517,6 +2863,13 @@ packages: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + + property-expr@2.0.6: + resolution: {integrity: sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -2594,6 +2947,14 @@ packages: resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==} engines: {node: '>=0.10.0'} + read-package-up@11.0.0: + resolution: {integrity: sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==} + engines: {node: '>=18'} + + read-pkg@9.0.1: + resolution: {integrity: sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==} + engines: {node: '>=18'} + readdirp@4.1.2: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} @@ -2602,6 +2963,20 @@ packages: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} + reflect-metadata@0.2.2: + resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} + + regexp-match-indices@1.0.2: + resolution: {integrity: sha512-DwZuAkt8NF5mKwGGER1EGh2PRqyvhRhhLviH+R8y8dIuaQROlUfXjt4s9ZTXstIsSkptf06BSvwcEmmfheJJWQ==} + + regexp-tree@0.1.27: + resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} + hasBin: true + + repeat-string@1.6.1: + resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} + engines: {node: '>=0.10'} + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -2651,6 +3026,9 @@ packages: scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + seed-random@2.2.0: + resolution: {integrity: sha512-34EQV6AAHQGhoc0tn/96a9Fsi6v2xdqe/dMUwljGRaFOzR3EgRmECvD0O8vi8X+/uQ50LGHfkNu/Eue5TPKZkQ==} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -2683,6 +3061,14 @@ packages: 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'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + shell-quote@1.8.3: resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} engines: {node: '>= 0.4'} @@ -2724,12 +3110,34 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + spdx-correct@3.2.0: + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} + + spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + + spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + + spdx-license-ids@3.0.22: + resolution: {integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==} + sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + stackframe@1.3.4: + resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} + statuses@1.5.0: resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} engines: {node: '>= 0.6'} @@ -2748,6 +3156,10 @@ packages: strict-event-emitter@0.5.1: resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} + string-argv@0.3.1: + resolution: {integrity: sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==} + engines: {node: '>=0.6.19'} + string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} @@ -2756,6 +3168,10 @@ packages: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + string-width@7.2.0: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} @@ -2810,6 +3226,16 @@ packages: resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} engines: {node: '>=6'} + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + tiny-case@1.0.3: + resolution: {integrity: sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -2843,6 +3269,9 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + toposort@2.0.2: + resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==} + tough-cookie@6.0.0: resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==} engines: {node: '>=16'} @@ -2855,6 +3284,24 @@ packages: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true + ts-dedent@2.2.0: + resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} + engines: {node: '>=6.10'} + + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + tsconfck@3.1.6: resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} engines: {node: ^18 || >=20} @@ -2877,6 +3324,10 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + type-fest@2.19.0: + resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} + engines: {node: '>=12.20'} + type-fest@4.41.0: resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} engines: {node: '>=16'} @@ -2897,6 +3348,10 @@ packages: undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + unicorn-magic@0.1.0: + resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} + engines: {node: '>=18'} + unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} @@ -2910,6 +3365,9 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + upper-case-first@2.0.2: + resolution: {integrity: sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==} + use-callback-ref@1.3.3: resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} engines: {node: '>=10'} @@ -2935,10 +3393,31 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + util-arity@1.1.0: + resolution: {integrity: sha512-kkyIsXKwemfSy8ZEoaIz06ApApnWsk5hQO0vLjZS6UkBiGiW++Jsyb8vSBoc0WKlffGoGs5yYy/j5pp8zckrFA==} + utils-merge@1.0.1: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} + uuid@10.0.0: + resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} + hasBin: true + + uuid@11.0.5: + resolution: {integrity: sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==} + hasBin: true + + uuid@11.1.0: + resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} + hasBin: true + + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + + validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} @@ -3045,6 +3524,11 @@ packages: resolution: {integrity: sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==} engines: {node: '>=20'} + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} @@ -3058,6 +3542,10 @@ packages: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + wrap-ansi@9.0.2: resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} engines: {node: '>=18'} @@ -3082,6 +3570,10 @@ packages: resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} engines: {node: '>=18'} + xmlbuilder@15.1.1: + resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==} + engines: {node: '>=8.0'} + xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} @@ -3105,10 +3597,17 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + yoctocolors-cjs@2.1.3: resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} engines: {node: '>=18'} + yup@1.7.0: + resolution: {integrity: sha512-VJce62dBd+JQvoc+fCVq+KZfPHr+hXaxCcVgotfwWvlR0Ja3ffYKaJBT8rptPOSKOGJDCUnW2C2JWpud7aRP6Q==} + zod@4.1.12: resolution: {integrity: sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==} @@ -3159,7 +3658,7 @@ snapshots: '@babel/types': 7.28.5 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -3243,7 +3742,7 @@ snapshots: '@babel/parser': 7.28.5 '@babel/template': 7.27.2 '@babel/types': 7.28.5 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -3308,6 +3807,13 @@ snapshots: '@biomejs/cli-win32-x64@2.3.6': optional: true + '@colors/colors@1.5.0': + optional: true + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + '@csstools/color-helpers@5.1.0': {} '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': @@ -3330,6 +3836,138 @@ snapshots: '@csstools/css-tokenizer@3.0.4': {} + '@cucumber/ci-environment@10.0.1': {} + + '@cucumber/cucumber-expressions@18.0.1': + dependencies: + regexp-match-indices: 1.0.2 + + '@cucumber/cucumber@12.2.0': + dependencies: + '@cucumber/ci-environment': 10.0.1 + '@cucumber/cucumber-expressions': 18.0.1 + '@cucumber/gherkin': 34.0.0 + '@cucumber/gherkin-streams': 5.0.1(@cucumber/gherkin@34.0.0)(@cucumber/message-streams@4.0.1(@cucumber/messages@28.1.0))(@cucumber/messages@28.1.0) + '@cucumber/gherkin-utils': 9.2.0 + '@cucumber/html-formatter': 21.14.0(@cucumber/messages@28.1.0) + '@cucumber/junit-xml-formatter': 0.8.1(@cucumber/messages@28.1.0) + '@cucumber/message-streams': 4.0.1(@cucumber/messages@31.0.0) + '@cucumber/messages': 28.1.0 + '@cucumber/pretty-formatter': 1.0.1(@cucumber/cucumber@12.2.0)(@cucumber/messages@28.1.0) + '@cucumber/tag-expressions': 6.2.0 + assertion-error-formatter: 3.0.0 + capital-case: 1.0.4 + chalk: 4.1.2 + cli-table3: 0.6.5 + commander: 14.0.2 + debug: 4.4.3(supports-color@8.1.1) + error-stack-parser: 2.1.4 + figures: 3.2.0 + glob: 11.1.0 + has-ansi: 4.0.1 + indent-string: 4.0.0 + is-installed-globally: 0.4.0 + is-stream: 2.0.1 + knuth-shuffle-seeded: 1.0.6 + lodash.merge: 4.6.2 + lodash.mergewith: 4.6.2 + luxon: 3.7.1 + mime: 3.0.0 + mkdirp: 3.0.1 + mz: 2.7.0 + progress: 2.0.3 + read-package-up: 11.0.0 + semver: 7.7.2 + string-argv: 0.3.1 + supports-color: 8.1.1 + type-fest: 4.41.0 + util-arity: 1.1.0 + yaml: 2.8.1 + yup: 1.7.0 + + '@cucumber/gherkin-streams@5.0.1(@cucumber/gherkin@34.0.0)(@cucumber/message-streams@4.0.1(@cucumber/messages@28.1.0))(@cucumber/messages@28.1.0)': + dependencies: + '@cucumber/gherkin': 34.0.0 + '@cucumber/message-streams': 4.0.1(@cucumber/messages@31.0.0) + '@cucumber/messages': 28.1.0 + commander: 9.1.0 + source-map-support: 0.5.21 + + '@cucumber/gherkin-utils@9.2.0': + dependencies: + '@cucumber/gherkin': 31.0.0 + '@cucumber/messages': 27.2.0 + '@teppeis/multimaps': 3.0.0 + commander: 13.1.0 + source-map-support: 0.5.21 + + '@cucumber/gherkin@31.0.0': + dependencies: + '@cucumber/messages': 26.0.1 + + '@cucumber/gherkin@34.0.0': + dependencies: + '@cucumber/messages': 28.1.0 + + '@cucumber/html-formatter@21.14.0(@cucumber/messages@28.1.0)': + dependencies: + '@cucumber/messages': 28.1.0 + + '@cucumber/junit-xml-formatter@0.8.1(@cucumber/messages@28.1.0)': + dependencies: + '@cucumber/messages': 28.1.0 + '@cucumber/query': 13.6.0(@cucumber/messages@28.1.0) + '@teppeis/multimaps': 3.0.0 + luxon: 3.7.1 + xmlbuilder: 15.1.1 + + '@cucumber/message-streams@4.0.1(@cucumber/messages@31.0.0)': + dependencies: + '@cucumber/messages': 31.0.0 + + '@cucumber/messages@26.0.1': + dependencies: + '@types/uuid': 10.0.0 + class-transformer: 0.5.1 + reflect-metadata: 0.2.2 + uuid: 10.0.0 + + '@cucumber/messages@27.2.0': + dependencies: + '@types/uuid': 10.0.0 + class-transformer: 0.5.1 + reflect-metadata: 0.2.2 + uuid: 11.0.5 + + '@cucumber/messages@28.1.0': + dependencies: + '@types/uuid': 10.0.0 + class-transformer: 0.5.1 + reflect-metadata: 0.2.2 + uuid: 11.1.0 + + '@cucumber/messages@31.0.0': + dependencies: + class-transformer: 0.5.1 + reflect-metadata: 0.2.2 + + '@cucumber/pretty-formatter@1.0.1(@cucumber/cucumber@12.2.0)(@cucumber/messages@28.1.0)': + dependencies: + '@cucumber/cucumber': 12.2.0 + '@cucumber/messages': 28.1.0 + ansi-styles: 5.2.0 + cli-table3: 0.6.5 + figures: 3.2.0 + ts-dedent: 2.2.0 + + '@cucumber/query@13.6.0(@cucumber/messages@28.1.0)': + dependencies: + '@cucumber/messages': 28.1.0 + '@teppeis/multimaps': 3.0.0 + lodash.sortby: 4.7.0 + + '@cucumber/tag-expressions@6.2.0': {} + '@emnapi/runtime@1.7.0': dependencies: tslib: 2.8.1 @@ -3584,6 +4222,21 @@ snapshots: optionalDependencies: '@types/node': 24.10.1 + '@isaacs/balanced-match@4.0.1': {} + + '@isaacs/brace-expansion@5.0.0': + dependencies: + '@isaacs/balanced-match': 4.0.1 + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.2 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -3603,6 +4256,11 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + '@jsdevtools/ono@7.1.3': {} '@jsep-plugin/assignment@1.3.0(jsep@1.4.0)': @@ -3619,7 +4277,7 @@ snapshots: '@koa/router@14.0.0': dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) http-errors: 2.0.0 koa-compose: 4.1.0 path-to-regexp: 8.3.0 @@ -3682,6 +4340,10 @@ snapshots: '@open-draft/until@2.1.0': {} + '@playwright/test@1.56.1': + dependencies: + playwright: 1.56.1 + '@radix-ui/primitive@1.1.3': {} '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.6))(@types/react@19.2.6)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': @@ -4133,6 +4795,8 @@ snapshots: postcss: 8.5.6 tailwindcss: 4.1.17 + '@teppeis/multimaps@3.0.0': {} + '@testing-library/dom@10.4.1': dependencies: '@babel/code-frame': 7.27.1 @@ -4167,6 +4831,14 @@ snapshots: dependencies: '@testing-library/dom': 10.4.1 + '@tsconfig/node10@1.0.12': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + '@types/aria-query@5.0.4': {} '@types/babel__core@7.20.5': @@ -4205,6 +4877,8 @@ snapshots: dependencies: undici-types: 7.16.0 + '@types/normalize-package-data@2.4.4': {} + '@types/react-dom@19.2.3(@types/react@19.2.6)': dependencies: '@types/react': 19.2.6 @@ -4215,6 +4889,8 @@ snapshots: '@types/statuses@2.0.6': {} + '@types/uuid@10.0.0': {} + '@vitejs/plugin-react@5.1.1(vite@7.2.2(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.1))': dependencies: '@babel/core': 7.28.5 @@ -4272,6 +4948,12 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 + acorn-walk@8.3.4: + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + agent-base@7.1.4: {} ajv-formats@3.0.1(ajv@8.17.1): @@ -4291,6 +4973,8 @@ snapshots: dependencies: environment: 1.1.0 + ansi-regex@4.1.1: {} + ansi-regex@5.0.1: {} ansi-regex@6.2.2: {} @@ -4303,6 +4987,10 @@ snapshots: ansi-styles@6.2.3: {} + any-promise@1.3.0: {} + + arg@4.1.3: {} + argparse@1.0.10: dependencies: sprintf-js: 1.0.3 @@ -4319,6 +5007,12 @@ snapshots: array-flatten@1.1.1: {} + assertion-error-formatter@3.0.0: + dependencies: + diff: 4.0.2 + pad-right: 0.2.2 + repeat-string: 1.6.1 + assertion-error@2.0.1: {} babel-plugin-react-compiler@1.0.0: @@ -4327,7 +5021,7 @@ snapshots: baseline-browser-mapping@2.8.26: {} - better-auth@1.4.0-beta.25(next@16.0.3(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + better-auth@1.4.0-beta.25(next@16.0.3(@babel/core@7.28.5)(@playwright/test@1.56.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: '@better-auth/core': 1.4.0-beta.25(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.18)(better-call@1.0.28)(jose@6.1.2)(kysely@0.28.8)(nanostores@1.0.1) '@better-auth/telemetry': 1.4.0-beta.25(@better-auth/core@1.4.0-beta.25(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.18)(better-call@1.0.28)(jose@6.1.2)(kysely@0.28.8)(nanostores@1.0.1)) @@ -4343,7 +5037,7 @@ snapshots: nanostores: 1.0.1 zod: 4.1.12 optionalDependencies: - next: 16.0.3(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + next: 16.0.3(@babel/core@7.28.5)(@playwright/test@1.56.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) react: 19.2.0 react-dom: 19.2.0(react@19.2.0) @@ -4387,6 +5081,8 @@ snapshots: node-releases: 2.0.27 update-browserslist-db: 1.1.4(browserslist@4.28.0) + buffer-from@1.1.2: {} + bundle-name@4.1.0: dependencies: run-applescript: 7.1.0 @@ -4422,6 +5118,12 @@ snapshots: caniuse-lite@1.0.30001754: {} + capital-case@1.0.4: + dependencies: + no-case: 3.0.4 + tslib: 2.8.1 + upper-case-first: 2.0.2 + chai@6.2.1: {} chalk@4.1.2: @@ -4437,6 +5139,8 @@ snapshots: dependencies: consola: 3.4.2 + class-transformer@0.5.1: {} + class-variance-authority@0.7.1: dependencies: clsx: 2.1.1 @@ -4445,6 +5149,12 @@ snapshots: dependencies: restore-cursor: 5.1.0 + cli-table3@0.6.5: + dependencies: + string-width: 4.2.3 + optionalDependencies: + '@colors/colors': 1.5.0 + cli-truncate@5.1.1: dependencies: slice-ansi: 7.1.2 @@ -4472,10 +5182,14 @@ snapshots: colorette@2.0.20: {} + commander@13.1.0: {} + commander@14.0.1: {} commander@14.0.2: {} + commander@9.1.0: {} + concurrently@9.2.1: dependencies: chalk: 4.1.2 @@ -4508,6 +5222,14 @@ snapshots: depd: 2.0.0 keygrip: 1.1.0 + create-require@1.1.1: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + css-tree@3.1.0: dependencies: mdn-data: 2.12.2 @@ -4532,9 +5254,11 @@ snapshots: dependencies: ms: 2.0.0 - debug@4.4.3: + debug@4.4.3(supports-color@8.1.1): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 8.1.1 decimal.js@10.6.0: {} @@ -4567,6 +5291,8 @@ snapshots: detect-node-es@1.1.0: {} + diff@4.0.2: {} + dom-accessibility-api@0.5.16: {} dom-accessibility-api@0.6.3: {} @@ -4579,6 +5305,8 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + eastasianwidth@0.2.0: {} + ee-first@1.1.1: {} electron-to-chromium@1.5.250: {} @@ -4587,6 +5315,8 @@ snapshots: emoji-regex@8.0.0: {} + emoji-regex@9.2.2: {} + encodeurl@1.0.2: {} encodeurl@2.0.0: {} @@ -4600,6 +5330,10 @@ snapshots: environment@1.1.0: {} + error-stack-parser@2.1.4: + dependencies: + stackframe: 1.3.4 + es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -4643,6 +5377,8 @@ snapshots: escape-html@1.0.3: {} + escape-string-regexp@1.0.5: {} + esprima@4.0.1: {} estree-walker@3.0.3: @@ -4703,6 +5439,10 @@ snapshots: optionalDependencies: picomatch: 4.0.3 + figures@3.2.0: + dependencies: + escape-string-regexp: 1.0.5 + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -4719,12 +5459,22 @@ snapshots: transitivePeerDependencies: - supports-color + find-up-simple@1.0.1: {} + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + format-util@1.0.5: {} forwarded@0.2.0: {} fresh@0.5.2: {} + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -4769,6 +5519,19 @@ snapshots: nypm: 0.6.2 pathe: 2.0.3 + glob@11.1.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 4.1.1 + minimatch: 10.1.1 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 2.0.1 + + global-dirs@3.0.1: + dependencies: + ini: 2.0.0 + globrex@0.1.2: {} gopd@1.2.0: {} @@ -4777,6 +5540,10 @@ snapshots: graphql@16.12.0: {} + has-ansi@4.0.1: + dependencies: + ansi-regex: 4.1.1 + has-flag@4.0.0: {} has-symbols@1.1.0: {} @@ -4787,6 +5554,10 @@ snapshots: headers-polyfill@4.0.3: {} + hosted-git-info@7.0.2: + dependencies: + lru-cache: 10.4.3 + html-encoding-sniffer@4.0.0: dependencies: whatwg-encoding: 3.1.1 @@ -4815,14 +5586,14 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.4 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.4 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -4842,8 +5613,12 @@ snapshots: indent-string@4.0.0: {} + index-to-position@1.2.0: {} + inherits@2.0.4: {} + ini@2.0.0: {} + ipaddr.js@1.9.1: {} is-docker@3.0.0: {} @@ -4858,16 +5633,31 @@ snapshots: dependencies: is-docker: 3.0.0 + is-installed-globally@0.4.0: + dependencies: + global-dirs: 3.0.1 + is-path-inside: 3.0.3 + is-node-process@1.2.0: {} is-number@7.0.0: {} + is-path-inside@3.0.3: {} + is-potential-custom-element-name@1.0.1: {} + is-stream@2.0.1: {} + is-wsl@3.1.0: dependencies: is-inside-container: 1.0.0 + isexe@2.0.0: {} + + jackspeak@4.1.1: + dependencies: + '@isaacs/cliui': 8.0.2 + jiti@2.6.1: {} jose@6.1.2: {} @@ -4939,6 +5729,10 @@ snapshots: dependencies: tsscmp: 1.0.6 + knuth-shuffle-seeded@1.0.6: + dependencies: + seed-random: 2.2.0 + koa-compose@4.1.0: {} koa@3.1.1: @@ -5032,6 +5826,12 @@ snapshots: rfdc: 1.4.1 wrap-ansi: 9.0.2 + lodash.merge@4.6.2: {} + + lodash.mergewith@4.6.2: {} + + lodash.sortby@4.7.0: {} + lodash@4.17.21: {} log-update@6.1.0: @@ -5042,6 +5842,12 @@ snapshots: strip-ansi: 7.1.2 wrap-ansi: 9.0.2 + lower-case@2.0.2: + dependencies: + tslib: 2.8.1 + + lru-cache@10.4.3: {} + lru-cache@11.2.2: {} lru-cache@5.1.1: @@ -5052,12 +5858,16 @@ snapshots: dependencies: react: 19.2.0 + luxon@3.7.1: {} + lz-string@1.5.0: {} magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + make-error@1.3.6: {} + math-intrinsics@1.1.0: {} mdn-data@2.12.2: {} @@ -5089,10 +5899,20 @@ snapshots: mime@1.6.0: {} + mime@3.0.0: {} + mimic-function@5.0.1: {} min-indent@1.0.1: {} + minimatch@10.1.1: + dependencies: + '@isaacs/brace-expansion': 5.0.0 + + minipass@7.1.2: {} + + mkdirp@3.0.1: {} + ms@2.0.0: {} ms@2.1.3: {} @@ -5124,6 +5944,12 @@ snapshots: mute-stream@2.0.0: {} + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + nano-spawn@2.0.0: {} nanoid@3.3.11: {} @@ -5134,7 +5960,7 @@ snapshots: negotiator@0.6.3: {} - next@16.0.3(@babel/core@7.28.5)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + next@16.0.3(@babel/core@7.28.5)(@playwright/test@1.56.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: '@next/env': 16.0.3 '@swc/helpers': 0.5.15 @@ -5152,16 +5978,28 @@ snapshots: '@next/swc-linux-x64-musl': 16.0.3 '@next/swc-win32-arm64-msvc': 16.0.3 '@next/swc-win32-x64-msvc': 16.0.3 + '@playwright/test': 1.56.1 babel-plugin-react-compiler: 1.0.0 sharp: 0.34.5 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros + no-case@3.0.4: + dependencies: + lower-case: 2.0.2 + tslib: 2.8.1 + node-fetch-native@1.6.7: {} node-releases@2.0.27: {} + normalize-package-data@6.0.2: + dependencies: + hosted-git-info: 7.0.2 + semver: 7.7.3 + validate-npm-package-license: 3.0.4 + nypm@0.6.2: dependencies: citty: 0.1.6 @@ -5170,6 +6008,8 @@ snapshots: pkg-types: 2.3.0 tinyexec: 1.0.2 + object-assign@4.1.1: {} + object-inspect@1.13.4: {} ohash@2.0.11: {} @@ -5178,7 +6018,7 @@ snapshots: dependencies: '@koa/cors': 5.0.0 '@koa/router': 14.0.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) eta: 4.4.1 jose: 6.1.2 jsesc: 3.1.0 @@ -5210,12 +6050,31 @@ snapshots: outvariant@1.4.3: {} + package-json-from-dist@1.0.1: {} + + pad-right@0.2.2: + dependencies: + repeat-string: 1.6.1 + + parse-json@8.3.0: + dependencies: + '@babel/code-frame': 7.27.1 + index-to-position: 1.2.0 + type-fest: 4.41.0 + parse5@8.0.0: dependencies: entities: 6.0.1 parseurl@1.3.3: {} + path-key@3.1.1: {} + + path-scurry@2.0.1: + dependencies: + lru-cache: 11.2.2 + minipass: 7.1.2 + path-to-regexp@0.1.12: {} path-to-regexp@6.3.0: {} @@ -5240,6 +6099,14 @@ snapshots: exsolve: 1.0.8 pathe: 2.0.3 + playwright-core@1.56.1: {} + + playwright@1.56.1: + dependencies: + playwright-core: 1.56.1 + optionalDependencies: + fsevents: 2.3.2 + postcss@8.4.31: dependencies: nanoid: 3.3.11 @@ -5258,6 +6125,10 @@ snapshots: ansi-styles: 5.2.0 react-is: 17.0.2 + progress@2.0.3: {} + + property-expr@2.0.6: {} + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -5330,6 +6201,20 @@ snapshots: react@19.2.0: {} + read-package-up@11.0.0: + dependencies: + find-up-simple: 1.0.1 + read-pkg: 9.0.1 + type-fest: 4.41.0 + + read-pkg@9.0.1: + dependencies: + '@types/normalize-package-data': 2.4.4 + normalize-package-data: 6.0.2 + parse-json: 8.3.0 + type-fest: 4.41.0 + unicorn-magic: 0.1.0 + readdirp@4.1.2: {} redent@3.0.0: @@ -5337,6 +6222,16 @@ snapshots: indent-string: 4.0.0 strip-indent: 3.0.0 + reflect-metadata@0.2.2: {} + + regexp-match-indices@1.0.2: + dependencies: + regexp-tree: 0.1.27 + + regexp-tree@0.1.27: {} + + repeat-string@1.6.1: {} + require-directory@2.1.1: {} require-from-string@2.0.2: {} @@ -5398,12 +6293,13 @@ snapshots: scheduler@0.27.0: {} + seed-random@2.2.0: {} + semver@6.3.1: {} semver@7.7.2: {} - semver@7.7.3: - optional: true + semver@7.7.3: {} send@0.19.0: dependencies: @@ -5468,6 +6364,12 @@ snapshots: '@img/sharp-win32-x64': 0.34.5 optional: true + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + shell-quote@1.8.3: {} side-channel-list@1.0.0: @@ -5514,10 +6416,33 @@ snapshots: source-map-js@1.2.1: {} + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + spdx-correct@3.2.0: + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.22 + + spdx-exceptions@2.5.0: {} + + spdx-expression-parse@3.0.1: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.22 + + spdx-license-ids@3.0.22: {} + sprintf-js@1.0.3: {} stackback@0.0.2: {} + stackframe@1.3.4: {} + statuses@1.5.0: {} statuses@2.0.1: {} @@ -5528,6 +6453,8 @@ snapshots: strict-event-emitter@0.5.1: {} + string-argv@0.3.1: {} + string-argv@0.3.2: {} string-width@4.2.3: @@ -5536,6 +6463,12 @@ snapshots: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.2 + string-width@7.2.0: dependencies: emoji-regex: 10.6.0 @@ -5582,6 +6515,16 @@ snapshots: tapable@2.3.0: {} + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + tiny-case@1.0.3: {} + tinybench@2.9.0: {} tinyexec@0.3.2: {} @@ -5607,6 +6550,8 @@ snapshots: toidentifier@1.0.1: {} + toposort@2.0.2: {} + tough-cookie@6.0.0: dependencies: tldts: 7.0.17 @@ -5617,6 +6562,26 @@ snapshots: tree-kill@1.2.2: {} + ts-dedent@2.2.0: {} + + ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.12 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 24.10.1 + acorn: 8.15.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.9.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + tsconfck@3.1.6(typescript@5.9.3): optionalDependencies: typescript: 5.9.3 @@ -5632,6 +6597,8 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + type-fest@2.19.0: {} + type-fest@4.41.0: {} type-is@1.6.18: @@ -5649,6 +6616,8 @@ snapshots: undici-types@7.16.0: {} + unicorn-magic@0.1.0: {} + unpipe@1.0.0: {} until-async@3.0.2: {} @@ -5659,6 +6628,10 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + upper-case-first@2.0.2: + dependencies: + tslib: 2.8.1 + use-callback-ref@1.3.3(@types/react@19.2.6)(react@19.2.0): dependencies: react: 19.2.0 @@ -5678,13 +6651,28 @@ snapshots: dependencies: react: 19.2.0 + util-arity@1.1.0: {} + utils-merge@1.0.1: {} + uuid@10.0.0: {} + + uuid@11.0.5: {} + + uuid@11.1.0: {} + + v8-compile-cache-lib@3.0.1: {} + + validate-npm-package-license@3.0.4: + dependencies: + spdx-correct: 3.2.0 + spdx-expression-parse: 3.0.1 + vary@1.1.2: {} vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.2.2(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.1)): dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.9.3) optionalDependencies: @@ -5718,7 +6706,7 @@ snapshots: '@vitest/snapshot': 4.0.10 '@vitest/spy': 4.0.10 '@vitest/utils': 4.0.10 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) es-module-lexer: 1.7.0 expect-type: 1.2.2 magic-string: 0.30.21 @@ -5765,6 +6753,10 @@ snapshots: tr46: 6.0.0 webidl-conversions: 8.0.0 + which@2.0.2: + dependencies: + isexe: 2.0.0 + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 @@ -5782,6 +6774,12 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.1.2 + wrap-ansi@9.0.2: dependencies: ansi-styles: 6.2.3 @@ -5796,6 +6794,8 @@ snapshots: xml-name-validator@5.0.0: {} + xmlbuilder@15.1.1: {} + xmlchars@2.2.0: {} y18n@5.0.8: {} @@ -5816,6 +6816,15 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 + yn@3.1.1: {} + yoctocolors-cjs@2.1.3: {} + yup@1.7.0: + dependencies: + property-expr: 2.0.6 + tiny-case: 1.0.3 + toposort: 2.0.2 + type-fest: 2.19.0 + zod@4.1.12: {} diff --git a/tests/bdd/features/login.feature b/tests/bdd/features/login.feature new file mode 100644 index 00000000..734fb06b --- /dev/null +++ b/tests/bdd/features/login.feature @@ -0,0 +1,8 @@ +Feature: Login flow + + Scenario: Sign in and land on Catalog + Given I am on "/signin" + When I click the "Okta" button + Then I should be on "/catalog" + And I should see a heading "MCP Server Catalog" + diff --git a/tests/bdd/steps/global.steps.ts b/tests/bdd/steps/global.steps.ts new file mode 100644 index 00000000..d88ac6d9 --- /dev/null +++ b/tests/bdd/steps/global.steps.ts @@ -0,0 +1,41 @@ +import { Given, Then, When } from "@cucumber/cucumber"; +import { expect } from "@playwright/test"; +import type { PlaywrightWorld } from "../support/world"; + +Given("I am on {string}", async function (this: PlaywrightWorld, path: string) { + await this.page.goto(`${this.baseUrl}${path}`); +}); + +When( + "I click the {string} button", + async function (this: PlaywrightWorld, label: string) { + await this.page.getByRole("button", { name: label }).click(); + }, +); + +Then( + "I should see the text {string}", + async function (this: PlaywrightWorld, text: string) { + await expect(this.page.getByText(text)).toBeVisible(); + }, +); + +Then( + "I should see a heading {string}", + async function (this: PlaywrightWorld, heading: string) { + await expect( + this.page.getByRole("heading", { name: heading }), + ).toBeVisible(); + }, +); + +Then( + "I should be on {string}", + async function (this: PlaywrightWorld, path: string) { + await expect(this.page).toHaveURL( + new RegExp( + `${this.baseUrl}${path.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&")}$`, + ), + ); + }, +); diff --git a/tests/bdd/support/hooks.ts b/tests/bdd/support/hooks.ts new file mode 100644 index 00000000..776de407 --- /dev/null +++ b/tests/bdd/support/hooks.ts @@ -0,0 +1,27 @@ +import { After, Before, setDefaultTimeout } from "@cucumber/cucumber"; +import { + type Browser, + type BrowserContext, + chromium, + type Page, +} from "@playwright/test"; +import type { PlaywrightWorld } from "./world"; + +let browser: Browser | undefined; + +setDefaultTimeout(60 * 1000); // 60s per step + +Before(async function (this: PlaywrightWorld) { + if (!browser) { + browser = await chromium.launch({ headless: true }); + } + const context: BrowserContext = await browser.newContext(); + const page: Page = await context.newPage(); + this.context = context; + this.page = page; +}); + +After(async function (this: PlaywrightWorld) { + if (this.page) await this.page.close(); + if (this.context) await this.context.close(); +}); diff --git a/tests/bdd/support/world.ts b/tests/bdd/support/world.ts new file mode 100644 index 00000000..d9559dce --- /dev/null +++ b/tests/bdd/support/world.ts @@ -0,0 +1,14 @@ +import { setWorldConstructor, type World } from "@cucumber/cucumber"; +import type { BrowserContext, Page } from "@playwright/test"; + +export class PlaywrightWorld implements World { + page!: Page; + context!: BrowserContext; + baseUrl: string; + + constructor() { + this.baseUrl = process.env.BASE_URL || "http://localhost:3000"; + } +} + +setWorldConstructor(PlaywrightWorld); diff --git a/tests/bdd/ts-node.js b/tests/bdd/ts-node.js new file mode 100644 index 00000000..e52b79d0 --- /dev/null +++ b/tests/bdd/ts-node.js @@ -0,0 +1,7 @@ +// Local ts-node registration for Cucumber with CommonJS output +require("ts-node").register({ + transpileOnly: true, + compilerOptions: { + module: "commonjs", + }, +}); diff --git a/tsconfig.json b/tsconfig.json index f55bef4e..c5718250 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -31,5 +31,5 @@ "**/*.mts", ".next/dev/types/**/*.ts" ], - "exclude": ["node_modules", ".next/dev/types/**"] + "exclude": ["node_modules", ".next/dev/types/**", "tests/bdd/**"] } From ce5a53d943f85ff43441277d08de47eb282385fe Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Fri, 21 Nov 2025 16:44:26 +0100 Subject: [PATCH 02/31] add inspector mode --- package.json | 1 + tests/bdd/support/hooks.ts | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index eba60530..4b305ee6 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "format": "biome format --write", "test": "vitest", "test:bdd": "cucumber-js", + "test:bdd:debug": "PWDEBUG=1 cucumber-js", "type-check": "tsc --noEmit", "prepare": "husky", "oidc": "node dev-auth/oidc-provider.mjs", diff --git a/tests/bdd/support/hooks.ts b/tests/bdd/support/hooks.ts index 776de407..e78c4866 100644 --- a/tests/bdd/support/hooks.ts +++ b/tests/bdd/support/hooks.ts @@ -12,13 +12,21 @@ let browser: Browser | undefined; setDefaultTimeout(60 * 1000); // 60s per step Before(async function (this: PlaywrightWorld) { + const isDebug = !!process.env.PWDEBUG; if (!browser) { - browser = await chromium.launch({ headless: true }); + browser = await chromium.launch({ + headless: !isDebug, + slowMo: isDebug ? 100 : 0, + }); } const context: BrowserContext = await browser.newContext(); const page: Page = await context.newPage(); this.context = context; this.page = page; + + if (isDebug) { + await this.page.pause(); + } }); After(async function (this: PlaywrightWorld) { From 8a2da1f83b5c979590b2e7e543798e714f4c252f Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Fri, 21 Nov 2025 17:40:06 +0100 Subject: [PATCH 03/31] add tracing mode --- .github/workflows/bdd.yml | 52 ++++++++++++++++++++++++++++++++++++++ .gitignore | 4 +++ CLAUDE.md | 24 +++++++++++++++--- README.md | 26 ++++++++++++++++++- package.json | 1 + tests/bdd/support/hooks.ts | 22 ++++++++++++++-- 6 files changed, 122 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/bdd.yml diff --git a/.github/workflows/bdd.yml b/.github/workflows/bdd.yml new file mode 100644 index 00000000..9cdd4853 --- /dev/null +++ b/.github/workflows/bdd.yml @@ -0,0 +1,52 @@ +name: BDD E2E + +on: + push: + branches: ["**"] + pull_request: + branches: ["**"] + +permissions: + contents: read + +jobs: + e2e: + name: Run BDD (Cucumber + Playwright) + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Checkout + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + + - name: Setup + uses: ./.github/actions/setup + + - name: Install Playwright browsers + run: pnpm exec playwright install --with-deps + + - name: Start dev stack (Next + OIDC + Mock) + run: | + pnpm dev & + echo $! > dev.pid + # Wait for Next (3000), OIDC (4000), and Mock API (9090) + for url in http://localhost:3000 http://localhost:4000 http://localhost:9090; do + echo "Waiting for $url..." + for i in {1..60}; do + if curl -sf "$url" >/dev/null; then + echo "$url is up" + break + fi + sleep 2 + done + done + + - name: Run BDD tests + env: + BASE_URL: http://localhost:3000 + run: pnpm run test:bdd + + - name: Cleanup dev + if: always() + run: | + if [ -f dev.pid ]; then kill $(cat dev.pid) || true; fi + diff --git a/.gitignore b/.gitignore index 5eb4b534..903226dd 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,10 @@ # testing /coverage +# Playwright/Cucumber E2E artifacts +test-results/ +playwright-report/ +blob-report/ # next.js /.next/ diff --git a/CLAUDE.md b/CLAUDE.md index f834b31c..96372181 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -155,7 +155,7 @@ pnpm generate-client:nofetch # Regenerate without fetching ### Backend API -- **Base URL**: Configured via `NEXT_PUBLIC_API_URL` +- **Base URL**: Configured via `API_BASE_URL` (server-side only) - **Format**: Official MCP Registry API (upstream compatible) - **Endpoints**: - `GET /api/v0/servers` - List all MCP servers @@ -178,7 +178,7 @@ pnpm generate-client:nofetch # Regenerate without fetching ### Production 1. User accesses protected route -2. Redirected to `/sign-in` +2. Redirected to `/signin` 3. Better Auth initiates OIDC flow with configured provider 4. Provider redirects back with authorization code 5. Better Auth exchanges code for tokens @@ -218,6 +218,19 @@ pnpm generate-client:nofetch # Regenerate without fetching - **Testing Library** - Component testing - **jsdom** - DOM simulation +### BDD E2E (Cucumber + Playwright) + +- End-to-end scenarios live under `tests/bdd` and run against a live dev stack. +- Commands: + - `pnpm dev` – starts Next.js (3000), mock OIDC (4000), and MSW mock API (9090) + - `pnpm run test:bdd` – runs Cucumber scenarios with Playwright (headless) + - `pnpm run test:bdd:debug` – runs with Playwright Inspector (headed, pauses on start) + - `pnpm run test:bdd:trace` – runs with Playwright tracing, artifacts in `test-results/traces/*.zip` +- CI runs BDD tests via `.github/workflows/bdd.yml` and installs Playwright browsers. +- Install browsers locally once: `pnpm exec playwright install` + +Scaffolded global steps include navigation, clicking buttons by accessible name, URL assertions, and text/heading checks. Prefer reusing global steps; add domain-specific ones only when needed for clarity. + ### Example Test ```typescript @@ -274,7 +287,10 @@ git push origin v0.x.x ### Authentication Not Working -- **Development**: Ensure OIDC mock is running (`pnpm oidc`) +- **Development**: + - Ensure OIDC mock is running (`pnpm oidc`) or start the full stack with `pnpm dev` + - Dev provider issues refresh tokens unconditionally and uses a short AccessToken TTL (15s) to exercise the refresh flow + - If you see origin errors (403), ensure `BETTER_AUTH_URL` matches the port you use (default `http://localhost:3000`) or include it in `TRUSTED_ORIGINS` - **Production**: Check environment variables: - `OIDC_ISSUER_URL` - OIDC provider URL - `OIDC_CLIENT_ID` - OAuth2 client ID @@ -285,7 +301,7 @@ git push origin v0.x.x ### API Calls Failing -- Check `NEXT_PUBLIC_API_URL` environment variable +- Check `API_BASE_URL` environment variable - Verify backend API is running - Check browser console for CORS errors diff --git a/README.md b/README.md index aa739679..4253bea6 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ bun dev Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +Authentication: the dev stack also starts a local OIDC provider (on :4000) and MSW mock API (on :9090). The `/signin` page initiates the OIDC flow and redirects back to `/catalog` on success. This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. @@ -109,6 +109,30 @@ To learn more about Next.js, take a look at the following resources: You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! +## Testing + +### Unit/Component + +```bash +pnpm test # Vitest +pnpm type-check # TypeScript +pnpm lint # Biome +``` + +### BDD E2E (Cucumber + Playwright) + +Run the app and E2E tests locally: + +```bash +pnpm exec playwright install # one-time browser install +pnpm dev # start Next (3000) + OIDC (4000) + Mock API (9090) +pnpm run test:bdd # run Cucumber scenarios (headless) +pnpm run test:bdd:debug # headed with Playwright Inspector (PWDEBUG=1) +pnpm run test:bdd:trace # capture Playwright traces (PWTRACE=1) +``` + +CI runs the E2E suite via `.github/workflows/bdd.yml`. + ## Deploy on Vercel The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. diff --git a/package.json b/package.json index 4b305ee6..45aa3a68 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "format": "biome format --write", "test": "vitest", "test:bdd": "cucumber-js", + "test:bdd:trace": "PWTRACE=1 cucumber-js", "test:bdd:debug": "PWDEBUG=1 cucumber-js", "type-check": "tsc --noEmit", "prepare": "husky", diff --git a/tests/bdd/support/hooks.ts b/tests/bdd/support/hooks.ts index e78c4866..f7d5e782 100644 --- a/tests/bdd/support/hooks.ts +++ b/tests/bdd/support/hooks.ts @@ -1,4 +1,9 @@ -import { After, Before, setDefaultTimeout } from "@cucumber/cucumber"; +import { + After, + Before, + type ITestCaseHookParameter, + setDefaultTimeout, +} from "@cucumber/cucumber"; import { type Browser, type BrowserContext, @@ -8,6 +13,7 @@ import { import type { PlaywrightWorld } from "./world"; let browser: Browser | undefined; +const TRACE_ENABLED = process.env.PWTRACE === "1"; setDefaultTimeout(60 * 1000); // 60s per step @@ -24,12 +30,24 @@ Before(async function (this: PlaywrightWorld) { this.context = context; this.page = page; + if (TRACE_ENABLED) { + await this.context.tracing.start({ screenshots: true, snapshots: true }); + } + if (isDebug) { await this.page.pause(); } }); -After(async function (this: PlaywrightWorld) { +After(async function (this: PlaywrightWorld, scenario: ITestCaseHookParameter) { + if (TRACE_ENABLED && this.context) { + const safeName = scenario.pickle.name.replace(/[^a-z0-9-]+/gi, "_"); + const { mkdir } = await import("node:fs/promises"); + await mkdir("test-results/traces", { recursive: true }); + await this.context.tracing.stop({ + path: `test-results/traces/${safeName}.zip`, + }); + } if (this.page) await this.page.close(); if (this.context) await this.context.close(); }); From a3c2cd7dcd9e091c52a831538fb58cf8a3a0293e Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Fri, 21 Nov 2025 18:18:44 +0100 Subject: [PATCH 04/31] automate all aria roles for cucumber tests --- cucumber.js | 2 ++ package.json | 1 + pnpm-lock.yaml | 11 +++++++- tests/bdd/steps/global.steps.ts | 11 +++++++- tests/bdd/support/parameter-types.ts | 38 ++++++++++++++++++++++++++++ tests/bdd/support/roles.ts | 15 +++++++++++ 6 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 tests/bdd/support/parameter-types.ts create mode 100644 tests/bdd/support/roles.ts diff --git a/cucumber.js b/cucumber.js index d4db2fc4..2d1ece17 100644 --- a/cucumber.js +++ b/cucumber.js @@ -4,6 +4,8 @@ module.exports = { require: [ "tests/bdd/support/world.ts", "tests/bdd/support/hooks.ts", + "tests/bdd/support/roles.ts", + "tests/bdd/support/parameter-types.ts", "tests/bdd/steps/**/*.ts", ], paths: ["tests/bdd/features/**/*.feature"], diff --git a/package.json b/package.json index 45aa3a68..dba23cc8 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "@types/react": "^19", "@types/react-dom": "^19", "@vitejs/plugin-react": "^5.1.1", + "aria-query": "^5.3.2", "babel-plugin-react-compiler": "1.0.0", "concurrently": "^9.2.1", "dotenv": "^17.2.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6c2172fb..baf80d33 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -114,6 +114,9 @@ importers: '@vitejs/plugin-react': specifier: ^5.1.1 version: 5.1.1(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.1)) + aria-query: + specifier: ^5.3.2 + version: 5.3.2 babel-plugin-react-compiler: specifier: 1.0.0 version: 1.0.0 @@ -1713,6 +1716,10 @@ packages: aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + array-flatten@1.1.1: resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} @@ -4814,7 +4821,7 @@ snapshots: '@testing-library/jest-dom@6.9.1': dependencies: '@adobe/css-tools': 4.4.4 - aria-query: 5.3.0 + aria-query: 5.3.2 css.escape: 1.5.1 dom-accessibility-api: 0.6.3 picocolors: 1.1.1 @@ -5008,6 +5015,8 @@ snapshots: dependencies: dequal: 2.0.3 + aria-query@5.3.2: {} + array-flatten@1.1.1: {} assertion-error-formatter@3.0.0: diff --git a/tests/bdd/steps/global.steps.ts b/tests/bdd/steps/global.steps.ts index d88ac6d9..81f508cc 100644 --- a/tests/bdd/steps/global.steps.ts +++ b/tests/bdd/steps/global.steps.ts @@ -1,11 +1,20 @@ import { Given, Then, When } from "@cucumber/cucumber"; -import { expect } from "@playwright/test"; +import { type AriaRole, expect } from "@playwright/test"; import type { PlaywrightWorld } from "../support/world"; Given("I am on {string}", async function (this: PlaywrightWorld, path: string) { await this.page.goto(`${this.baseUrl}${path}`); }); +// Generic click step using a custom {role} parameter type +When( + 'I click the "{string}" {role}', + async function (this: PlaywrightWorld, label: string, role: AriaRole) { + await this.page.getByRole(role, { name: label }).click(); + }, +); + +// Backward-compatible convenience wrapper for "button" steps When( "I click the {string} button", async function (this: PlaywrightWorld, label: string) { diff --git a/tests/bdd/support/parameter-types.ts b/tests/bdd/support/parameter-types.ts new file mode 100644 index 00000000..2ee1453e --- /dev/null +++ b/tests/bdd/support/parameter-types.ts @@ -0,0 +1,38 @@ +import { defineParameterType } from "@cucumber/cucumber"; +import type { AriaRole } from "@playwright/test"; +import { roleAliases, validAriaRoles } from "./roles.ts"; + +defineParameterType({ + name: "role", + // Match human-written role text, e.g., "menu item", "radio button", "checkbox" + regexp: /[A-Za-z ][A-Za-z -]*/, + transformer: (text: string): AriaRole => { + const input = text.trim().toLowerCase(); + + // 1) Exact match + if (validAriaRoles.has(input)) { + return input as AriaRole; + } + + // 2) Compact variant: remove spaces and hyphens + const compact = input.replace(/[\s-]+/g, ""); + if (validAriaRoles.has(compact)) { + return compact as AriaRole; + } + + // 3) Aliases + const alias = roleAliases[input]; + if (alias) { + return alias; + } + + // 4) Helpful error + const sample = Array.from(validAriaRoles).slice(0, 15).join(", "); + throw new Error( + `Unknown role "${text}".\n` + + `- Tried: "${input}" and "${compact}".\n` + + "- Add an alias in roleAliases if this is a valid custom phrase.\n" + + `- Example known roles: ${sample} ...`, + ); + }, +}); diff --git a/tests/bdd/support/roles.ts b/tests/bdd/support/roles.ts new file mode 100644 index 00000000..d09b308b --- /dev/null +++ b/tests/bdd/support/roles.ts @@ -0,0 +1,15 @@ +import type { AriaRole } from "@playwright/test"; +import { roles as ariaRolesMap } from "aria-query"; + +// Set of all valid ARIA roles sourced from aria-query +export const validAriaRoles = new Set([...ariaRolesMap.keys()]); + +// Map human-friendly phrases to real ARIA roles (keep this minimal and sensible) +export const roleAliases: Record = { + "menu item": "menuitem", + "radio button": "radio", + "check box": "checkbox", + // common synonyms + dropdown: "combobox", + "list box": "listbox", +}; From f70d24aa188a2e280a968e81ee063a762e8ce3b1 Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Fri, 21 Nov 2025 18:33:54 +0100 Subject: [PATCH 05/31] simplify some stuff --- tests/bdd/steps/global.steps.ts | 8 +----- tests/bdd/support/parameter-types.ts | 43 +++++++++++++++------------- tests/bdd/support/roles.ts | 31 +++++++++++++++----- 3 files changed, 48 insertions(+), 34 deletions(-) diff --git a/tests/bdd/steps/global.steps.ts b/tests/bdd/steps/global.steps.ts index 81f508cc..cc98e2e4 100644 --- a/tests/bdd/steps/global.steps.ts +++ b/tests/bdd/steps/global.steps.ts @@ -14,13 +14,7 @@ When( }, ); -// Backward-compatible convenience wrapper for "button" steps -When( - "I click the {string} button", - async function (this: PlaywrightWorld, label: string) { - await this.page.getByRole("button", { name: label }).click(); - }, -); +// Intentionally avoid per-role variants (e.g., button) to keep steps DRY and consistent. Then( "I should see the text {string}", diff --git a/tests/bdd/support/parameter-types.ts b/tests/bdd/support/parameter-types.ts index 2ee1453e..fa996c98 100644 --- a/tests/bdd/support/parameter-types.ts +++ b/tests/bdd/support/parameter-types.ts @@ -1,6 +1,10 @@ import { defineParameterType } from "@cucumber/cucumber"; import type { AriaRole } from "@playwright/test"; -import { roleAliases, validAriaRoles } from "./roles.ts"; +import { + allowedRolePhrases, + preferredPhraseByRole, + validAriaRoles, +} from "./roles.ts"; defineParameterType({ name: "role", @@ -9,30 +13,29 @@ defineParameterType({ transformer: (text: string): AriaRole => { const input = text.trim().toLowerCase(); - // 1) Exact match - if (validAriaRoles.has(input)) { - return input as AriaRole; - } - - // 2) Compact variant: remove spaces and hyphens - const compact = input.replace(/[\s-]+/g, ""); - if (validAriaRoles.has(compact)) { - return compact as AriaRole; + // Accept only canonical phrases to reduce variants + const canonical = allowedRolePhrases[input]; + if (canonical) { + return canonical; } - // 3) Aliases - const alias = roleAliases[input]; - if (alias) { - return alias; + // If user provided an ARIA role directly, recommend the canonical phrase + if (validAriaRoles.has(input)) { + const role = input as AriaRole; + const preferred = preferredPhraseByRole[role]; + if (preferred && preferred !== input) { + throw new Error( + `Use canonical role phrase "${preferred}" instead of "${input}".`, + ); + } + // Role equals its canonical phrase (e.g., "button", "link", "checkbox") + return role; } - // 4) Helpful error - const sample = Array.from(validAriaRoles).slice(0, 15).join(", "); + // Helpful error with allowed phrases + const examples = Object.keys(allowedRolePhrases).slice(0, 10).join(", "); throw new Error( - `Unknown role "${text}".\n` + - `- Tried: "${input}" and "${compact}".\n` + - "- Add an alias in roleAliases if this is a valid custom phrase.\n" + - `- Example known roles: ${sample} ...`, + `Unknown role phrase "${text}". Use one of the canonical phrases (e.g., ${examples} ...).`, ); }, }); diff --git a/tests/bdd/support/roles.ts b/tests/bdd/support/roles.ts index d09b308b..dbc0b0cb 100644 --- a/tests/bdd/support/roles.ts +++ b/tests/bdd/support/roles.ts @@ -1,15 +1,32 @@ import type { AriaRole } from "@playwright/test"; import { roles as ariaRolesMap } from "aria-query"; -// Set of all valid ARIA roles sourced from aria-query +// Set of all valid ARIA roles sourced from aria-query (for diagnostics) export const validAriaRoles = new Set([...ariaRolesMap.keys()]); -// Map human-friendly phrases to real ARIA roles (keep this minimal and sensible) -export const roleAliases: Record = { +// Canonical phrases allowed in features → mapped to ARIA role +// Keep exactly one human-facing variant per role to minimize duplication +export const allowedRolePhrases: Record = { + // common interactive roles + button: "button", + link: "link", + checkbox: "checkbox", "menu item": "menuitem", "radio button": "radio", - "check box": "checkbox", - // common synonyms - dropdown: "combobox", - "list box": "listbox", + combobox: "combobox", + listbox: "listbox", + // useful extras (read-only) + heading: "heading", + textbox: "textbox", }; + +// Inverse lookup for recommendations (ARIA role → preferred phrase) +export const preferredPhraseByRole: Record = Object.entries( + allowedRolePhrases, +).reduce( + (acc, [phrase, role]) => { + acc[role] = phrase; + return acc; + }, + {} as Record, +); From 9da119c0e676510f2a4e1dfdc4c36fc63c40d4f3 Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Fri, 21 Nov 2025 19:58:00 +0100 Subject: [PATCH 06/31] . --- tests/bdd/features/login.feature | 9 +++++- tests/bdd/steps/global.steps.ts | 4 +-- tests/bdd/support/parameter-types.ts | 43 +++++++++------------------- 3 files changed, 24 insertions(+), 32 deletions(-) diff --git a/tests/bdd/features/login.feature b/tests/bdd/features/login.feature index 734fb06b..8ff53e28 100644 --- a/tests/bdd/features/login.feature +++ b/tests/bdd/features/login.feature @@ -2,7 +2,14 @@ Feature: Login flow Scenario: Sign in and land on Catalog Given I am on "/signin" - When I click the "Okta" button + When I click on the "Okta" button Then I should be on "/catalog" And I should see a heading "MCP Server Catalog" + Scenario: Log out from Catalog + Given I am on "/signin" + When I click on the "Okta" button + Then I should be on "/catalog" + When I click on the "Test User" button + And I click on the "Sign out" menu item + Then I should be on "/signin" diff --git a/tests/bdd/steps/global.steps.ts b/tests/bdd/steps/global.steps.ts index cc98e2e4..89d78075 100644 --- a/tests/bdd/steps/global.steps.ts +++ b/tests/bdd/steps/global.steps.ts @@ -6,9 +6,9 @@ Given("I am on {string}", async function (this: PlaywrightWorld, path: string) { await this.page.goto(`${this.baseUrl}${path}`); }); -// Generic click step using a custom {role} parameter type +// Generic click step using the {role} parameter type (canonical phrases only) When( - 'I click the "{string}" {role}', + "I click on the {string} {role}", async function (this: PlaywrightWorld, label: string, role: AriaRole) { await this.page.getByRole(role, { name: label }).click(); }, diff --git a/tests/bdd/support/parameter-types.ts b/tests/bdd/support/parameter-types.ts index fa996c98..eed6e119 100644 --- a/tests/bdd/support/parameter-types.ts +++ b/tests/bdd/support/parameter-types.ts @@ -1,38 +1,23 @@ import { defineParameterType } from "@cucumber/cucumber"; import type { AriaRole } from "@playwright/test"; -import { - allowedRolePhrases, - preferredPhraseByRole, - validAriaRoles, -} from "./roles.ts"; +import { allowedRolePhrases } from "./roles.ts"; + +// Build a tight, case-insensitive pattern from the canonical phrases +const escape = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +const phrases = Object.keys(allowedRolePhrases).map(escape).join("|"); +// Important: do not anchor (no ^ or $). Cucumber composes parameter regexps +// into a larger expression; anchors can prevent proper matching. +const rolePattern = new RegExp(`(?:${phrases})`); defineParameterType({ name: "role", - // Match human-written role text, e.g., "menu item", "radio button", "checkbox" - regexp: /[A-Za-z ][A-Za-z -]*/, + // keep snippets focused on canonical phrases in feature files + useForSnippets: false, + regexp: rolePattern, transformer: (text: string): AriaRole => { - const input = text.trim().toLowerCase(); - - // Accept only canonical phrases to reduce variants - const canonical = allowedRolePhrases[input]; - if (canonical) { - return canonical; - } - - // If user provided an ARIA role directly, recommend the canonical phrase - if (validAriaRoles.has(input)) { - const role = input as AriaRole; - const preferred = preferredPhraseByRole[role]; - if (preferred && preferred !== input) { - throw new Error( - `Use canonical role phrase "${preferred}" instead of "${input}".`, - ); - } - // Role equals its canonical phrase (e.g., "button", "link", "checkbox") - return role; - } - - // Helpful error with allowed phrases + const key = text.trim().toLowerCase(); + const role = allowedRolePhrases[key]; + if (role) return role; const examples = Object.keys(allowedRolePhrases).slice(0, 10).join(", "); throw new Error( `Unknown role phrase "${text}". Use one of the canonical phrases (e.g., ${examples} ...).`, From a188a5e9ddd44383403ba53f30ea292677afe77a Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Fri, 21 Nov 2025 20:28:31 +0100 Subject: [PATCH 07/31] . --- tests/bdd/support/roles.ts | 42 ++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/tests/bdd/support/roles.ts b/tests/bdd/support/roles.ts index dbc0b0cb..06d9c4c7 100644 --- a/tests/bdd/support/roles.ts +++ b/tests/bdd/support/roles.ts @@ -4,22 +4,38 @@ import { roles as ariaRolesMap } from "aria-query"; // Set of all valid ARIA roles sourced from aria-query (for diagnostics) export const validAriaRoles = new Set([...ariaRolesMap.keys()]); -// Canonical phrases allowed in features → mapped to ARIA role -// Keep exactly one human-facing variant per role to minimize duplication -export const allowedRolePhrases: Record = { - // common interactive roles - button: "button", - link: "link", - checkbox: "checkbox", +// Customized human-friendly phrases → ARIA roles. +// Only add entries here when the preferred phrase differs from the raw ARIA role name. +const customizedRoleNames: Record = { + // Prefer "menu item" over the raw ARIA role string "menuitem" "menu item": "menuitem", - "radio button": "radio", - combobox: "combobox", - listbox: "listbox", - // useful extras (read-only) - heading: "heading", - textbox: "textbox", }; +// Build the canonical mapping of phrases allowed in features → ARIA roles. +// - Start from customized phrases +// - Then add every remaining ARIA role mapping to itself (phrase === role) +// - Do NOT add a self-mapping for roles already covered by a custom phrase, +// so there is exactly one canonical phrase per role. +const buildAllowedRolePhrases = (): Record => { + const mapping: Record = {}; + + // Seed with custom phrases (lowercased keys) + for (const [phrase, role] of Object.entries(customizedRoleNames)) { + mapping[phrase.trim().toLowerCase()] = role; + } + + const customizedRoles = new Set(Object.values(customizedRoleNames)); + for (const roleName of ariaRolesMap.keys()) { + const role = String(roleName) as AriaRole; + if (!customizedRoles.has(role)) { + mapping[role] = role; // default: phrase equals role name + } + } + return mapping; +}; + +export const allowedRolePhrases = buildAllowedRolePhrases(); + // Inverse lookup for recommendations (ARIA role → preferred phrase) export const preferredPhraseByRole: Record = Object.entries( allowedRolePhrases, From 2f96c6f2f76e47a9c20fee81accec4df748b25ec Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Fri, 21 Nov 2025 20:30:26 +0100 Subject: [PATCH 08/31] . --- tests/bdd/support/roles.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/tests/bdd/support/roles.ts b/tests/bdd/support/roles.ts index 06d9c4c7..681f5200 100644 --- a/tests/bdd/support/roles.ts +++ b/tests/bdd/support/roles.ts @@ -37,12 +37,5 @@ const buildAllowedRolePhrases = (): Record => { export const allowedRolePhrases = buildAllowedRolePhrases(); // Inverse lookup for recommendations (ARIA role → preferred phrase) -export const preferredPhraseByRole: Record = Object.entries( - allowedRolePhrases, -).reduce( - (acc, [phrase, role]) => { - acc[role] = phrase; - return acc; - }, - {} as Record, -); +// Note: We intentionally omit an inverse mapping (role -> preferred phrase) +// to keep this surface minimal. Add it here if you want suggestion messages. From 6cc0279f18f0e2165aa42c0a10e48141aa3e04ad Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Tue, 25 Nov 2025 14:29:44 +0100 Subject: [PATCH 09/31] fixes --- .github/workflows/bdd.yml | 6 ++++++ tests/bdd/support/parameter-types.ts | 4 ++-- tests/bdd/ts-node.js | 7 ------- 3 files changed, 8 insertions(+), 9 deletions(-) delete mode 100644 tests/bdd/ts-node.js diff --git a/.github/workflows/bdd.yml b/.github/workflows/bdd.yml index 9cdd4853..e1a818cb 100644 --- a/.github/workflows/bdd.yml +++ b/.github/workflows/bdd.yml @@ -31,13 +31,19 @@ jobs: # Wait for Next (3000), OIDC (4000), and Mock API (9090) for url in http://localhost:3000 http://localhost:4000 http://localhost:9090; do echo "Waiting for $url..." + up=false for i in {1..60}; do if curl -sf "$url" >/dev/null; then echo "$url is up" + up=true break fi sleep 2 done + if [ "$up" = false ]; then + echo "ERROR: $url failed to start" + exit 1 + fi done - name: Run BDD tests diff --git a/tests/bdd/support/parameter-types.ts b/tests/bdd/support/parameter-types.ts index eed6e119..b1d893ec 100644 --- a/tests/bdd/support/parameter-types.ts +++ b/tests/bdd/support/parameter-types.ts @@ -3,8 +3,8 @@ import type { AriaRole } from "@playwright/test"; import { allowedRolePhrases } from "./roles.ts"; // Build a tight, case-insensitive pattern from the canonical phrases -const escape = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); -const phrases = Object.keys(allowedRolePhrases).map(escape).join("|"); +const escapeRegex = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +const phrases = Object.keys(allowedRolePhrases).map(escapeRegex).join("|"); // Important: do not anchor (no ^ or $). Cucumber composes parameter regexps // into a larger expression; anchors can prevent proper matching. const rolePattern = new RegExp(`(?:${phrases})`); diff --git a/tests/bdd/ts-node.js b/tests/bdd/ts-node.js deleted file mode 100644 index e52b79d0..00000000 --- a/tests/bdd/ts-node.js +++ /dev/null @@ -1,7 +0,0 @@ -// Local ts-node registration for Cucumber with CommonJS output -require("ts-node").register({ - transpileOnly: true, - compilerOptions: { - module: "commonjs", - }, -}); From 4fd4ff0b0332526895cdb842c646732b9729c10d Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Tue, 25 Nov 2025 14:42:09 +0100 Subject: [PATCH 10/31] login "fixture" --- tests/bdd/features/login.feature | 5 ++--- tests/bdd/steps/global.steps.ts | 7 +++++++ tests/bdd/support/hooks.ts | 7 +++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/tests/bdd/features/login.feature b/tests/bdd/features/login.feature index 8ff53e28..c9bba278 100644 --- a/tests/bdd/features/login.feature +++ b/tests/bdd/features/login.feature @@ -7,9 +7,8 @@ Feature: Login flow And I should see a heading "MCP Server Catalog" Scenario: Log out from Catalog - Given I am on "/signin" - When I click on the "Okta" button - Then I should be on "/catalog" + Given I am logged in + And I am on "/catalog" When I click on the "Test User" button And I click on the "Sign out" menu item Then I should be on "/signin" diff --git a/tests/bdd/steps/global.steps.ts b/tests/bdd/steps/global.steps.ts index 89d78075..e2b8a79d 100644 --- a/tests/bdd/steps/global.steps.ts +++ b/tests/bdd/steps/global.steps.ts @@ -6,6 +6,13 @@ Given("I am on {string}", async function (this: PlaywrightWorld, path: string) { await this.page.goto(`${this.baseUrl}${path}`); }); +Given("I am logged in", async function (this: PlaywrightWorld) { + await this.page.goto(`${this.baseUrl}/signin`); + await this.page.getByRole("button", { name: "Okta" }).click(); + // Wait for auth to complete (redirects away from signin) + await this.page.waitForURL((url) => !url.pathname.startsWith("/signin")); +}); + // Generic click step using the {role} parameter type (canonical phrases only) When( "I click on the {string} {role}", diff --git a/tests/bdd/support/hooks.ts b/tests/bdd/support/hooks.ts index f7d5e782..b2bac0a5 100644 --- a/tests/bdd/support/hooks.ts +++ b/tests/bdd/support/hooks.ts @@ -30,6 +30,13 @@ Before(async function (this: PlaywrightWorld) { this.context = context; this.page = page; + // Hide Next.js dev overlay to keep traces clean + await this.page.addInitScript(() => { + const style = document.createElement("style"); + style.textContent = "nextjs-portal { display: none !important; }"; + document.head.appendChild(style); + }); + if (TRACE_ENABLED) { await this.context.tracing.start({ screenshots: true, snapshots: true }); } From f44313fd449858b7b1251f4bab1e8f6146dd3f5b Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Tue, 25 Nov 2025 14:42:45 +0100 Subject: [PATCH 11/31] do not hang after successful run --- tests/bdd/support/hooks.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/bdd/support/hooks.ts b/tests/bdd/support/hooks.ts index b2bac0a5..a33cd9fa 100644 --- a/tests/bdd/support/hooks.ts +++ b/tests/bdd/support/hooks.ts @@ -1,5 +1,6 @@ import { After, + AfterAll, Before, type ITestCaseHookParameter, setDefaultTimeout, @@ -58,3 +59,10 @@ After(async function (this: PlaywrightWorld, scenario: ITestCaseHookParameter) { if (this.page) await this.page.close(); if (this.context) await this.context.close(); }); + +AfterAll(async () => { + if (browser) { + await browser.close(); + browser = undefined; + } +}); From d5264ac44638807a827014b4caaf2c641ca4480d Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Tue, 25 Nov 2025 14:50:24 +0100 Subject: [PATCH 12/31] cookie injection to keep authentication fast --- cucumber.js | 1 + tests/bdd/features/catalog.feature | 6 ++++++ tests/bdd/steps/global.steps.ts | 7 +++---- tests/bdd/support/auth.ts | 32 ++++++++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 tests/bdd/features/catalog.feature create mode 100644 tests/bdd/support/auth.ts diff --git a/cucumber.js b/cucumber.js index 2d1ece17..dc7bff40 100644 --- a/cucumber.js +++ b/cucumber.js @@ -6,6 +6,7 @@ module.exports = { "tests/bdd/support/hooks.ts", "tests/bdd/support/roles.ts", "tests/bdd/support/parameter-types.ts", + "tests/bdd/support/auth.ts", "tests/bdd/steps/**/*.ts", ], paths: ["tests/bdd/features/**/*.feature"], diff --git a/tests/bdd/features/catalog.feature b/tests/bdd/features/catalog.feature new file mode 100644 index 00000000..dff0954c --- /dev/null +++ b/tests/bdd/features/catalog.feature @@ -0,0 +1,6 @@ +Feature: Catalog page + + Scenario: View catalog page header + Given I am logged in + And I am on "/catalog" + Then I should see a heading "MCP Server Catalog" diff --git a/tests/bdd/steps/global.steps.ts b/tests/bdd/steps/global.steps.ts index e2b8a79d..52369b5e 100644 --- a/tests/bdd/steps/global.steps.ts +++ b/tests/bdd/steps/global.steps.ts @@ -1,5 +1,6 @@ import { Given, Then, When } from "@cucumber/cucumber"; import { type AriaRole, expect } from "@playwright/test"; +import { injectAuthCookies } from "../support/auth.ts"; import type { PlaywrightWorld } from "../support/world"; Given("I am on {string}", async function (this: PlaywrightWorld, path: string) { @@ -7,10 +8,8 @@ Given("I am on {string}", async function (this: PlaywrightWorld, path: string) { }); Given("I am logged in", async function (this: PlaywrightWorld) { - await this.page.goto(`${this.baseUrl}/signin`); - await this.page.getByRole("button", { name: "Okta" }).click(); - // Wait for auth to complete (redirects away from signin) - await this.page.waitForURL((url) => !url.pathname.startsWith("/signin")); + // Perform login in a separate page and inject cookies into context + await injectAuthCookies(this.context); }); // Generic click step using the {role} parameter type (canonical phrases only) diff --git a/tests/bdd/support/auth.ts b/tests/bdd/support/auth.ts new file mode 100644 index 00000000..0eabbedb --- /dev/null +++ b/tests/bdd/support/auth.ts @@ -0,0 +1,32 @@ +import type { BrowserContext } from "@playwright/test"; + +const BASE_URL = process.env.BASE_URL || "http://localhost:3000"; + +/** + * Performs programmatic login by navigating through the OIDC flow + * in an isolated context, then extracts and injects the cookies. + * + * This is faster than UI-based login for tests that need auth + * but aren't testing the login flow itself. + */ +export async function injectAuthCookies( + context: BrowserContext, +): Promise { + // Create a temporary page to perform login + const page = await context.newPage(); + + try { + // Navigate to signin and click the Okta button + await page.goto(`${BASE_URL}/signin`); + await page.getByRole("button", { name: "Okta" }).click(); + + // Wait for auth to complete (redirects away from signin) + await page.waitForURL((url) => !url.pathname.startsWith("/signin"), { + timeout: 30000, + }); + + // Cookies are now set in the context - they'll persist for other pages + } finally { + await page.close(); + } +} From d10417b655d6f9d4e633efca513ce2fc7f33e462 Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Tue, 25 Nov 2025 14:57:15 +0100 Subject: [PATCH 13/31] fix ci failure --- .github/workflows/bdd.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/bdd.yml b/.github/workflows/bdd.yml index e1a818cb..3321ea35 100644 --- a/.github/workflows/bdd.yml +++ b/.github/workflows/bdd.yml @@ -25,6 +25,8 @@ jobs: run: pnpm exec playwright install --with-deps - name: Start dev stack (Next + OIDC + Mock) + env: + API_BASE_URL: http://localhost:9090 run: | pnpm dev & echo $! > dev.pid From cc0d781f94699520ce0b7436ededb21b8fa939a2 Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Tue, 25 Nov 2025 15:06:45 +0100 Subject: [PATCH 14/31] . --- .github/workflows/bdd.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/bdd.yml b/.github/workflows/bdd.yml index 3321ea35..39bb275a 100644 --- a/.github/workflows/bdd.yml +++ b/.github/workflows/bdd.yml @@ -30,8 +30,8 @@ jobs: run: | pnpm dev & echo $! > dev.pid - # Wait for Next (3000), OIDC (4000), and Mock API (9090) - for url in http://localhost:3000 http://localhost:4000 http://localhost:9090; do + # Wait for Next (3000), OIDC (4000 - use discovery endpoint), and Mock API (9090) + for url in http://localhost:3000 http://localhost:4000/.well-known/openid-configuration http://localhost:9090; do echo "Waiting for $url..." up=false for i in {1..60}; do From 4e43d58514bfb79f25d54f59629f0d7f46e14407 Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Tue, 25 Nov 2025 16:17:28 +0100 Subject: [PATCH 15/31] . --- .github/workflows/bdd.yml | 4 ++-- src/mocks/server.ts | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/bdd.yml b/.github/workflows/bdd.yml index 39bb275a..8313b069 100644 --- a/.github/workflows/bdd.yml +++ b/.github/workflows/bdd.yml @@ -30,8 +30,8 @@ jobs: run: | pnpm dev & echo $! > dev.pid - # Wait for Next (3000), OIDC (4000 - use discovery endpoint), and Mock API (9090) - for url in http://localhost:3000 http://localhost:4000/.well-known/openid-configuration http://localhost:9090; do + # Wait for Next (3000), OIDC (4000 - use discovery endpoint), and Mock API (9090 - use health endpoint) + for url in http://localhost:3000 http://localhost:4000/.well-known/openid-configuration http://localhost:9090/health; do echo "Waiting for $url..." up=false for i in {1..60}; do diff --git a/src/mocks/server.ts b/src/mocks/server.ts index 4a2acd57..93b74fbb 100644 --- a/src/mocks/server.ts +++ b/src/mocks/server.ts @@ -1,6 +1,7 @@ import type { IncomingMessage, ServerResponse } from "node:http"; import { createServer } from "@mswjs/http-middleware"; import { config } from "dotenv"; +import { HttpResponse, http } from "msw"; import { handlers } from "./handlers"; // Load .env first, then .env.local (which overrides .env) @@ -21,7 +22,12 @@ if (!port) { throw new Error("API_BASE_URL must include a port number"); } -const httpServer = createServer(...handlers); +// Add health check endpoint for CI readiness checks +const healthHandler = http.get("*/health", () => { + return HttpResponse.json({ status: "ok" }); +}); + +const httpServer = createServer(healthHandler, ...handlers); httpServer.on("request", (req: IncomingMessage, _res: ServerResponse) => { console.log(`[mock] ${req.method} ${req.url}`); From 5430b648960ad50fa5144d6daa9b11a9584728c6 Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Tue, 25 Nov 2025 16:40:11 +0100 Subject: [PATCH 16/31] . --- .github/workflows/bdd.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/bdd.yml b/.github/workflows/bdd.yml index 8313b069..49f92f06 100644 --- a/.github/workflows/bdd.yml +++ b/.github/workflows/bdd.yml @@ -27,6 +27,12 @@ jobs: - name: Start dev stack (Next + OIDC + Mock) env: API_BASE_URL: http://localhost:9090 + OIDC_ISSUER_URL: http://localhost:4000 + OIDC_CLIENT_ID: better-auth-dev + OIDC_CLIENT_SECRET: dev-secret-change-in-production + NEXT_PUBLIC_OIDC_PROVIDER_ID: okta + BETTER_AUTH_URL: http://localhost:3000 + BETTER_AUTH_SECRET: ci-test-secret-not-for-production run: | pnpm dev & echo $! > dev.pid From acdc4965a42a00ea533493f3930294f776d86595 Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Tue, 25 Nov 2025 16:47:37 +0100 Subject: [PATCH 17/31] implement review suggestions --- src/mocks/server.ts | 2 ++ tests/bdd/steps/global.steps.ts | 12 ++++++------ tests/bdd/support/hooks.ts | 3 ++- tests/bdd/support/world.ts | 16 ++++++++++++++-- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/mocks/server.ts b/src/mocks/server.ts index 93b74fbb..ec3d326e 100644 --- a/src/mocks/server.ts +++ b/src/mocks/server.ts @@ -30,6 +30,8 @@ const healthHandler = http.get("*/health", () => { const httpServer = createServer(healthHandler, ...handlers); httpServer.on("request", (req: IncomingMessage, _res: ServerResponse) => { + // Skip logging health checks to reduce CI noise + if (req.url?.includes("/health")) return; console.log(`[mock] ${req.method} ${req.url}`); }); diff --git a/tests/bdd/steps/global.steps.ts b/tests/bdd/steps/global.steps.ts index 52369b5e..59629499 100644 --- a/tests/bdd/steps/global.steps.ts +++ b/tests/bdd/steps/global.steps.ts @@ -4,19 +4,19 @@ import { injectAuthCookies } from "../support/auth.ts"; import type { PlaywrightWorld } from "../support/world"; Given("I am on {string}", async function (this: PlaywrightWorld, path: string) { - await this.page.goto(`${this.baseUrl}${path}`); + await this.requirePage().goto(`${this.baseUrl}${path}`); }); Given("I am logged in", async function (this: PlaywrightWorld) { // Perform login in a separate page and inject cookies into context - await injectAuthCookies(this.context); + await injectAuthCookies(this.requireContext()); }); // Generic click step using the {role} parameter type (canonical phrases only) When( "I click on the {string} {role}", async function (this: PlaywrightWorld, label: string, role: AriaRole) { - await this.page.getByRole(role, { name: label }).click(); + await this.requirePage().getByRole(role, { name: label }).click(); }, ); @@ -25,7 +25,7 @@ When( Then( "I should see the text {string}", async function (this: PlaywrightWorld, text: string) { - await expect(this.page.getByText(text)).toBeVisible(); + await expect(this.requirePage().getByText(text)).toBeVisible(); }, ); @@ -33,7 +33,7 @@ Then( "I should see a heading {string}", async function (this: PlaywrightWorld, heading: string) { await expect( - this.page.getByRole("heading", { name: heading }), + this.requirePage().getByRole("heading", { name: heading }), ).toBeVisible(); }, ); @@ -41,7 +41,7 @@ Then( Then( "I should be on {string}", async function (this: PlaywrightWorld, path: string) { - await expect(this.page).toHaveURL( + await expect(this.requirePage()).toHaveURL( new RegExp( `${this.baseUrl}${path.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&")}$`, ), diff --git a/tests/bdd/support/hooks.ts b/tests/bdd/support/hooks.ts index a33cd9fa..ea62c452 100644 --- a/tests/bdd/support/hooks.ts +++ b/tests/bdd/support/hooks.ts @@ -50,10 +50,11 @@ Before(async function (this: PlaywrightWorld) { After(async function (this: PlaywrightWorld, scenario: ITestCaseHookParameter) { if (TRACE_ENABLED && this.context) { const safeName = scenario.pickle.name.replace(/[^a-z0-9-]+/gi, "_"); + const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); const { mkdir } = await import("node:fs/promises"); await mkdir("test-results/traces", { recursive: true }); await this.context.tracing.stop({ - path: `test-results/traces/${safeName}.zip`, + path: `test-results/traces/${safeName}_${timestamp}.zip`, }); } if (this.page) await this.page.close(); diff --git a/tests/bdd/support/world.ts b/tests/bdd/support/world.ts index d9559dce..ef5599d4 100644 --- a/tests/bdd/support/world.ts +++ b/tests/bdd/support/world.ts @@ -2,13 +2,25 @@ import { setWorldConstructor, type World } from "@cucumber/cucumber"; import type { BrowserContext, Page } from "@playwright/test"; export class PlaywrightWorld implements World { - page!: Page; - context!: BrowserContext; + page: Page | undefined; + context: BrowserContext | undefined; baseUrl: string; constructor() { this.baseUrl = process.env.BASE_URL || "http://localhost:3000"; } + + requirePage(): Page { + if (!this.page) + throw new Error("Page not initialized - Before hook may have failed"); + return this.page; + } + + requireContext(): BrowserContext { + if (!this.context) + throw new Error("Context not initialized - Before hook may have failed"); + return this.context; + } } setWorldConstructor(PlaywrightWorld); From fe563b7cdaa801ecffbc5adbeeb46f28611524b2 Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Tue, 25 Nov 2025 16:51:07 +0100 Subject: [PATCH 18/31] cleanup --- src/mocks/server.ts | 7 ------- tests/bdd/steps/global.steps.ts | 4 ---- tests/bdd/support/auth.ts | 13 +----------- tests/bdd/support/hooks.ts | 4 ++-- tests/bdd/support/parameter-types.ts | 9 +------- tests/bdd/support/roles.ts | 31 +++++++--------------------- 6 files changed, 12 insertions(+), 56 deletions(-) diff --git a/src/mocks/server.ts b/src/mocks/server.ts index ec3d326e..2af43365 100644 --- a/src/mocks/server.ts +++ b/src/mocks/server.ts @@ -4,25 +4,19 @@ import { config } from "dotenv"; import { HttpResponse, http } from "msw"; import { handlers } from "./handlers"; -// Load .env first, then .env.local (which overrides .env) config(); config({ path: ".env.local" }); -// Mock server runs on the port configured in API_BASE_URL -// This ensures the app can reach the mock server at the expected URL const apiBaseUrl = process.env.API_BASE_URL; - if (!apiBaseUrl) { throw new Error("API_BASE_URL environment variable is required"); } const port = new URL(apiBaseUrl).port; - if (!port) { throw new Error("API_BASE_URL must include a port number"); } -// Add health check endpoint for CI readiness checks const healthHandler = http.get("*/health", () => { return HttpResponse.json({ status: "ok" }); }); @@ -30,7 +24,6 @@ const healthHandler = http.get("*/health", () => { const httpServer = createServer(healthHandler, ...handlers); httpServer.on("request", (req: IncomingMessage, _res: ServerResponse) => { - // Skip logging health checks to reduce CI noise if (req.url?.includes("/health")) return; console.log(`[mock] ${req.method} ${req.url}`); }); diff --git a/tests/bdd/steps/global.steps.ts b/tests/bdd/steps/global.steps.ts index 59629499..8a1b2666 100644 --- a/tests/bdd/steps/global.steps.ts +++ b/tests/bdd/steps/global.steps.ts @@ -8,11 +8,9 @@ Given("I am on {string}", async function (this: PlaywrightWorld, path: string) { }); Given("I am logged in", async function (this: PlaywrightWorld) { - // Perform login in a separate page and inject cookies into context await injectAuthCookies(this.requireContext()); }); -// Generic click step using the {role} parameter type (canonical phrases only) When( "I click on the {string} {role}", async function (this: PlaywrightWorld, label: string, role: AriaRole) { @@ -20,8 +18,6 @@ When( }, ); -// Intentionally avoid per-role variants (e.g., button) to keep steps DRY and consistent. - Then( "I should see the text {string}", async function (this: PlaywrightWorld, text: string) { diff --git a/tests/bdd/support/auth.ts b/tests/bdd/support/auth.ts index 0eabbedb..b08602e1 100644 --- a/tests/bdd/support/auth.ts +++ b/tests/bdd/support/auth.ts @@ -3,29 +3,18 @@ import type { BrowserContext } from "@playwright/test"; const BASE_URL = process.env.BASE_URL || "http://localhost:3000"; /** - * Performs programmatic login by navigating through the OIDC flow - * in an isolated context, then extracts and injects the cookies. - * - * This is faster than UI-based login for tests that need auth - * but aren't testing the login flow itself. + * Logs in via OIDC flow in a temporary page, leaving auth cookies in the context. */ export async function injectAuthCookies( context: BrowserContext, ): Promise { - // Create a temporary page to perform login const page = await context.newPage(); - try { - // Navigate to signin and click the Okta button await page.goto(`${BASE_URL}/signin`); await page.getByRole("button", { name: "Okta" }).click(); - - // Wait for auth to complete (redirects away from signin) await page.waitForURL((url) => !url.pathname.startsWith("/signin"), { timeout: 30000, }); - - // Cookies are now set in the context - they'll persist for other pages } finally { await page.close(); } diff --git a/tests/bdd/support/hooks.ts b/tests/bdd/support/hooks.ts index ea62c452..9b8d9b68 100644 --- a/tests/bdd/support/hooks.ts +++ b/tests/bdd/support/hooks.ts @@ -16,7 +16,7 @@ import type { PlaywrightWorld } from "./world"; let browser: Browser | undefined; const TRACE_ENABLED = process.env.PWTRACE === "1"; -setDefaultTimeout(60 * 1000); // 60s per step +setDefaultTimeout(60_000); Before(async function (this: PlaywrightWorld) { const isDebug = !!process.env.PWDEBUG; @@ -31,8 +31,8 @@ Before(async function (this: PlaywrightWorld) { this.context = context; this.page = page; - // Hide Next.js dev overlay to keep traces clean await this.page.addInitScript(() => { + // Hide Next.js dev overlay const style = document.createElement("style"); style.textContent = "nextjs-portal { display: none !important; }"; document.head.appendChild(style); diff --git a/tests/bdd/support/parameter-types.ts b/tests/bdd/support/parameter-types.ts index b1d893ec..512001ea 100644 --- a/tests/bdd/support/parameter-types.ts +++ b/tests/bdd/support/parameter-types.ts @@ -2,25 +2,18 @@ import { defineParameterType } from "@cucumber/cucumber"; import type { AriaRole } from "@playwright/test"; import { allowedRolePhrases } from "./roles.ts"; -// Build a tight, case-insensitive pattern from the canonical phrases const escapeRegex = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); const phrases = Object.keys(allowedRolePhrases).map(escapeRegex).join("|"); -// Important: do not anchor (no ^ or $). Cucumber composes parameter regexps -// into a larger expression; anchors can prevent proper matching. const rolePattern = new RegExp(`(?:${phrases})`); defineParameterType({ name: "role", - // keep snippets focused on canonical phrases in feature files useForSnippets: false, regexp: rolePattern, transformer: (text: string): AriaRole => { const key = text.trim().toLowerCase(); const role = allowedRolePhrases[key]; if (role) return role; - const examples = Object.keys(allowedRolePhrases).slice(0, 10).join(", "); - throw new Error( - `Unknown role phrase "${text}". Use one of the canonical phrases (e.g., ${examples} ...).`, - ); + throw new Error(`Unknown role phrase "${text}".`); }, }); diff --git a/tests/bdd/support/roles.ts b/tests/bdd/support/roles.ts index 681f5200..cdf25864 100644 --- a/tests/bdd/support/roles.ts +++ b/tests/bdd/support/roles.ts @@ -1,41 +1,26 @@ import type { AriaRole } from "@playwright/test"; import { roles as ariaRolesMap } from "aria-query"; -// Set of all valid ARIA roles sourced from aria-query (for diagnostics) -export const validAriaRoles = new Set([...ariaRolesMap.keys()]); - -// Customized human-friendly phrases → ARIA roles. -// Only add entries here when the preferred phrase differs from the raw ARIA role name. -const customizedRoleNames: Record = { - // Prefer "menu item" over the raw ARIA role string "menuitem" +// Custom phrases for ARIA roles (when the phrase differs from the role name) +const customPhrases: Record = { "menu item": "menuitem", }; -// Build the canonical mapping of phrases allowed in features → ARIA roles. -// - Start from customized phrases -// - Then add every remaining ARIA role mapping to itself (phrase === role) -// - Do NOT add a self-mapping for roles already covered by a custom phrase, -// so there is exactly one canonical phrase per role. -const buildAllowedRolePhrases = (): Record => { +function buildAllowedRolePhrases(): Record { const mapping: Record = {}; - // Seed with custom phrases (lowercased keys) - for (const [phrase, role] of Object.entries(customizedRoleNames)) { + for (const [phrase, role] of Object.entries(customPhrases)) { mapping[phrase.trim().toLowerCase()] = role; } - const customizedRoles = new Set(Object.values(customizedRoleNames)); + const customRoles = new Set(Object.values(customPhrases)); for (const roleName of ariaRolesMap.keys()) { const role = String(roleName) as AriaRole; - if (!customizedRoles.has(role)) { - mapping[role] = role; // default: phrase equals role name + if (!customRoles.has(role)) { + mapping[role] = role; } } return mapping; -}; +} export const allowedRolePhrases = buildAllowedRolePhrases(); - -// Inverse lookup for recommendations (ARIA role → preferred phrase) -// Note: We intentionally omit an inverse mapping (role -> preferred phrase) -// to keep this surface minimal. Add it here if you want suggestion messages. From 123250d44c7a5d8ca8df4a396563361bae3dff16 Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Tue, 25 Nov 2025 17:16:27 +0100 Subject: [PATCH 19/31] cleanup --- README.md | 28 ++++++++++++++-------------- tests/bdd/features/catalog.feature | 2 +- tests/bdd/features/login.feature | 2 +- tests/bdd/steps/global.steps.ts | 4 ++-- tests/bdd/support/parameter-types.ts | 7 +++++++ 5 files changed, 25 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 29bf7cee..7e152121 100644 --- a/README.md +++ b/README.md @@ -262,26 +262,26 @@ BETTER_AUTH_URL=http://localhost:3000 ### Testing +#### Unit/Component Tests + ```bash -# Run all tests -pnpm test +pnpm test # Run all tests +pnpm test --watch # Watch mode +pnpm test --coverage # With coverage +``` -# Run tests in watch mode -pnpm test --watch +Uses Vitest + Testing Library + MSW. -# Run tests with coverage -pnpm test --coverage +#### BDD E2E Tests (Cucumber + Playwright) -# Run specific test file -pnpm test src/components/navbar.test.tsx +```bash +pnpm exec playwright install # One-time browser install +pnpm dev # Start dev stack +pnpm run test:bdd # Run scenarios (headless) +pnpm run test:bdd:debug # With Playwright Inspector +pnpm run test:bdd:trace # Capture traces ``` -Tests use: - -- **Vitest** - Test runner -- **Testing Library** - React component testing -- **MSW** - API mocking - ### Mock Server The project includes a standalone MSW mock server for development: diff --git a/tests/bdd/features/catalog.feature b/tests/bdd/features/catalog.feature index dff0954c..418178cb 100644 --- a/tests/bdd/features/catalog.feature +++ b/tests/bdd/features/catalog.feature @@ -3,4 +3,4 @@ Feature: Catalog page Scenario: View catalog page header Given I am logged in And I am on "/catalog" - Then I should see a heading "MCP Server Catalog" + Then I should see an "MCP Server Catalog" heading diff --git a/tests/bdd/features/login.feature b/tests/bdd/features/login.feature index c9bba278..42bbbc3e 100644 --- a/tests/bdd/features/login.feature +++ b/tests/bdd/features/login.feature @@ -4,7 +4,7 @@ Feature: Login flow Given I am on "/signin" When I click on the "Okta" button Then I should be on "/catalog" - And I should see a heading "MCP Server Catalog" + And I should see an "MCP Server Catalog" heading Scenario: Log out from Catalog Given I am logged in diff --git a/tests/bdd/steps/global.steps.ts b/tests/bdd/steps/global.steps.ts index 8a1b2666..ff2f8143 100644 --- a/tests/bdd/steps/global.steps.ts +++ b/tests/bdd/steps/global.steps.ts @@ -26,8 +26,8 @@ Then( ); Then( - "I should see a heading {string}", - async function (this: PlaywrightWorld, heading: string) { + "I should see {article} {string} heading", + async function (this: PlaywrightWorld, _article: null, heading: string) { await expect( this.requirePage().getByRole("heading", { name: heading }), ).toBeVisible(); diff --git a/tests/bdd/support/parameter-types.ts b/tests/bdd/support/parameter-types.ts index 512001ea..bb1bd443 100644 --- a/tests/bdd/support/parameter-types.ts +++ b/tests/bdd/support/parameter-types.ts @@ -17,3 +17,10 @@ defineParameterType({ throw new Error(`Unknown role phrase "${text}".`); }, }); + +defineParameterType({ + name: "article", + useForSnippets: false, + regexp: /a|an/, + transformer: () => null, +}); From 2cd7547806939e0ec18216a67d7dcc74afed3e10 Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Tue, 25 Nov 2025 17:18:07 +0100 Subject: [PATCH 20/31] remove unused rule definition --- tests/bdd/steps/global.steps.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/bdd/steps/global.steps.ts b/tests/bdd/steps/global.steps.ts index ff2f8143..bce0f18c 100644 --- a/tests/bdd/steps/global.steps.ts +++ b/tests/bdd/steps/global.steps.ts @@ -18,13 +18,6 @@ When( }, ); -Then( - "I should see the text {string}", - async function (this: PlaywrightWorld, text: string) { - await expect(this.requirePage().getByText(text)).toBeVisible(); - }, -); - Then( "I should see {article} {string} heading", async function (this: PlaywrightWorld, _article: null, heading: string) { From d3ddb4c53149c232888af1c3feb87a4d0450b957 Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Tue, 25 Nov 2025 17:21:46 +0100 Subject: [PATCH 21/31] stricted path for healthcheck url --- src/mocks/server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mocks/server.ts b/src/mocks/server.ts index 2af43365..8d2d03b4 100644 --- a/src/mocks/server.ts +++ b/src/mocks/server.ts @@ -17,7 +17,7 @@ if (!port) { throw new Error("API_BASE_URL must include a port number"); } -const healthHandler = http.get("*/health", () => { +const healthHandler = http.get("/health", () => { return HttpResponse.json({ status: "ok" }); }); From 61b26fa1fb5da1e01ecc089c2328fc1223ee1bb4 Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Fri, 12 Dec 2025 14:23:25 +0100 Subject: [PATCH 22/31] remove cucumber --- .github/workflows/bdd.yml | 25 +- CLAUDE.md | 14 +- README.md | 16 +- cucumber.js | 16 - package.json | 10 +- playwright.config.ts | 41 ++ pnpm-lock.yaml | 999 +-------------------------- tests/bdd/features/catalog.feature | 6 - tests/bdd/features/login.feature | 14 - tests/bdd/steps/global.steps.ts | 39 -- tests/bdd/support/auth.ts | 21 - tests/bdd/support/hooks.ts | 69 -- tests/bdd/support/parameter-types.ts | 26 - tests/bdd/support/roles.ts | 26 - tests/bdd/support/world.ts | 26 - tests/e2e/catalog.spec.ts | 10 + tests/e2e/fixtures.ts | 20 + tests/e2e/login.spec.ts | 19 + 18 files changed, 135 insertions(+), 1262 deletions(-) delete mode 100644 cucumber.js create mode 100644 playwright.config.ts delete mode 100644 tests/bdd/features/catalog.feature delete mode 100644 tests/bdd/features/login.feature delete mode 100644 tests/bdd/steps/global.steps.ts delete mode 100644 tests/bdd/support/auth.ts delete mode 100644 tests/bdd/support/hooks.ts delete mode 100644 tests/bdd/support/parameter-types.ts delete mode 100644 tests/bdd/support/roles.ts delete mode 100644 tests/bdd/support/world.ts create mode 100644 tests/e2e/catalog.spec.ts create mode 100644 tests/e2e/fixtures.ts create mode 100644 tests/e2e/login.spec.ts diff --git a/.github/workflows/bdd.yml b/.github/workflows/bdd.yml index 49f92f06..659bc1ee 100644 --- a/.github/workflows/bdd.yml +++ b/.github/workflows/bdd.yml @@ -1,4 +1,4 @@ -name: BDD E2E +name: E2E Tests on: push: @@ -11,9 +11,9 @@ permissions: jobs: e2e: - name: Run BDD (Cucumber + Playwright) + name: Playwright E2E runs-on: ubuntu-latest - timeout-minutes: 20 + timeout-minutes: 15 steps: - name: Checkout uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 @@ -22,7 +22,7 @@ jobs: uses: ./.github/actions/setup - name: Install Playwright browsers - run: pnpm exec playwright install --with-deps + run: pnpm exec playwright install --with-deps chromium - name: Start dev stack (Next + OIDC + Mock) env: @@ -40,7 +40,7 @@ jobs: for url in http://localhost:3000 http://localhost:4000/.well-known/openid-configuration http://localhost:9090/health; do echo "Waiting for $url..." up=false - for i in {1..60}; do + for i in {1..30}; do if curl -sf "$url" >/dev/null; then echo "$url is up" up=true @@ -54,13 +54,22 @@ jobs: fi done - - name: Run BDD tests + - name: Run Playwright tests env: BASE_URL: http://localhost:3000 - run: pnpm run test:bdd + run: pnpm run test:e2e + + - name: Upload test artifacts + uses: actions/upload-artifact@v4 + if: failure() + with: + name: playwright-report + path: | + test-results/ + playwright-report/ + retention-days: 7 - name: Cleanup dev if: always() run: | if [ -f dev.pid ]; then kill $(cat dev.pid) || true; fi - diff --git a/CLAUDE.md b/CLAUDE.md index 6f5e34c1..b3b944a7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -222,18 +222,18 @@ pnpm generate-client:nofetch # Regenerate without fetching - **Testing Library** - Component testing - **jsdom** - DOM simulation -### BDD E2E (Cucumber + Playwright) +### E2E Tests (Playwright) -- End-to-end scenarios live under `tests/bdd` and run against a live dev stack. +- End-to-end tests live under `tests/e2e` and run against a live dev stack. - Commands: - `pnpm dev` – starts Next.js (3000), mock OIDC (4000), and MSW mock API (9090) - - `pnpm run test:bdd` – runs Cucumber scenarios with Playwright (headless) - - `pnpm run test:bdd:debug` – runs with Playwright Inspector (headed, pauses on start) - - `pnpm run test:bdd:trace` – runs with Playwright tracing, artifacts in `test-results/traces/*.zip` -- CI runs BDD tests via `.github/workflows/bdd.yml` and installs Playwright browsers. + - `pnpm run test:e2e` – runs Playwright tests (headless) + - `pnpm run test:e2e:ui` – opens Playwright UI mode for interactive debugging + - `pnpm run test:e2e:debug` – runs with Playwright Inspector +- CI runs E2E tests via `.github/workflows/bdd.yml` and installs Playwright browsers. - Install browsers locally once: `pnpm exec playwright install` -Scaffolded global steps include navigation, clicking buttons by accessible name, URL assertions, and text/heading checks. Prefer reusing global steps; add domain-specific ones only when needed for clarity. +Tests use custom fixtures for authentication. The `authenticatedPage` fixture handles login automatically. ### Example Test diff --git a/README.md b/README.md index 24a76965..f1601ee0 100644 --- a/README.md +++ b/README.md @@ -285,14 +285,14 @@ pnpm test --coverage # With coverage Uses Vitest + Testing Library + MSW. -#### BDD E2E Tests (Cucumber + Playwright) +#### E2E Tests (Playwright) ```bash pnpm exec playwright install # One-time browser install pnpm dev # Start dev stack -pnpm run test:bdd # Run scenarios (headless) -pnpm run test:bdd:debug # With Playwright Inspector -pnpm run test:bdd:trace # Capture traces +pnpm run test:e2e # Run tests (headless) +pnpm run test:e2e:ui # Playwright UI mode +pnpm run test:e2e:debug # With Playwright Inspector ``` ### Mock Server @@ -479,16 +479,16 @@ pnpm type-check # TypeScript pnpm lint # Biome ``` -### BDD E2E (Cucumber + Playwright) +### E2E Tests (Playwright) Run the app and E2E tests locally: ```bash pnpm exec playwright install # one-time browser install pnpm dev # start Next (3000) + OIDC (4000) + Mock API (9090) -pnpm run test:bdd # run Cucumber scenarios (headless) -pnpm run test:bdd:debug # headed with Playwright Inspector (PWDEBUG=1) -pnpm run test:bdd:trace # capture Playwright traces (PWTRACE=1) +pnpm run test:e2e # run Playwright tests (headless) +pnpm run test:e2e:ui # Playwright UI mode for debugging +pnpm run test:e2e:debug # with Playwright Inspector ``` CI runs the E2E suite via `.github/workflows/bdd.yml`. diff --git a/cucumber.js b/cucumber.js deleted file mode 100644 index dc7bff40..00000000 --- a/cucumber.js +++ /dev/null @@ -1,16 +0,0 @@ -module.exports = { - default: { - requireModule: ["ts-node/register/transpile-only"], - require: [ - "tests/bdd/support/world.ts", - "tests/bdd/support/hooks.ts", - "tests/bdd/support/roles.ts", - "tests/bdd/support/parameter-types.ts", - "tests/bdd/support/auth.ts", - "tests/bdd/steps/**/*.ts", - ], - paths: ["tests/bdd/features/**/*.feature"], - publishQuiet: true, - format: ["progress"], - }, -}; diff --git a/package.json b/package.json index f85e5b6e..069c674a 100644 --- a/package.json +++ b/package.json @@ -13,9 +13,9 @@ "lint": "biome check", "format": "biome format --write", "test": "vitest", - "test:bdd": "cucumber-js", - "test:bdd:trace": "PWTRACE=1 cucumber-js", - "test:bdd:debug": "PWDEBUG=1 cucumber-js", + "test:e2e": "playwright test", + "test:e2e:ui": "playwright test --ui", + "test:e2e:debug": "playwright test --debug", "type-check": "tsc --noEmit", "prepare": "husky", "oidc": "node dev-auth/oidc-provider.mjs", @@ -51,8 +51,6 @@ }, "devDependencies": { "@biomejs/biome": "2.3.8", - "@cucumber/cucumber": "^12.2.0", - "@cucumber/messages": "^31.0.0", "@hey-api/client-next": "0.5.1", "@hey-api/openapi-ts": "0.89.0", "@mswjs/http-middleware": "^0.10.2", @@ -66,7 +64,6 @@ "@types/react": "^19", "@types/react-dom": "^19", "@vitejs/plugin-react": "^5.1.1", - "aria-query": "^5.3.2", "babel-plugin-react-compiler": "1.0.0", "concurrently": "^9.2.1", "dotenv": "^17.2.3", @@ -75,7 +72,6 @@ "lint-staged": "^16.0.0", "oidc-provider": "^9.5.2", "tailwindcss": "^4", - "ts-node": "^10.9.2", "tsx": "4.21.0", "typescript": "^5", "vite": "^7.2.2", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 00000000..ec190fd7 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,41 @@ +import { defineConfig, devices } from "@playwright/test"; + +const BASE_URL = process.env.BASE_URL || "http://localhost:3000"; + +export default defineConfig({ + testDir: "./tests/e2e", + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: process.env.CI ? "github" : "list", + timeout: 30_000, + use: { + baseURL: BASE_URL, + trace: "on-first-retry", + screenshot: "only-on-failure", + }, + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + ], + webServer: { + command: "pnpm dev", + url: BASE_URL, + reuseExistingServer: !process.env.CI, + timeout: 120_000, + stdout: "pipe", + stderr: "pipe", + env: { + API_BASE_URL: "http://localhost:9090", + OIDC_ISSUER_URL: "http://localhost:4000", + OIDC_CLIENT_ID: "better-auth-dev", + OIDC_CLIENT_SECRET: "dev-secret-change-in-production", + NEXT_PUBLIC_OIDC_PROVIDER_ID: "okta", + BETTER_AUTH_URL: "http://localhost:3000", + BETTER_AUTH_SECRET: "e2e-test-secret-at-least-32-chars-long", + }, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 16c6456f..3daf5d67 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -78,12 +78,6 @@ importers: '@biomejs/biome': specifier: 2.3.8 version: 2.3.8 - '@cucumber/cucumber': - specifier: ^12.2.0 - version: 12.2.0 - '@cucumber/messages': - specifier: ^31.0.0 - version: 31.0.0 '@hey-api/client-next': specifier: 0.5.1 version: 0.5.1(@hey-api/openapi-ts@0.89.0(typescript@5.9.3)) @@ -123,9 +117,6 @@ importers: '@vitejs/plugin-react': specifier: ^5.1.1 version: 5.1.2(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) - aria-query: - specifier: ^5.3.2 - version: 5.3.2 babel-plugin-react-compiler: specifier: 1.0.0 version: 1.0.0 @@ -150,9 +141,6 @@ importers: tailwindcss: specifier: ^4 version: 4.1.17 - ts-node: - specifier: ^10.9.2 - version: 10.9.2(@types/node@24.10.1)(typescript@5.9.3) tsx: specifier: 4.21.0 version: 4.21.0 @@ -351,14 +339,6 @@ packages: cpu: [x64] os: [win32] - '@colors/colors@1.5.0': - resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} - engines: {node: '>=0.1.90'} - - '@cspotcode/source-map-support@0.8.1': - resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} - engines: {node: '>=12'} - '@csstools/color-helpers@5.1.0': resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} engines: {node: '>=18'} @@ -391,76 +371,6 @@ packages: resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} engines: {node: '>=18'} - '@cucumber/ci-environment@10.0.1': - resolution: {integrity: sha512-/+ooDMPtKSmvcPMDYnMZt4LuoipfFfHaYspStI4shqw8FyKcfQAmekz6G+QKWjQQrvM+7Hkljwx58MEwPCwwzg==} - - '@cucumber/cucumber-expressions@18.0.1': - resolution: {integrity: sha512-NSid6bI+7UlgMywl5octojY5NXnxR9uq+JisjOrO52VbFsQM6gTWuQFE8syI10KnIBEdPzuEUSVEeZ0VFzRnZA==} - - '@cucumber/cucumber@12.2.0': - resolution: {integrity: sha512-b7W4snvXYi1T2puUjxamASCCNhNzVSzb/fQUuGSkdjm/AFfJ24jo8kOHQyOcaoArCG71sVQci4vkZaITzl/V1w==} - engines: {node: 20 || 22 || >=24} - hasBin: true - - '@cucumber/gherkin-streams@5.0.1': - resolution: {integrity: sha512-/7VkIE/ASxIP/jd4Crlp4JHXqdNFxPGQokqWqsaCCiqBiu5qHoKMxcWNlp9njVL/n9yN4S08OmY3ZR8uC5x74Q==} - hasBin: true - peerDependencies: - '@cucumber/gherkin': '>=22.0.0' - '@cucumber/message-streams': '>=4.0.0' - '@cucumber/messages': '>=17.1.1' - - '@cucumber/gherkin-utils@9.2.0': - resolution: {integrity: sha512-3nmRbG1bUAZP3fAaUBNmqWO0z0OSkykZZotfLjyhc8KWwDSOrOmMJlBTd474lpA8EWh4JFLAX3iXgynBqBvKzw==} - hasBin: true - - '@cucumber/gherkin@31.0.0': - resolution: {integrity: sha512-wlZfdPif7JpBWJdqvHk1Mkr21L5vl4EfxVUOS4JinWGf3FLRV6IKUekBv5bb5VX79fkDcfDvESzcQ8WQc07Wgw==} - - '@cucumber/gherkin@34.0.0': - resolution: {integrity: sha512-659CCFsrsyvuBi/Eix1fnhSheMnojSfnBcqJ3IMPNawx7JlrNJDcXYSSdxcUw3n/nG05P+ptCjmiZY3i14p+tA==} - - '@cucumber/html-formatter@21.14.0': - resolution: {integrity: sha512-vQqbmQZc0QiN4c+cMCffCItpODJlOlYtPG7pH6We096dBOa7u0ttDMjT6KrMAnQlcln54rHL46r408IFpuznAw==} - peerDependencies: - '@cucumber/messages': '>=18' - - '@cucumber/junit-xml-formatter@0.8.1': - resolution: {integrity: sha512-FT1Y96pyd9/ifbE9I7dbkTCjkwEdW9C0MBobUZoKD13c8EnWAt0xl1Yy/v/WZLTk4XfCLte1DATtLx01jt+YiA==} - peerDependencies: - '@cucumber/messages': '*' - - '@cucumber/message-streams@4.0.1': - resolution: {integrity: sha512-Kxap9uP5jD8tHUZVjTWgzxemi/0uOsbGjd4LBOSxcJoOCRbESFwemUzilJuzNTB8pcTQUh8D5oudUyxfkJOKmA==} - peerDependencies: - '@cucumber/messages': '>=17.1.1' - - '@cucumber/messages@26.0.1': - resolution: {integrity: sha512-DIxSg+ZGariumO+Lq6bn4kOUIUET83A4umrnWmidjGFl8XxkBieUZtsmNbLYgH/gnsmP07EfxxdTr0hOchV1Sg==} - - '@cucumber/messages@27.2.0': - resolution: {integrity: sha512-f2o/HqKHgsqzFLdq6fAhfG1FNOQPdBdyMGpKwhb7hZqg0yZtx9BVqkTyuoNk83Fcvk3wjMVfouFXXHNEk4nddA==} - - '@cucumber/messages@28.1.0': - resolution: {integrity: sha512-2LzZtOwYKNlCuNf31ajkrekoy2M4z0Z1QGiPH40n4gf5t8VOUFb7m1ojtR4LmGvZxBGvJZP8voOmRqDWzBzYKA==} - - '@cucumber/messages@31.0.0': - resolution: {integrity: sha512-Dqhatp4AjMsH9SREfWz3Q8nlGuwJMTW7YAW5L3OzRId86ZUEu/a8vIL1RO2c0agQefuBS2SVH9fEZ66ovrMYRA==} - - '@cucumber/pretty-formatter@1.0.1': - resolution: {integrity: sha512-A1lU4VVP0aUWdOTmpdzvXOyEYuPtBDI0xYwYJnmoMDplzxMdhcHk86lyyvYDoMoPzzq6OkOE3isuosvUU4X7IQ==} - peerDependencies: - '@cucumber/cucumber': '>=7.0.0' - '@cucumber/messages': '*' - - '@cucumber/query@13.6.0': - resolution: {integrity: sha512-tiDneuD5MoWsJ9VKPBmQok31mSX9Ybl+U4wqDoXeZgsXHDURqzM3rnpWVV3bC34y9W6vuFxrlwF/m7HdOxwqRw==} - peerDependencies: - '@cucumber/messages': '*' - - '@cucumber/tag-expressions@6.2.0': - resolution: {integrity: sha512-KIF0eLcafHbWOuSDWFw0lMmgJOLdDRWjEL1kfXEWrqHmx2119HxVAr35WuEd9z542d3Yyg+XNqSr+81rIKqEdg==} - '@emnapi/runtime@1.7.0': resolution: {integrity: sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q==} @@ -986,18 +896,6 @@ packages: '@types/node': optional: true - '@isaacs/balanced-match@4.0.1': - resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} - engines: {node: 20 || >=22} - - '@isaacs/brace-expansion@5.0.0': - resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} - engines: {node: 20 || >=22} - - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -1014,9 +912,6 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@jridgewell/trace-mapping@0.3.9': - resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - '@jsdevtools/ono@7.1.3': resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} @@ -1689,10 +1584,6 @@ packages: '@tailwindcss/postcss@4.1.17': resolution: {integrity: sha512-+nKl9N9mN5uJ+M7dBOOCzINw94MPstNR/GtIhz1fpZysxL/4a+No64jCBD6CPN+bIHWFx3KWuu8XJRrj/572Dw==} - '@teppeis/multimaps@3.0.0': - resolution: {integrity: sha512-ID7fosbc50TbT0MK0EG12O+gAP3W3Aa/Pz4DaTtQtEvlc9Odaqi0de+xuZ7Li2GtK4HzEX7IuRWS/JmZLksR3Q==} - engines: {node: '>=14'} - '@testing-library/dom@10.4.1': resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} engines: {node: '>=18'} @@ -1722,18 +1613,6 @@ packages: peerDependencies: '@testing-library/dom': '>=7.21.4' - '@tsconfig/node10@1.0.12': - resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==} - - '@tsconfig/node12@1.0.11': - resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} - - '@tsconfig/node14@1.0.3': - resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} - - '@tsconfig/node16@1.0.4': - resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} @@ -1764,9 +1643,6 @@ packages: '@types/node@24.10.1': resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} - '@types/normalize-package-data@2.4.4': - resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} - '@types/react-dom@19.2.3': resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} peerDependencies: @@ -1778,9 +1654,6 @@ packages: '@types/statuses@2.0.6': resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} - '@types/uuid@10.0.0': - resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} - '@vitejs/plugin-react@5.1.2': resolution: {integrity: sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1820,15 +1693,6 @@ packages: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} - acorn-walk@8.3.4: - resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} - engines: {node: '>=0.4.0'} - - acorn@8.15.0: - resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} - engines: {node: '>=0.4.0'} - hasBin: true - agent-base@7.1.4: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} @@ -1852,10 +1716,6 @@ packages: resolution: {integrity: sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==} engines: {node: '>=18'} - ansi-regex@4.1.1: - resolution: {integrity: sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==} - engines: {node: '>=6'} - ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -1876,12 +1736,6 @@ packages: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} - any-promise@1.3.0: - resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - - arg@4.1.3: - resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} - argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -1902,9 +1756,6 @@ packages: array-flatten@1.1.1: resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} - assertion-error-formatter@3.0.0: - resolution: {integrity: sha512-6YyAVLrEze0kQ7CmJfUgrLHb+Y7XghmL2Ie7ijVa2Y9ynP3LV+VDiwFk62Dn0qtqbmY0BT0ss6p1xxpiF2PYbQ==} - assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -1972,9 +1823,6 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - bundle-name@4.1.0: resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} engines: {node: '>=18'} @@ -2005,9 +1853,6 @@ packages: caniuse-lite@1.0.30001760: resolution: {integrity: sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==} - capital-case@1.0.4: - resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==} - chai@6.2.1: resolution: {integrity: sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==} engines: {node: '>=18'} @@ -2023,9 +1868,6 @@ packages: citty@0.1.6: resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} - class-transformer@0.5.1: - resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==} - class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} @@ -2033,10 +1875,6 @@ packages: resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} engines: {node: '>=18'} - cli-table3@0.6.5: - resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} - engines: {node: 10.* || >= 12.*} - cli-truncate@5.1.1: resolution: {integrity: sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==} engines: {node: '>=20'} @@ -2070,18 +1908,10 @@ packages: colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} - commander@13.1.0: - resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} - engines: {node: '>=18'} - commander@14.0.2: resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==} engines: {node: '>=20'} - commander@9.1.0: - resolution: {integrity: sha512-i0/MaqBtdbnJ4XQs4Pmyb+oFQl+q0lsAmokVUH92SlSw4fkeAcG3bVon+Qt7hmtF+u3Het6o4VgrcY3qAoEB6w==} - engines: {node: ^12.20.0 || >=14} - concurrently@9.2.1: resolution: {integrity: sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==} engines: {node: '>=18'} @@ -2120,13 +1950,6 @@ packages: resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==} engines: {node: '>= 0.8'} - create-require@1.1.1: - resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} - css-tree@3.1.0: resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} @@ -2212,10 +2035,6 @@ packages: detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} - diff@4.0.2: - resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} - engines: {node: '>=0.3.1'} - dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} @@ -2230,9 +2049,6 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -2245,9 +2061,6 @@ packages: emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - encodeurl@1.0.2: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} @@ -2268,9 +2081,6 @@ packages: resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} engines: {node: '>=18'} - error-stack-parser@2.1.4: - resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==} - es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -2303,10 +2113,6 @@ packages: escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} @@ -2352,10 +2158,6 @@ packages: picomatch: optional: true - figures@3.2.0: - resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} - engines: {node: '>=8'} - fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -2364,14 +2166,6 @@ packages: resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} engines: {node: '>= 0.8'} - find-up-simple@1.0.1: - resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} - engines: {node: '>=18'} - - foreground-child@3.3.1: - resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} - engines: {node: '>=14'} - format-util@1.0.5: resolution: {integrity: sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg==} @@ -2427,15 +2221,6 @@ packages: resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} hasBin: true - glob@11.1.0: - resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==} - engines: {node: 20 || >=22} - hasBin: true - - global-dirs@3.0.1: - resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} - engines: {node: '>=10'} - globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} @@ -2450,10 +2235,6 @@ packages: resolution: {integrity: sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} - has-ansi@4.0.1: - resolution: {integrity: sha512-Qr4RtTm30xvEdqUXbSBVWDu+PrTokJOwe/FU+VdfJPk+MXAPoeOzKpRyrDTnZIJwAkQ4oBLTU53nu0HrkF/Z2A==} - engines: {node: '>=8'} - has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -2469,10 +2250,6 @@ packages: headers-polyfill@4.0.3: resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==} - hosted-git-info@7.0.2: - resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} - engines: {node: ^16.14.0 || >=18.0.0} - html-encoding-sniffer@4.0.0: resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} engines: {node: '>=18'} @@ -2518,17 +2295,9 @@ packages: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} - index-to-position@1.2.0: - resolution: {integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==} - engines: {node: '>=18'} - inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - ini@2.0.0: - resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==} - engines: {node: '>=10'} - ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} @@ -2555,10 +2324,6 @@ packages: engines: {node: '>=14.16'} hasBin: true - is-installed-globally@0.4.0: - resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==} - engines: {node: '>=10'} - is-node-process@1.2.0: resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} @@ -2566,28 +2331,13 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} - is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} - is-wsl@3.1.0: resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} engines: {node: '>=16'} - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - jackspeak@4.1.1: - resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} - engines: {node: 20 || >=22} - jiti@2.6.1: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true @@ -2650,9 +2400,6 @@ packages: engines: {node: '>= 0.6'} deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - knuth-shuffle-seeded@1.0.6: - resolution: {integrity: sha512-9pFH0SplrfyKyojCLxZfMcvkhf5hH0d+UwR9nTVJ/DDQJGuzcXjTwB7TP7sDfehSudlGGaOLblmEWqv04ERVWg==} - koa-compose@4.1.0: resolution: {integrity: sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==} @@ -2743,15 +2490,6 @@ packages: resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==} engines: {node: '>=20.0.0'} - lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - - lodash.mergewith@4.6.2: - resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==} - - lodash.sortby@4.7.0: - resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} - lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -2759,12 +2497,6 @@ packages: resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} engines: {node: '>=18'} - lower-case@2.0.2: - resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} - - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.2.2: resolution: {integrity: sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==} engines: {node: 20 || >=22} @@ -2777,10 +2509,6 @@ packages: peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 - luxon@3.7.1: - resolution: {integrity: sha512-RkRWjA926cTvz5rAb1BqyWkKbbjzCGchDUIKMCUvNi17j6f6j8uHGDV82Aqcqtzd+icoYpELmG3ksgGiFNNcNg==} - engines: {node: '>=12'} - lz-string@1.5.0: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true @@ -2788,9 +2516,6 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - make-error@1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -2838,11 +2563,6 @@ packages: engines: {node: '>=4'} hasBin: true - mime@3.0.0: - resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} - engines: {node: '>=10.0.0'} - hasBin: true - mimic-function@5.0.1: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} @@ -2851,19 +2571,6 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} - minimatch@10.1.1: - resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} - engines: {node: 20 || >=22} - - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} - engines: {node: '>=16 || 14 >=14.17'} - - mkdirp@3.0.1: - resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} - engines: {node: '>=10'} - hasBin: true - ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} @@ -2888,9 +2595,6 @@ packages: resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} engines: {node: ^18.17.0 || >=20.5.0} - mz@2.7.0: - resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - nano-spawn@2.0.0: resolution: {integrity: sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==} engines: {node: '>=20.17'} @@ -2940,19 +2644,12 @@ packages: sass: optional: true - no-case@3.0.4: - resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} - node-fetch-native@1.6.7: resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} - normalize-package-data@6.0.2: - resolution: {integrity: sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==} - engines: {node: ^16.14.0 || >=18.0.0} - nuqs@2.8.2: resolution: {integrity: sha512-KMb6gmUJaLVRw+SbKUmBTo0IWLGU2s1Z4Iz/N64+EIDcu6Iw51CuppgKmxZR2EW3iXaOz5LF4avGKD2wq45eqg==} peerDependencies: @@ -2979,10 +2676,6 @@ packages: engines: {node: ^14.16.0 || >=16.10.0} hasBin: true - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - object-inspect@1.13.4: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} @@ -3014,17 +2707,6 @@ packages: outvariant@1.4.3: resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - - pad-right@0.2.2: - resolution: {integrity: sha512-4cy8M95ioIGolCoMmm2cMntGR1lPLEbOMzOKu8bzjuJP6JpzEMQcDHmh7hHLYGgob+nKe1YHFMaG4V59HQa89g==} - engines: {node: '>=0.10.0'} - - parse-json@8.3.0: - resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} - engines: {node: '>=18'} - parse5@8.0.0: resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==} @@ -3032,14 +2714,6 @@ packages: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-scurry@2.0.1: - resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} - engines: {node: 20 || >=22} - path-to-regexp@0.1.12: resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} @@ -3100,13 +2774,6 @@ packages: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - progress@2.0.3: - resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} - engines: {node: '>=0.4.0'} - - property-expr@2.0.6: - resolution: {integrity: sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==} - proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -3184,14 +2851,6 @@ packages: resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==} engines: {node: '>=0.10.0'} - read-package-up@11.0.0: - resolution: {integrity: sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==} - engines: {node: '>=18'} - - read-pkg@9.0.1: - resolution: {integrity: sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==} - engines: {node: '>=18'} - readdirp@4.1.2: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} @@ -3200,20 +2859,6 @@ packages: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} - reflect-metadata@0.2.2: - resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} - - regexp-match-indices@1.0.2: - resolution: {integrity: sha512-DwZuAkt8NF5mKwGGER1EGh2PRqyvhRhhLviH+R8y8dIuaQROlUfXjt4s9ZTXstIsSkptf06BSvwcEmmfheJJWQ==} - - regexp-tree@0.1.27: - resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} - hasBin: true - - repeat-string@1.6.1: - resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} - engines: {node: '>=0.10'} - require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -3263,9 +2908,6 @@ packages: scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} - seed-random@2.2.0: - resolution: {integrity: sha512-34EQV6AAHQGhoc0tn/96a9Fsi6v2xdqe/dMUwljGRaFOzR3EgRmECvD0O8vi8X+/uQ50LGHfkNu/Eue5TPKZkQ==} - semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -3298,14 +2940,6 @@ packages: 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'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - shell-quote@1.8.3: resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} engines: {node: '>= 0.4'} @@ -3347,34 +2981,12 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} - source-map-support@0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - - source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - - spdx-correct@3.2.0: - resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} - - spdx-exceptions@2.5.0: - resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} - - spdx-expression-parse@3.0.1: - resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} - - spdx-license-ids@3.0.22: - resolution: {integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==} - sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - stackframe@1.3.4: - resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} - statuses@1.5.0: resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} engines: {node: '>= 0.6'} @@ -3393,10 +3005,6 @@ packages: strict-event-emitter@0.5.1: resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} - string-argv@0.3.1: - resolution: {integrity: sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==} - engines: {node: '>=0.6.19'} - string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} @@ -3405,10 +3013,6 @@ packages: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - string-width@7.2.0: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} @@ -3467,16 +3071,6 @@ packages: resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} engines: {node: '>=6'} - thenify-all@1.6.0: - resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} - engines: {node: '>=0.8'} - - thenify@3.3.1: - resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - - tiny-case@1.0.3: - resolution: {integrity: sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==} - tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -3507,9 +3101,6 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} - toposort@2.0.2: - resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==} - tough-cookie@6.0.0: resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==} engines: {node: '>=16'} @@ -3522,24 +3113,6 @@ packages: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true - ts-dedent@2.2.0: - resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} - engines: {node: '>=6.10'} - - ts-node@10.9.2: - resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} - hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true - tsconfck@3.1.6: resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} engines: {node: ^18 || >=20} @@ -3562,14 +3135,6 @@ packages: engines: {node: '>=18.0.0'} hasBin: true - type-fest@2.19.0: - resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} - engines: {node: '>=12.20'} - - type-fest@4.41.0: - resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} - engines: {node: '>=16'} - type-fest@5.2.0: resolution: {integrity: sha512-xxCJm+Bckc6kQBknN7i9fnP/xobQRsRQxR01CztFkp/h++yfVxUUcmMgfR2HttJx/dpWjS9ubVuyspJv24Q9DA==} engines: {node: '>=20'} @@ -3590,10 +3155,6 @@ packages: undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} - unicorn-magic@0.1.0: - resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} - engines: {node: '>=18'} - unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} @@ -3607,9 +3168,6 @@ packages: peerDependencies: browserslist: '>= 4.21.0' - upper-case-first@2.0.2: - resolution: {integrity: sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==} - use-callback-ref@1.3.3: resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} engines: {node: '>=10'} @@ -3635,31 +3193,10 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - util-arity@1.1.0: - resolution: {integrity: sha512-kkyIsXKwemfSy8ZEoaIz06ApApnWsk5hQO0vLjZS6UkBiGiW++Jsyb8vSBoc0WKlffGoGs5yYy/j5pp8zckrFA==} - utils-merge@1.0.1: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} - uuid@10.0.0: - resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} - hasBin: true - - uuid@11.0.5: - resolution: {integrity: sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==} - hasBin: true - - uuid@11.1.0: - resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} - hasBin: true - - v8-compile-cache-lib@3.0.1: - resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} - - validate-npm-package-license@3.0.4: - resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} - vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} @@ -3766,11 +3303,6 @@ packages: resolution: {integrity: sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==} engines: {node: '>=20'} - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} @@ -3784,10 +3316,6 @@ packages: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - wrap-ansi@9.0.2: resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} engines: {node: '>=18'} @@ -3812,10 +3340,6 @@ packages: resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} engines: {node: '>=18'} - xmlbuilder@15.1.1: - resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==} - engines: {node: '>=8.0'} - xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} @@ -3839,17 +3363,10 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} - yn@3.1.1: - resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} - engines: {node: '>=6'} - yoctocolors-cjs@2.1.3: resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} engines: {node: '>=18'} - yup@1.7.0: - resolution: {integrity: sha512-VJce62dBd+JQvoc+fCVq+KZfPHr+hXaxCcVgotfwWvlR0Ja3ffYKaJBT8rptPOSKOGJDCUnW2C2JWpud7aRP6Q==} - zod@4.1.12: resolution: {integrity: sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==} @@ -3900,7 +3417,7 @@ snapshots: '@babel/types': 7.28.5 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -3984,7 +3501,7 @@ snapshots: '@babel/parser': 7.28.5 '@babel/template': 7.27.2 '@babel/types': 7.28.5 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -4049,13 +3566,6 @@ snapshots: '@biomejs/cli-win32-x64@2.3.8': optional: true - '@colors/colors@1.5.0': - optional: true - - '@cspotcode/source-map-support@0.8.1': - dependencies: - '@jridgewell/trace-mapping': 0.3.9 - '@csstools/color-helpers@5.1.0': {} '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': @@ -4078,138 +3588,6 @@ snapshots: '@csstools/css-tokenizer@3.0.4': {} - '@cucumber/ci-environment@10.0.1': {} - - '@cucumber/cucumber-expressions@18.0.1': - dependencies: - regexp-match-indices: 1.0.2 - - '@cucumber/cucumber@12.2.0': - dependencies: - '@cucumber/ci-environment': 10.0.1 - '@cucumber/cucumber-expressions': 18.0.1 - '@cucumber/gherkin': 34.0.0 - '@cucumber/gherkin-streams': 5.0.1(@cucumber/gherkin@34.0.0)(@cucumber/message-streams@4.0.1(@cucumber/messages@28.1.0))(@cucumber/messages@28.1.0) - '@cucumber/gherkin-utils': 9.2.0 - '@cucumber/html-formatter': 21.14.0(@cucumber/messages@28.1.0) - '@cucumber/junit-xml-formatter': 0.8.1(@cucumber/messages@28.1.0) - '@cucumber/message-streams': 4.0.1(@cucumber/messages@31.0.0) - '@cucumber/messages': 28.1.0 - '@cucumber/pretty-formatter': 1.0.1(@cucumber/cucumber@12.2.0)(@cucumber/messages@28.1.0) - '@cucumber/tag-expressions': 6.2.0 - assertion-error-formatter: 3.0.0 - capital-case: 1.0.4 - chalk: 4.1.2 - cli-table3: 0.6.5 - commander: 14.0.2 - debug: 4.4.3(supports-color@8.1.1) - error-stack-parser: 2.1.4 - figures: 3.2.0 - glob: 11.1.0 - has-ansi: 4.0.1 - indent-string: 4.0.0 - is-installed-globally: 0.4.0 - is-stream: 2.0.1 - knuth-shuffle-seeded: 1.0.6 - lodash.merge: 4.6.2 - lodash.mergewith: 4.6.2 - luxon: 3.7.1 - mime: 3.0.0 - mkdirp: 3.0.1 - mz: 2.7.0 - progress: 2.0.3 - read-package-up: 11.0.0 - semver: 7.7.2 - string-argv: 0.3.1 - supports-color: 8.1.1 - type-fest: 4.41.0 - util-arity: 1.1.0 - yaml: 2.8.1 - yup: 1.7.0 - - '@cucumber/gherkin-streams@5.0.1(@cucumber/gherkin@34.0.0)(@cucumber/message-streams@4.0.1(@cucumber/messages@28.1.0))(@cucumber/messages@28.1.0)': - dependencies: - '@cucumber/gherkin': 34.0.0 - '@cucumber/message-streams': 4.0.1(@cucumber/messages@31.0.0) - '@cucumber/messages': 28.1.0 - commander: 9.1.0 - source-map-support: 0.5.21 - - '@cucumber/gherkin-utils@9.2.0': - dependencies: - '@cucumber/gherkin': 31.0.0 - '@cucumber/messages': 27.2.0 - '@teppeis/multimaps': 3.0.0 - commander: 13.1.0 - source-map-support: 0.5.21 - - '@cucumber/gherkin@31.0.0': - dependencies: - '@cucumber/messages': 26.0.1 - - '@cucumber/gherkin@34.0.0': - dependencies: - '@cucumber/messages': 28.1.0 - - '@cucumber/html-formatter@21.14.0(@cucumber/messages@28.1.0)': - dependencies: - '@cucumber/messages': 28.1.0 - - '@cucumber/junit-xml-formatter@0.8.1(@cucumber/messages@28.1.0)': - dependencies: - '@cucumber/messages': 28.1.0 - '@cucumber/query': 13.6.0(@cucumber/messages@28.1.0) - '@teppeis/multimaps': 3.0.0 - luxon: 3.7.1 - xmlbuilder: 15.1.1 - - '@cucumber/message-streams@4.0.1(@cucumber/messages@31.0.0)': - dependencies: - '@cucumber/messages': 31.0.0 - - '@cucumber/messages@26.0.1': - dependencies: - '@types/uuid': 10.0.0 - class-transformer: 0.5.1 - reflect-metadata: 0.2.2 - uuid: 10.0.0 - - '@cucumber/messages@27.2.0': - dependencies: - '@types/uuid': 10.0.0 - class-transformer: 0.5.1 - reflect-metadata: 0.2.2 - uuid: 11.0.5 - - '@cucumber/messages@28.1.0': - dependencies: - '@types/uuid': 10.0.0 - class-transformer: 0.5.1 - reflect-metadata: 0.2.2 - uuid: 11.1.0 - - '@cucumber/messages@31.0.0': - dependencies: - class-transformer: 0.5.1 - reflect-metadata: 0.2.2 - - '@cucumber/pretty-formatter@1.0.1(@cucumber/cucumber@12.2.0)(@cucumber/messages@28.1.0)': - dependencies: - '@cucumber/cucumber': 12.2.0 - '@cucumber/messages': 28.1.0 - ansi-styles: 5.2.0 - cli-table3: 0.6.5 - figures: 3.2.0 - ts-dedent: 2.2.0 - - '@cucumber/query@13.6.0(@cucumber/messages@28.1.0)': - dependencies: - '@cucumber/messages': 28.1.0 - '@teppeis/multimaps': 3.0.0 - lodash.sortby: 4.7.0 - - '@cucumber/tag-expressions@6.2.0': {} - '@emnapi/runtime@1.7.0': dependencies: tslib: 2.8.1 @@ -4544,21 +3922,6 @@ snapshots: optionalDependencies: '@types/node': 24.10.1 - '@isaacs/balanced-match@4.0.1': {} - - '@isaacs/brace-expansion@5.0.0': - dependencies: - '@isaacs/balanced-match': 4.0.1 - - '@isaacs/cliui@8.0.2': - dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.2 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 - '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -4578,11 +3941,6 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping@0.3.9': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - '@jsdevtools/ono@7.1.3': {} '@jsep-plugin/assignment@1.3.0(jsep@1.4.0)': @@ -4599,7 +3957,7 @@ snapshots: '@koa/router@14.0.0': dependencies: - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3 http-errors: 2.0.0 koa-compose: 4.1.0 path-to-regexp: 8.3.0 @@ -5133,8 +4491,6 @@ snapshots: postcss: 8.5.6 tailwindcss: 4.1.17 - '@teppeis/multimaps@3.0.0': {} - '@testing-library/dom@10.4.1': dependencies: '@babel/code-frame': 7.27.1 @@ -5169,14 +4525,6 @@ snapshots: dependencies: '@testing-library/dom': 10.4.1 - '@tsconfig/node10@1.0.12': {} - - '@tsconfig/node12@1.0.11': {} - - '@tsconfig/node14@1.0.3': {} - - '@tsconfig/node16@1.0.4': {} - '@types/aria-query@5.0.4': {} '@types/babel__core@7.20.5': @@ -5215,8 +4563,6 @@ snapshots: dependencies: undici-types: 7.16.0 - '@types/normalize-package-data@2.4.4': {} - '@types/react-dom@19.2.3(@types/react@19.2.7)': dependencies: '@types/react': 19.2.7 @@ -5227,8 +4573,6 @@ snapshots: '@types/statuses@2.0.6': {} - '@types/uuid@10.0.0': {} - '@vitejs/plugin-react@5.1.2(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1))': dependencies: '@babel/core': 7.28.5 @@ -5286,12 +4630,6 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 - acorn-walk@8.3.4: - dependencies: - acorn: 8.15.0 - - acorn@8.15.0: {} - agent-base@7.1.4: {} ajv-formats@3.0.1(ajv@8.17.1): @@ -5311,8 +4649,6 @@ snapshots: dependencies: environment: 1.1.0 - ansi-regex@4.1.1: {} - ansi-regex@5.0.1: {} ansi-regex@6.2.2: {} @@ -5325,10 +4661,6 @@ snapshots: ansi-styles@6.2.3: {} - any-promise@1.3.0: {} - - arg@4.1.3: {} - argparse@1.0.10: dependencies: sprintf-js: 1.0.3 @@ -5347,12 +4679,6 @@ snapshots: array-flatten@1.1.1: {} - assertion-error-formatter@3.0.0: - dependencies: - diff: 4.0.2 - pad-right: 0.2.2 - repeat-string: 1.6.1 - assertion-error@2.0.1: {} babel-plugin-react-compiler@1.0.0: @@ -5423,8 +4749,6 @@ snapshots: node-releases: 2.0.27 update-browserslist-db: 1.2.2(browserslist@4.28.1) - buffer-from@1.1.2: {} - bundle-name@4.1.0: dependencies: run-applescript: 7.1.0 @@ -5460,12 +4784,6 @@ snapshots: caniuse-lite@1.0.30001760: {} - capital-case@1.0.4: - dependencies: - no-case: 3.0.4 - tslib: 2.8.1 - upper-case-first: 2.0.2 - chai@6.2.1: {} chalk@4.1.2: @@ -5481,8 +4799,6 @@ snapshots: dependencies: consola: 3.4.2 - class-transformer@0.5.1: {} - class-variance-authority@0.7.1: dependencies: clsx: 2.1.1 @@ -5491,12 +4807,6 @@ snapshots: dependencies: restore-cursor: 5.1.0 - cli-table3@0.6.5: - dependencies: - string-width: 4.2.3 - optionalDependencies: - '@colors/colors': 1.5.0 - cli-truncate@5.1.1: dependencies: slice-ansi: 7.1.2 @@ -5524,12 +4834,8 @@ snapshots: colorette@2.0.20: {} - commander@13.1.0: {} - commander@14.0.2: {} - commander@9.1.0: {} - concurrently@9.2.1: dependencies: chalk: 4.1.2 @@ -5562,14 +4868,6 @@ snapshots: depd: 2.0.0 keygrip: 1.1.0 - create-require@1.1.1: {} - - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - css-tree@3.1.0: dependencies: mdn-data: 2.12.2 @@ -5594,11 +4892,9 @@ snapshots: dependencies: ms: 2.0.0 - debug@4.4.3(supports-color@8.1.1): + debug@4.4.3: dependencies: ms: 2.1.3 - optionalDependencies: - supports-color: 8.1.1 decimal.js@10.6.0: {} @@ -5631,8 +4927,6 @@ snapshots: detect-node-es@1.1.0: {} - diff@4.0.2: {} - dom-accessibility-api@0.5.16: {} dom-accessibility-api@0.6.3: {} @@ -5645,8 +4939,6 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - eastasianwidth@0.2.0: {} - ee-first@1.1.1: {} electron-to-chromium@1.5.267: {} @@ -5655,8 +4947,6 @@ snapshots: emoji-regex@8.0.0: {} - emoji-regex@9.2.2: {} - encodeurl@1.0.2: {} encodeurl@2.0.0: {} @@ -5670,10 +4960,6 @@ snapshots: environment@1.1.0: {} - error-stack-parser@2.1.4: - dependencies: - stackframe: 1.3.4 - es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -5746,8 +5032,6 @@ snapshots: escape-html@1.0.3: {} - escape-string-regexp@1.0.5: {} - esprima@4.0.1: {} estree-walker@3.0.3: @@ -5808,10 +5092,6 @@ snapshots: optionalDependencies: picomatch: 4.0.3 - figures@3.2.0: - dependencies: - escape-string-regexp: 1.0.5 - fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -5828,13 +5108,6 @@ snapshots: transitivePeerDependencies: - supports-color - find-up-simple@1.0.1: {} - - foreground-child@3.3.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - format-util@1.0.5: {} forwarded@0.2.0: {} @@ -5888,19 +5161,6 @@ snapshots: nypm: 0.6.2 pathe: 2.0.3 - glob@11.1.0: - dependencies: - foreground-child: 3.3.1 - jackspeak: 4.1.1 - minimatch: 10.1.1 - minipass: 7.1.2 - package-json-from-dist: 1.0.1 - path-scurry: 2.0.1 - - global-dirs@3.0.1: - dependencies: - ini: 2.0.0 - globrex@0.1.2: {} gopd@1.2.0: {} @@ -5909,10 +5169,6 @@ snapshots: graphql@16.12.0: {} - has-ansi@4.0.1: - dependencies: - ansi-regex: 4.1.1 - has-flag@4.0.0: {} has-symbols@1.1.0: {} @@ -5923,10 +5179,6 @@ snapshots: headers-polyfill@4.0.3: {} - hosted-git-info@7.0.2: - dependencies: - lru-cache: 10.4.3 - html-encoding-sniffer@4.0.0: dependencies: whatwg-encoding: 3.1.1 @@ -5955,14 +5207,14 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.4 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3 transitivePeerDependencies: - supports-color https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.4 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -5982,12 +5234,8 @@ snapshots: indent-string@4.0.0: {} - index-to-position@1.2.0: {} - inherits@2.0.4: {} - ini@2.0.0: {} - ipaddr.js@1.9.1: {} is-docker@3.0.0: {} @@ -6004,31 +5252,16 @@ snapshots: dependencies: is-docker: 3.0.0 - is-installed-globally@0.4.0: - dependencies: - global-dirs: 3.0.1 - is-path-inside: 3.0.3 - is-node-process@1.2.0: {} is-number@7.0.0: {} - is-path-inside@3.0.3: {} - is-potential-custom-element-name@1.0.1: {} - is-stream@2.0.1: {} - is-wsl@3.1.0: dependencies: is-inside-container: 1.0.0 - isexe@2.0.0: {} - - jackspeak@4.1.1: - dependencies: - '@isaacs/cliui': 8.0.2 - jiti@2.6.1: {} jose@6.1.3: {} @@ -6100,10 +5333,6 @@ snapshots: dependencies: tsscmp: 1.0.6 - knuth-shuffle-seeded@1.0.6: - dependencies: - seed-random: 2.2.0 - koa-compose@4.1.0: {} koa@3.1.1: @@ -6197,12 +5426,6 @@ snapshots: rfdc: 1.4.1 wrap-ansi: 9.0.2 - lodash.merge@4.6.2: {} - - lodash.mergewith@4.6.2: {} - - lodash.sortby@4.7.0: {} - lodash@4.17.21: {} log-update@6.1.0: @@ -6213,12 +5436,6 @@ snapshots: strip-ansi: 7.1.2 wrap-ansi: 9.0.2 - lower-case@2.0.2: - dependencies: - tslib: 2.8.1 - - lru-cache@10.4.3: {} - lru-cache@11.2.2: {} lru-cache@5.1.1: @@ -6229,16 +5446,12 @@ snapshots: dependencies: react: 19.2.3 - luxon@3.7.1: {} - lz-string@1.5.0: {} magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - make-error@1.3.6: {} - math-intrinsics@1.1.0: {} mdn-data@2.12.2: {} @@ -6270,20 +5483,10 @@ snapshots: mime@1.6.0: {} - mime@3.0.0: {} - mimic-function@5.0.1: {} min-indent@1.0.1: {} - minimatch@10.1.1: - dependencies: - '@isaacs/brace-expansion': 5.0.0 - - minipass@7.1.2: {} - - mkdirp@3.0.1: {} - ms@2.0.0: {} ms@2.1.3: {} @@ -6317,12 +5520,6 @@ snapshots: mute-stream@2.0.0: {} - mz@2.7.0: - dependencies: - any-promise: 1.3.0 - object-assign: 4.1.1 - thenify-all: 1.6.0 - nano-spawn@2.0.0: {} nanoid@3.3.11: {} @@ -6363,21 +5560,10 @@ snapshots: - '@babel/core' - babel-plugin-macros - no-case@3.0.4: - dependencies: - lower-case: 2.0.2 - tslib: 2.8.1 - node-fetch-native@1.6.7: {} node-releases@2.0.27: {} - normalize-package-data@6.0.2: - dependencies: - hosted-git-info: 7.0.2 - semver: 7.7.3 - validate-npm-package-license: 3.0.4 - nuqs@2.8.2(next@16.0.10(@babel/core@7.28.5)(@playwright/test@1.57.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3): dependencies: '@standard-schema/spec': 1.0.0 @@ -6393,8 +5579,6 @@ snapshots: pkg-types: 2.3.0 tinyexec: 1.0.2 - object-assign@4.1.1: {} - object-inspect@1.13.4: {} obug@2.1.1: {} @@ -6405,7 +5589,7 @@ snapshots: dependencies: '@koa/cors': 5.0.0 '@koa/router': 14.0.0 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3 eta: 4.4.1 jose: 6.1.3 jsesc: 3.1.0 @@ -6439,31 +5623,12 @@ snapshots: outvariant@1.4.3: {} - package-json-from-dist@1.0.1: {} - - pad-right@0.2.2: - dependencies: - repeat-string: 1.6.1 - - parse-json@8.3.0: - dependencies: - '@babel/code-frame': 7.27.1 - index-to-position: 1.2.0 - type-fest: 4.41.0 - parse5@8.0.0: dependencies: entities: 6.0.1 parseurl@1.3.3: {} - path-key@3.1.1: {} - - path-scurry@2.0.1: - dependencies: - lru-cache: 11.2.2 - minipass: 7.1.2 - path-to-regexp@0.1.12: {} path-to-regexp@6.3.0: {} @@ -6516,10 +5681,6 @@ snapshots: ansi-styles: 5.2.0 react-is: 17.0.2 - progress@2.0.3: {} - - property-expr@2.0.6: {} - proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -6592,20 +5753,6 @@ snapshots: react@19.2.3: {} - read-package-up@11.0.0: - dependencies: - find-up-simple: 1.0.1 - read-pkg: 9.0.1 - type-fest: 4.41.0 - - read-pkg@9.0.1: - dependencies: - '@types/normalize-package-data': 2.4.4 - normalize-package-data: 6.0.2 - parse-json: 8.3.0 - type-fest: 4.41.0 - unicorn-magic: 0.1.0 - readdirp@4.1.2: {} redent@3.0.0: @@ -6613,16 +5760,6 @@ snapshots: indent-string: 4.0.0 strip-indent: 3.0.0 - reflect-metadata@0.2.2: {} - - regexp-match-indices@1.0.2: - dependencies: - regexp-tree: 0.1.27 - - regexp-tree@0.1.27: {} - - repeat-string@1.6.1: {} - require-directory@2.1.1: {} require-from-string@2.0.2: {} @@ -6684,13 +5821,12 @@ snapshots: scheduler@0.27.0: {} - seed-random@2.2.0: {} - semver@6.3.1: {} semver@7.7.2: {} - semver@7.7.3: {} + semver@7.7.3: + optional: true send@0.19.0: dependencies: @@ -6755,12 +5891,6 @@ snapshots: '@img/sharp-win32-x64': 0.34.5 optional: true - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - shell-quote@1.8.3: {} side-channel-list@1.0.0: @@ -6807,33 +5937,10 @@ snapshots: source-map-js@1.2.1: {} - source-map-support@0.5.21: - dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 - - source-map@0.6.1: {} - - spdx-correct@3.2.0: - dependencies: - spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.22 - - spdx-exceptions@2.5.0: {} - - spdx-expression-parse@3.0.1: - dependencies: - spdx-exceptions: 2.5.0 - spdx-license-ids: 3.0.22 - - spdx-license-ids@3.0.22: {} - sprintf-js@1.0.3: {} stackback@0.0.2: {} - stackframe@1.3.4: {} - statuses@1.5.0: {} statuses@2.0.1: {} @@ -6844,8 +5951,6 @@ snapshots: strict-event-emitter@0.5.1: {} - string-argv@0.3.1: {} - string-argv@0.3.2: {} string-width@4.2.3: @@ -6854,12 +5959,6 @@ snapshots: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - string-width@5.1.2: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.1.2 - string-width@7.2.0: dependencies: emoji-regex: 10.6.0 @@ -6908,16 +6007,6 @@ snapshots: tapable@2.3.0: {} - thenify-all@1.6.0: - dependencies: - thenify: 3.3.1 - - thenify@3.3.1: - dependencies: - any-promise: 1.3.0 - - tiny-case@1.0.3: {} - tinybench@2.9.0: {} tinyexec@1.0.2: {} @@ -6941,8 +6030,6 @@ snapshots: toidentifier@1.0.1: {} - toposort@2.0.2: {} - tough-cookie@6.0.0: dependencies: tldts: 7.0.19 @@ -6953,26 +6040,6 @@ snapshots: tree-kill@1.2.2: {} - ts-dedent@2.2.0: {} - - ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3): - dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.12 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 - '@types/node': 24.10.1 - acorn: 8.15.0 - acorn-walk: 8.3.4 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.2 - make-error: 1.3.6 - typescript: 5.9.3 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - tsconfck@3.1.6(typescript@5.9.3): optionalDependencies: typescript: 5.9.3 @@ -6988,10 +6055,6 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - type-fest@2.19.0: {} - - type-fest@4.41.0: {} - type-fest@5.2.0: dependencies: tagged-tag: 1.0.0 @@ -7011,8 +6074,6 @@ snapshots: undici-types@7.16.0: {} - unicorn-magic@0.1.0: {} - unpipe@1.0.0: {} until-async@3.0.2: {} @@ -7023,10 +6084,6 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 - upper-case-first@2.0.2: - dependencies: - tslib: 2.8.1 - use-callback-ref@1.3.3(@types/react@19.2.7)(react@19.2.3): dependencies: react: 19.2.3 @@ -7046,28 +6103,13 @@ snapshots: dependencies: react: 19.2.3 - util-arity@1.1.0: {} - utils-merge@1.0.1: {} - uuid@10.0.0: {} - - uuid@11.0.5: {} - - uuid@11.1.0: {} - - v8-compile-cache-lib@3.0.1: {} - - validate-npm-package-license@3.0.4: - dependencies: - spdx-correct: 3.2.0 - spdx-expression-parse: 3.0.1 - vary@1.1.2: {} vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)): dependencies: - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.9.3) optionalDependencies: @@ -7147,10 +6189,6 @@ snapshots: tr46: 6.0.0 webidl-conversions: 8.0.0 - which@2.0.2: - dependencies: - isexe: 2.0.0 - why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 @@ -7168,12 +6206,6 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 - wrap-ansi@8.1.0: - dependencies: - ansi-styles: 6.2.3 - string-width: 5.1.2 - strip-ansi: 7.1.2 - wrap-ansi@9.0.2: dependencies: ansi-styles: 6.2.3 @@ -7189,8 +6221,6 @@ snapshots: xml-name-validator@5.0.0: {} - xmlbuilder@15.1.1: {} - xmlchars@2.2.0: {} y18n@5.0.8: {} @@ -7211,15 +6241,6 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 - yn@3.1.1: {} - yoctocolors-cjs@2.1.3: {} - yup@1.7.0: - dependencies: - property-expr: 2.0.6 - tiny-case: 1.0.3 - toposort: 2.0.2 - type-fest: 2.19.0 - zod@4.1.12: {} diff --git a/tests/bdd/features/catalog.feature b/tests/bdd/features/catalog.feature deleted file mode 100644 index 418178cb..00000000 --- a/tests/bdd/features/catalog.feature +++ /dev/null @@ -1,6 +0,0 @@ -Feature: Catalog page - - Scenario: View catalog page header - Given I am logged in - And I am on "/catalog" - Then I should see an "MCP Server Catalog" heading diff --git a/tests/bdd/features/login.feature b/tests/bdd/features/login.feature deleted file mode 100644 index 42bbbc3e..00000000 --- a/tests/bdd/features/login.feature +++ /dev/null @@ -1,14 +0,0 @@ -Feature: Login flow - - Scenario: Sign in and land on Catalog - Given I am on "/signin" - When I click on the "Okta" button - Then I should be on "/catalog" - And I should see an "MCP Server Catalog" heading - - Scenario: Log out from Catalog - Given I am logged in - And I am on "/catalog" - When I click on the "Test User" button - And I click on the "Sign out" menu item - Then I should be on "/signin" diff --git a/tests/bdd/steps/global.steps.ts b/tests/bdd/steps/global.steps.ts deleted file mode 100644 index bce0f18c..00000000 --- a/tests/bdd/steps/global.steps.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Given, Then, When } from "@cucumber/cucumber"; -import { type AriaRole, expect } from "@playwright/test"; -import { injectAuthCookies } from "../support/auth.ts"; -import type { PlaywrightWorld } from "../support/world"; - -Given("I am on {string}", async function (this: PlaywrightWorld, path: string) { - await this.requirePage().goto(`${this.baseUrl}${path}`); -}); - -Given("I am logged in", async function (this: PlaywrightWorld) { - await injectAuthCookies(this.requireContext()); -}); - -When( - "I click on the {string} {role}", - async function (this: PlaywrightWorld, label: string, role: AriaRole) { - await this.requirePage().getByRole(role, { name: label }).click(); - }, -); - -Then( - "I should see {article} {string} heading", - async function (this: PlaywrightWorld, _article: null, heading: string) { - await expect( - this.requirePage().getByRole("heading", { name: heading }), - ).toBeVisible(); - }, -); - -Then( - "I should be on {string}", - async function (this: PlaywrightWorld, path: string) { - await expect(this.requirePage()).toHaveURL( - new RegExp( - `${this.baseUrl}${path.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&")}$`, - ), - ); - }, -); diff --git a/tests/bdd/support/auth.ts b/tests/bdd/support/auth.ts deleted file mode 100644 index b08602e1..00000000 --- a/tests/bdd/support/auth.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { BrowserContext } from "@playwright/test"; - -const BASE_URL = process.env.BASE_URL || "http://localhost:3000"; - -/** - * Logs in via OIDC flow in a temporary page, leaving auth cookies in the context. - */ -export async function injectAuthCookies( - context: BrowserContext, -): Promise { - const page = await context.newPage(); - try { - await page.goto(`${BASE_URL}/signin`); - await page.getByRole("button", { name: "Okta" }).click(); - await page.waitForURL((url) => !url.pathname.startsWith("/signin"), { - timeout: 30000, - }); - } finally { - await page.close(); - } -} diff --git a/tests/bdd/support/hooks.ts b/tests/bdd/support/hooks.ts deleted file mode 100644 index 9b8d9b68..00000000 --- a/tests/bdd/support/hooks.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { - After, - AfterAll, - Before, - type ITestCaseHookParameter, - setDefaultTimeout, -} from "@cucumber/cucumber"; -import { - type Browser, - type BrowserContext, - chromium, - type Page, -} from "@playwright/test"; -import type { PlaywrightWorld } from "./world"; - -let browser: Browser | undefined; -const TRACE_ENABLED = process.env.PWTRACE === "1"; - -setDefaultTimeout(60_000); - -Before(async function (this: PlaywrightWorld) { - const isDebug = !!process.env.PWDEBUG; - if (!browser) { - browser = await chromium.launch({ - headless: !isDebug, - slowMo: isDebug ? 100 : 0, - }); - } - const context: BrowserContext = await browser.newContext(); - const page: Page = await context.newPage(); - this.context = context; - this.page = page; - - await this.page.addInitScript(() => { - // Hide Next.js dev overlay - const style = document.createElement("style"); - style.textContent = "nextjs-portal { display: none !important; }"; - document.head.appendChild(style); - }); - - if (TRACE_ENABLED) { - await this.context.tracing.start({ screenshots: true, snapshots: true }); - } - - if (isDebug) { - await this.page.pause(); - } -}); - -After(async function (this: PlaywrightWorld, scenario: ITestCaseHookParameter) { - if (TRACE_ENABLED && this.context) { - const safeName = scenario.pickle.name.replace(/[^a-z0-9-]+/gi, "_"); - const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); - const { mkdir } = await import("node:fs/promises"); - await mkdir("test-results/traces", { recursive: true }); - await this.context.tracing.stop({ - path: `test-results/traces/${safeName}_${timestamp}.zip`, - }); - } - if (this.page) await this.page.close(); - if (this.context) await this.context.close(); -}); - -AfterAll(async () => { - if (browser) { - await browser.close(); - browser = undefined; - } -}); diff --git a/tests/bdd/support/parameter-types.ts b/tests/bdd/support/parameter-types.ts deleted file mode 100644 index bb1bd443..00000000 --- a/tests/bdd/support/parameter-types.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { defineParameterType } from "@cucumber/cucumber"; -import type { AriaRole } from "@playwright/test"; -import { allowedRolePhrases } from "./roles.ts"; - -const escapeRegex = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); -const phrases = Object.keys(allowedRolePhrases).map(escapeRegex).join("|"); -const rolePattern = new RegExp(`(?:${phrases})`); - -defineParameterType({ - name: "role", - useForSnippets: false, - regexp: rolePattern, - transformer: (text: string): AriaRole => { - const key = text.trim().toLowerCase(); - const role = allowedRolePhrases[key]; - if (role) return role; - throw new Error(`Unknown role phrase "${text}".`); - }, -}); - -defineParameterType({ - name: "article", - useForSnippets: false, - regexp: /a|an/, - transformer: () => null, -}); diff --git a/tests/bdd/support/roles.ts b/tests/bdd/support/roles.ts deleted file mode 100644 index cdf25864..00000000 --- a/tests/bdd/support/roles.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { AriaRole } from "@playwright/test"; -import { roles as ariaRolesMap } from "aria-query"; - -// Custom phrases for ARIA roles (when the phrase differs from the role name) -const customPhrases: Record = { - "menu item": "menuitem", -}; - -function buildAllowedRolePhrases(): Record { - const mapping: Record = {}; - - for (const [phrase, role] of Object.entries(customPhrases)) { - mapping[phrase.trim().toLowerCase()] = role; - } - - const customRoles = new Set(Object.values(customPhrases)); - for (const roleName of ariaRolesMap.keys()) { - const role = String(roleName) as AriaRole; - if (!customRoles.has(role)) { - mapping[role] = role; - } - } - return mapping; -} - -export const allowedRolePhrases = buildAllowedRolePhrases(); diff --git a/tests/bdd/support/world.ts b/tests/bdd/support/world.ts deleted file mode 100644 index ef5599d4..00000000 --- a/tests/bdd/support/world.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { setWorldConstructor, type World } from "@cucumber/cucumber"; -import type { BrowserContext, Page } from "@playwright/test"; - -export class PlaywrightWorld implements World { - page: Page | undefined; - context: BrowserContext | undefined; - baseUrl: string; - - constructor() { - this.baseUrl = process.env.BASE_URL || "http://localhost:3000"; - } - - requirePage(): Page { - if (!this.page) - throw new Error("Page not initialized - Before hook may have failed"); - return this.page; - } - - requireContext(): BrowserContext { - if (!this.context) - throw new Error("Context not initialized - Before hook may have failed"); - return this.context; - } -} - -setWorldConstructor(PlaywrightWorld); diff --git a/tests/e2e/catalog.spec.ts b/tests/e2e/catalog.spec.ts new file mode 100644 index 00000000..3bf49017 --- /dev/null +++ b/tests/e2e/catalog.spec.ts @@ -0,0 +1,10 @@ +import { expect, test } from "./fixtures"; + +test.describe("Catalog page", () => { + test("view catalog page header", async ({ authenticatedPage }) => { + await authenticatedPage.goto("/catalog"); + await expect( + authenticatedPage.getByRole("heading", { name: "MCP Server Catalog" }), + ).toBeVisible(); + }); +}); diff --git a/tests/e2e/fixtures.ts b/tests/e2e/fixtures.ts new file mode 100644 index 00000000..380ca02f --- /dev/null +++ b/tests/e2e/fixtures.ts @@ -0,0 +1,20 @@ +import { test as base, type Page } from "@playwright/test"; + +const BASE_URL = process.env.BASE_URL || "http://localhost:3000"; + +async function login(page: Page): Promise { + await page.goto(`${BASE_URL}/signin`); + await page.getByRole("button", { name: /oidc|okta/i }).click(); + await page.waitForURL((url) => !url.pathname.startsWith("/signin"), { + timeout: 30000, + }); +} + +export const test = base.extend<{ authenticatedPage: Page }>({ + authenticatedPage: async ({ page }, use) => { + await login(page); + await use(page); + }, +}); + +export { expect } from "@playwright/test"; diff --git a/tests/e2e/login.spec.ts b/tests/e2e/login.spec.ts new file mode 100644 index 00000000..e5e0bd8d --- /dev/null +++ b/tests/e2e/login.spec.ts @@ -0,0 +1,19 @@ +import { expect, test } from "./fixtures"; + +test.describe("Login flow", () => { + test("sign in and land on Catalog", async ({ page }) => { + await page.goto("/signin"); + await page.getByRole("button", { name: /oidc|okta/i }).click(); + await expect(page).toHaveURL(/\/catalog$/); + await expect( + page.getByRole("heading", { name: "MCP Server Catalog" }), + ).toBeVisible(); + }); + + test("log out from Catalog", async ({ authenticatedPage }) => { + await authenticatedPage.goto("/catalog"); + await authenticatedPage.getByRole("button", { name: "Test User" }).click(); + await authenticatedPage.getByRole("menuitem", { name: "Sign out" }).click(); + await expect(authenticatedPage).toHaveURL(/\/signin$/); + }); +}); From 80eb323a6acd896a750fd453e44bf756070513a6 Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Fri, 12 Dec 2025 15:22:49 +0100 Subject: [PATCH 23/31] auto run dev server if it's not already runnging --- playwright.config.mts | 53 +++++++++++++++++++++++++++++++++++++++++++ playwright.config.ts | 41 --------------------------------- 2 files changed, 53 insertions(+), 41 deletions(-) create mode 100644 playwright.config.mts delete mode 100644 playwright.config.ts diff --git a/playwright.config.mts b/playwright.config.mts new file mode 100644 index 00000000..ddaf463c --- /dev/null +++ b/playwright.config.mts @@ -0,0 +1,53 @@ +import { defineConfig, devices } from "@playwright/test"; + +const BASE_URL = process.env.BASE_URL || "http://localhost:3000"; + +async function isServerRunning(): Promise { + try { + await fetch(BASE_URL, { signal: AbortSignal.timeout(2000) }); + return true; + } catch { + return false; + } +} + +const serverAlreadyRunning = await isServerRunning(); + +export default defineConfig({ + testDir: "./tests/e2e", + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: process.env.CI ? "github" : "list", + timeout: 30_000, + use: { + baseURL: BASE_URL, + trace: "on-first-retry", + screenshot: "only-on-failure", + }, + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + ], + webServer: serverAlreadyRunning + ? undefined + : { + command: "pnpm dev", + url: BASE_URL, + timeout: 120_000, + stdout: "pipe", + stderr: "pipe", + env: { + API_BASE_URL: "http://localhost:9090", + OIDC_ISSUER_URL: "http://localhost:4000", + OIDC_CLIENT_ID: "better-auth-dev", + OIDC_CLIENT_SECRET: "dev-secret-change-in-production", + NEXT_PUBLIC_OIDC_PROVIDER_ID: "okta", + BETTER_AUTH_URL: "http://localhost:3000", + BETTER_AUTH_SECRET: "e2e-test-secret-at-least-32-chars-long", + }, + }, +}); diff --git a/playwright.config.ts b/playwright.config.ts deleted file mode 100644 index ec190fd7..00000000 --- a/playwright.config.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { defineConfig, devices } from "@playwright/test"; - -const BASE_URL = process.env.BASE_URL || "http://localhost:3000"; - -export default defineConfig({ - testDir: "./tests/e2e", - fullyParallel: true, - forbidOnly: !!process.env.CI, - retries: process.env.CI ? 2 : 0, - workers: process.env.CI ? 1 : undefined, - reporter: process.env.CI ? "github" : "list", - timeout: 30_000, - use: { - baseURL: BASE_URL, - trace: "on-first-retry", - screenshot: "only-on-failure", - }, - projects: [ - { - name: "chromium", - use: { ...devices["Desktop Chrome"] }, - }, - ], - webServer: { - command: "pnpm dev", - url: BASE_URL, - reuseExistingServer: !process.env.CI, - timeout: 120_000, - stdout: "pipe", - stderr: "pipe", - env: { - API_BASE_URL: "http://localhost:9090", - OIDC_ISSUER_URL: "http://localhost:4000", - OIDC_CLIENT_ID: "better-auth-dev", - OIDC_CLIENT_SECRET: "dev-secret-change-in-production", - NEXT_PUBLIC_OIDC_PROVIDER_ID: "okta", - BETTER_AUTH_URL: "http://localhost:3000", - BETTER_AUTH_SECRET: "e2e-test-secret-at-least-32-chars-long", - }, - }, -}); From d32f206db99c237a92c965ff474693a62d9f1e6e Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Fri, 12 Dec 2025 15:52:33 +0100 Subject: [PATCH 24/31] fix vitest config --- vitest.config.mts | 1 + 1 file changed, 1 insertion(+) diff --git a/vitest.config.mts b/vitest.config.mts index e38322e0..6468d92b 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -8,6 +8,7 @@ export default defineConfig({ globals: true, environment: "jsdom", setupFiles: ["src/mocks/test.setup.ts", "./vitest.setup.ts"], + exclude: ["**/node_modules/**", "**/dist/**", "tests/e2e/**"], env: { // Exactly 32 bytes for AES-256 BETTER_AUTH_SECRET: "12345678901234567890123456789012", // Exactly 32 bytes for AES-256 From df8c2d1ff8e8d9b30c78d6ee10667872c25c5d43 Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Fri, 12 Dec 2025 16:03:17 +0100 Subject: [PATCH 25/31] rename gh workload file --- .github/workflows/{bdd.yml => e2e.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{bdd.yml => e2e.yml} (100%) diff --git a/.github/workflows/bdd.yml b/.github/workflows/e2e.yml similarity index 100% rename from .github/workflows/bdd.yml rename to .github/workflows/e2e.yml From 0085ce5d3dc71c1b2faac496d27da32268b9f832 Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Fri, 12 Dec 2025 16:05:19 +0100 Subject: [PATCH 26/31] . --- tests/e2e/fixtures.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/e2e/fixtures.ts b/tests/e2e/fixtures.ts index 380ca02f..80dc0fbd 100644 --- a/tests/e2e/fixtures.ts +++ b/tests/e2e/fixtures.ts @@ -1,10 +1,14 @@ -import { test as base, type Page } from "@playwright/test"; +import { test as base, expect, type Page } from "@playwright/test"; const BASE_URL = process.env.BASE_URL || "http://localhost:3000"; async function login(page: Page): Promise { await page.goto(`${BASE_URL}/signin`); - await page.getByRole("button", { name: /oidc|okta/i }).click(); + + const signInButton = page.getByRole("button", { name: /oidc|okta/i }); + await expect(signInButton).toBeVisible({ timeout: 5000 }); + await signInButton.click(); + await page.waitForURL((url) => !url.pathname.startsWith("/signin"), { timeout: 30000, }); From 11926e6d7e7e3194bde4b5961a87a0dec9f58572 Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Fri, 12 Dec 2025 16:09:27 +0100 Subject: [PATCH 27/31] much cleaner ci --- .github/workflows/e2e.yml | 49 ++++++++------------------------------- 1 file changed, 10 insertions(+), 39 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 659bc1ee..c66b1666 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -14,9 +14,17 @@ jobs: name: Playwright E2E runs-on: ubuntu-latest timeout-minutes: 15 + env: + API_BASE_URL: http://localhost:9090 + OIDC_ISSUER_URL: http://localhost:4000 + OIDC_CLIENT_ID: better-auth-dev + OIDC_CLIENT_SECRET: dev-secret-change-in-production + NEXT_PUBLIC_OIDC_PROVIDER_ID: okta + BETTER_AUTH_URL: http://localhost:3000 + BETTER_AUTH_SECRET: ci-test-secret-at-least-32-chars-long steps: - name: Checkout - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@v4 - name: Setup uses: ./.github/actions/setup @@ -24,40 +32,8 @@ jobs: - name: Install Playwright browsers run: pnpm exec playwright install --with-deps chromium - - name: Start dev stack (Next + OIDC + Mock) - env: - API_BASE_URL: http://localhost:9090 - OIDC_ISSUER_URL: http://localhost:4000 - OIDC_CLIENT_ID: better-auth-dev - OIDC_CLIENT_SECRET: dev-secret-change-in-production - NEXT_PUBLIC_OIDC_PROVIDER_ID: okta - BETTER_AUTH_URL: http://localhost:3000 - BETTER_AUTH_SECRET: ci-test-secret-not-for-production - run: | - pnpm dev & - echo $! > dev.pid - # Wait for Next (3000), OIDC (4000 - use discovery endpoint), and Mock API (9090 - use health endpoint) - for url in http://localhost:3000 http://localhost:4000/.well-known/openid-configuration http://localhost:9090/health; do - echo "Waiting for $url..." - up=false - for i in {1..30}; do - if curl -sf "$url" >/dev/null; then - echo "$url is up" - up=true - break - fi - sleep 2 - done - if [ "$up" = false ]; then - echo "ERROR: $url failed to start" - exit 1 - fi - done - - name: Run Playwright tests - env: - BASE_URL: http://localhost:3000 - run: pnpm run test:e2e + run: pnpm test:e2e - name: Upload test artifacts uses: actions/upload-artifact@v4 @@ -68,8 +44,3 @@ jobs: test-results/ playwright-report/ retention-days: 7 - - - name: Cleanup dev - if: always() - run: | - if [ -f dev.pid ]; then kill $(cat dev.pid) || true; fi From 0b6e69ccca66e488d00ad08e4303c094db8b487f Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Fri, 12 Dec 2025 16:20:08 +0100 Subject: [PATCH 28/31] remove duplicated documentation --- README.md | 32 +++++--------------------------- 1 file changed, 5 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index f1601ee0..95d0d539 100644 --- a/README.md +++ b/README.md @@ -289,12 +289,13 @@ Uses Vitest + Testing Library + MSW. ```bash pnpm exec playwright install # One-time browser install -pnpm dev # Start dev stack -pnpm run test:e2e # Run tests (headless) -pnpm run test:e2e:ui # Playwright UI mode -pnpm run test:e2e:debug # With Playwright Inspector +pnpm test:e2e # Run tests (auto-starts dev server if needed) +pnpm test:e2e:ui # Playwright UI mode +pnpm test:e2e:debug # With Playwright Inspector ``` +Tests automatically start the dev stack if it's not already running. If you prefer to start it manually first, run `pnpm dev` before the tests. + ### Mock Server The project includes a standalone MSW mock server for development: @@ -469,29 +470,6 @@ For detailed information about the project: - [shadcn/ui Components](https://ui.shadcn.com) - [MCP Registry Official](https://github.com/modelcontextprotocol/registry) -## Testing - -### Unit/Component - -```bash -pnpm test # Vitest -pnpm type-check # TypeScript -pnpm lint # Biome -``` - -### E2E Tests (Playwright) - -Run the app and E2E tests locally: - -```bash -pnpm exec playwright install # one-time browser install -pnpm dev # start Next (3000) + OIDC (4000) + Mock API (9090) -pnpm run test:e2e # run Playwright tests (headless) -pnpm run test:e2e:ui # Playwright UI mode for debugging -pnpm run test:e2e:debug # with Playwright Inspector -``` - -CI runs the E2E suite via `.github/workflows/bdd.yml`. ## Deploy on Vercel From 8a8ddc471db204915a56aaa967362ee699f0af16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20K=C3=A1ntor?= Date: Fri, 12 Dec 2025 17:20:32 +0100 Subject: [PATCH 29/31] use obviously fake env vars Co-authored-by: Giuseppe Scuglia --- .github/workflows/e2e.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index c66b1666..eda7bf0c 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -17,11 +17,11 @@ jobs: env: API_BASE_URL: http://localhost:9090 OIDC_ISSUER_URL: http://localhost:4000 - OIDC_CLIENT_ID: better-auth-dev - OIDC_CLIENT_SECRET: dev-secret-change-in-production - NEXT_PUBLIC_OIDC_PROVIDER_ID: okta + OIDC_CLIENT_ID: test-only-not-a-real-id + OIDC_CLIENT_SECRET: test-only-not-a-real-secret + NEXT_PUBLIC_OIDC_PROVIDER_ID: oidc BETTER_AUTH_URL: http://localhost:3000 - BETTER_AUTH_SECRET: ci-test-secret-at-least-32-chars-long + BETTER_AUTH_SECRET: test-only-not-a-real-better-auth-secret steps: - name: Checkout uses: actions/checkout@v4 From 8eb91478b0164437378f562021037aecfbc8ef56 Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Fri, 12 Dec 2025 17:22:34 +0100 Subject: [PATCH 30/31] remove leftofver --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 46729954..4b74df5f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -33,5 +33,5 @@ "**/*.mts", ".next/dev/types/**/*.ts" ], - "exclude": ["node_modules", ".next/dev/types/**", "tests/bdd/**"] + "exclude": ["node_modules", ".next/dev/types/**"] } From 50ac1d1ec90aec60a1dec5f90537c36c7f9929cd Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Fri, 12 Dec 2025 17:40:08 +0100 Subject: [PATCH 31/31] test that mcp servers are rendered --- tests/e2e/catalog.spec.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/e2e/catalog.spec.ts b/tests/e2e/catalog.spec.ts index 3bf49017..1e1c8ba8 100644 --- a/tests/e2e/catalog.spec.ts +++ b/tests/e2e/catalog.spec.ts @@ -1,10 +1,20 @@ import { expect, test } from "./fixtures"; test.describe("Catalog page", () => { - test("view catalog page header", async ({ authenticatedPage }) => { + test("displays MCP servers from the catalog", async ({ + authenticatedPage, + }) => { await authenticatedPage.goto("/catalog"); + await expect( authenticatedPage.getByRole("heading", { name: "MCP Server Catalog" }), ).toBeVisible(); + + await expect( + authenticatedPage.getByText("awslabs/aws-nova-canvas"), + ).toBeVisible(); + await expect( + authenticatedPage.getByText("github/mcp-github"), + ).toBeVisible(); }); });