diff --git a/packages/adapter-devunus/.eslintrc.cjs b/packages/adapter-devunus/.eslintrc.cjs new file mode 100644 index 00000000..d45517e4 --- /dev/null +++ b/packages/adapter-devunus/.eslintrc.cjs @@ -0,0 +1,4 @@ +module.exports = { + extends: ['custom'], + root: true, +}; diff --git a/packages/adapter-devunus/CHANGELOG.md b/packages/adapter-devunus/CHANGELOG.md new file mode 100644 index 00000000..8f9f5180 --- /dev/null +++ b/packages/adapter-devunus/CHANGELOG.md @@ -0,0 +1,7 @@ +# @flags-sdk/devunus + +## 0.1.0 + +### Initial Release + +- Initial release of the Devunus adapter for flags-sdk diff --git a/packages/adapter-devunus/README.md b/packages/adapter-devunus/README.md new file mode 100644 index 00000000..7629171f --- /dev/null +++ b/packages/adapter-devunus/README.md @@ -0,0 +1,35 @@ +# @flags-sdk/devunus + +Devunus adapter for [flags-sdk](https://github.com/vercel/flags). + +- An adapter for loading feature flags from devunus (coming soon). +- A getProviderData function for use with the Flags Explorer (available today). + +## Installation + +```bash +npm install @flags-sdk/devunus +``` + +## Usage getProviderData + +Use a server env key for DEVUNUS_ENV_KEY. You can find your environment key in the [Devunus Admin Console](https://app.devunus.com/admin/def/project/1/get-started/e0-0/keys). + +`app/.well-known/vercel/flags/route.ts`: + +```tsx +import { verifyAccess, type ApiData } from 'flags'; +import { getProviderData } from '@flags-sdk/devunus'; +import { NextResponse, type NextRequest } from 'next/server'; + +export async function GET(request: NextRequest) { + const access = await verifyAccess(request.headers.get('Authorization')); + if (!access) return NextResponse.json(null, { status: 401 }); + + const flagData = await getProviderData({ + envKey: process.env.DEVUNUS_ENV_KEY, + }); + + return NextResponse.json(flagData); +} +``` diff --git a/packages/adapter-devunus/package.json b/packages/adapter-devunus/package.json new file mode 100644 index 00000000..a89002ee --- /dev/null +++ b/packages/adapter-devunus/package.json @@ -0,0 +1,57 @@ +{ + "name": "@flags-sdk/devunus", + "version": "0.1.0", + "description": "Devunus adapter for flags-sdk", + "keywords": [ + "feature-flags", + "devunus", + "flags-sdk" + ], + "license": "MIT", + "author": "", + "sideEffects": false, + "type": "module", + "exports": { + ".": { + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "main": "./dist/index.js", + "typesVersions": { + "*": { + ".": [ + "dist/*.d.ts", + "dist/*.d.cts" + ] + } + }, + "files": [ + "dist", + "CHANGELOG.md" + ], + "scripts": { + "build": "rimraf dist && tsup", + "dev": "tsup --watch --clean=false", + "eslint": "eslint-runner", + "eslint:fix": "eslint-runner --fix", + "test": "vitest --run", + "test:watch": "vitest", + "type-check": "tsc --noEmit" + }, + "devDependencies": { + "@types/node": "20.11.17", + "eslint-config-custom": "workspace:*", + "eslint-plugin-vitest": "0.5.4", + "flags": "workspace:*", + "msw": "2.6.4", + "rimraf": "6.0.1", + "tsconfig": "workspace:*", + "tsup": "8.0.1", + "typescript": "5.6.3", + "vitest": "1.4.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/adapter-devunus/src/index.ts b/packages/adapter-devunus/src/index.ts new file mode 100644 index 00000000..a1be2962 --- /dev/null +++ b/packages/adapter-devunus/src/index.ts @@ -0,0 +1 @@ +export { getProviderData } from './provider'; diff --git a/packages/adapter-devunus/src/provider/index.test.ts b/packages/adapter-devunus/src/provider/index.test.ts new file mode 100644 index 00000000..3e537e54 --- /dev/null +++ b/packages/adapter-devunus/src/provider/index.test.ts @@ -0,0 +1,74 @@ +import { describe, it, expect, vi, beforeAll, afterAll } from 'vitest'; +import { getProviderData } from './index'; +import { setupServer } from 'msw/node'; +import { http, HttpResponse } from 'msw'; + +const server = setupServer( + http.get('https://api.devunus.com/api/flags', ({ request }) => { + const authHeader = request.headers.get('Authorization'); + + if (!authHeader || authHeader !== 'valid-key') { + return new HttpResponse(null, { status: 401 }); + } + + return HttpResponse.json({ + flags: [ + { + id: 'flag1', + name: 'enableFeatureX', + description: 'Enable feature X', + value: true, + type: 'boolean', + createdAt: 1615000000000, + updatedAt: 1620000000000, + }, + { + id: 'flag2', + name: 'userTheme', + description: 'User theme preference', + value: 'dark', + type: 'string', + createdAt: 1615000000000, + updatedAt: 1620000000000, + }, + ], + baseUrl: 'https://app.devunus.com/admin/333/project/1', + }); + }), +); + +describe('Devunus provider', () => { + beforeAll(() => server.listen()); + afterAll(() => server.close()); + + it('should return empty definitions and a hint when no env key is provided', async () => { + const result = await getProviderData({ envKey: '' }); + + expect(result.definitions).toEqual({}); + expect(result.hints).toBeDefined(); + expect(result.hints?.length).toBe(1); + expect(result.hints?.[0]?.key).toBe('devunus/missing-env-key'); + }); + + it('should return empty definitions and a hint when the API returns an error', async () => { + const result = await getProviderData({ envKey: 'invalid-key' }); + + expect(result.definitions).toEqual({}); + expect(result.hints).toBeDefined(); + expect(result.hints?.length).toBe(1); + expect(result.hints?.[0]?.key).toBe('devunus/response-not-ok'); + }); + + it('should return flag definitions when the API returns valid data', async () => { + const result = await getProviderData({ envKey: 'valid-key' }); + + expect(Object.keys(result.definitions)).toHaveLength(2); + expect(result.definitions.enableFeatureX).toBeDefined(); + expect(result.definitions.userTheme).toBeDefined(); + const enableFeatureX = result.definitions.enableFeatureX; + const userTheme = result.definitions.userTheme; + expect(enableFeatureX?.description).toBe('Enable feature X'); + expect(userTheme?.description).toBe('User theme preference'); + expect(result.hints?.length).toBe(0); + }); +}); diff --git a/packages/adapter-devunus/src/provider/index.ts b/packages/adapter-devunus/src/provider/index.ts new file mode 100644 index 00000000..8616f795 --- /dev/null +++ b/packages/adapter-devunus/src/provider/index.ts @@ -0,0 +1,98 @@ +import type { ProviderData, JsonValue } from 'flags'; + +interface DevunusFlag { + id: string; + name: string; + description: string; + value: string; + type: 'boolean' | 'string' | 'number' | 'json'; + createdAt: number; + updatedAt: number; +} + +interface DevunusResponse { + flags: DevunusFlag[]; + baseUrl: string; +} + +export async function getProviderData(options: { + /** + * The Devunus environment key. + */ + envKey: string; +}): Promise { + if (!options.envKey) { + return { + definitions: {}, + hints: [ + { + key: 'devunus/missing-env-key', + text: 'Missing DevUnus environment key', + }, + ], + }; + } + + try { + // Get from edge API + const response = await fetch(`https://api.devunus.com/api/flags`, { + headers: { Authorization: `${options.envKey}` }, + cache: 'no-store', + }); + + if (response.status !== 200) { + return { + definitions: {}, + hints: [ + { + key: 'devunus/response-not-ok', + text: `Failed to fetch DevUnus flag definitions (received ${response.status} response)`, + }, + ], + }; + } + + const data = (await response.json()) as DevunusResponse; + const { flags, baseUrl } = data; + + const definitions: ProviderData['definitions'] = {}; + + for (const flag of flags) { + const flagOptions = []; + + // For boolean flags, provide true/false options + if (flag.type === 'boolean') { + flagOptions.push({ value: true, label: 'On' }); + flagOptions.push({ value: false, label: 'Off' }); + } + + // For string flags, include the current value as an option + if (flag.type === 'string') { + flagOptions.push({ value: flag.value }); + } + + definitions[flag.name] = { + description: flag.description, + options: flagOptions, + origin: `${baseUrl}/flag/${flag.id}`, + updatedAt: flag.updatedAt, + createdAt: flag.createdAt, + defaultValue: flag.value, + }; + } + + return { definitions, hints: [] }; + } catch (error) { + return { + definitions: {}, + hints: [ + { + key: 'devunus/unexpected-error', + text: `Unexpected error fetching DevUnus flag definitions: ${ + error instanceof Error ? error.message : String(error) + }`, + }, + ], + }; + } +} diff --git a/packages/adapter-devunus/tsconfig.json b/packages/adapter-devunus/tsconfig.json new file mode 100644 index 00000000..f56eab73 --- /dev/null +++ b/packages/adapter-devunus/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "tsconfig/base.json", + "include": ["src"], + "exclude": ["node_modules"], + "compilerOptions": { + "outDir": "dist", + "types": ["node", "vitest/globals"] + } +} diff --git a/packages/adapter-devunus/tsup.config.js b/packages/adapter-devunus/tsup.config.js new file mode 100644 index 00000000..b6a89d32 --- /dev/null +++ b/packages/adapter-devunus/tsup.config.js @@ -0,0 +1,12 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: ['src/index.ts'], + format: ['esm', 'cjs'], + dts: true, + splitting: false, + sourcemap: true, + clean: true, + treeshake: true, + minify: true, +}); diff --git a/packages/adapter-devunus/vitest.config.ts b/packages/adapter-devunus/vitest.config.ts new file mode 100644 index 00000000..014f97ef --- /dev/null +++ b/packages/adapter-devunus/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'node', + globals: true, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dcf88b8b..210b8ab8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -316,6 +316,39 @@ importers: specifier: ^5.4.4 version: 5.4.14(@types/node@22.9.0) + packages/adapter-devunus: + devDependencies: + '@types/node': + specifier: 20.11.17 + version: 20.11.17 + eslint-config-custom: + specifier: workspace:* + version: link:../../tooling/eslint-config-custom + eslint-plugin-vitest: + specifier: 0.5.4 + version: 0.5.4(eslint@8.57.1)(typescript@5.6.3)(vitest@1.4.0) + flags: + specifier: workspace:* + version: link:../flags + msw: + specifier: 2.6.4 + version: 2.6.4(@types/node@20.11.17)(typescript@5.6.3) + rimraf: + specifier: 6.0.1 + version: 6.0.1 + tsconfig: + specifier: workspace:* + version: link:../../tooling/tsconfig + tsup: + specifier: 8.0.1 + version: 8.0.1(typescript@5.6.3) + typescript: + specifier: 5.6.3 + version: 5.6.3 + vitest: + specifier: 1.4.0 + version: 1.4.0(@types/node@20.11.17) + packages/adapter-edge-config: dependencies: '@vercel/edge-config': @@ -627,7 +660,7 @@ importers: version: 5.10.0 react-dom: specifier: '*' - version: 19.0.0(react@19.1.0-canary-a4f9bd58-20250319) + version: 19.0.0(react@19.1.0-canary-4280563b-20250326) devDependencies: '@arethetypeswrong/cli': specifier: 0.17.3 @@ -649,10 +682,10 @@ importers: version: 2.6.4(@types/node@20.11.17)(typescript@5.6.3) next: specifier: 15.1.4 - version: 15.1.4(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@19.0.0)(react@19.1.0-canary-a4f9bd58-20250319) + version: 15.1.4(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@19.0.0)(react@19.1.0-canary-4280563b-20250326) react: specifier: canary - version: 19.1.0-canary-a4f9bd58-20250319 + version: 19.1.0-canary-4280563b-20250326 tsconfig: specifier: workspace:* version: link:../../tooling/tsconfig @@ -2062,6 +2095,17 @@ packages: dependencies: eslint: 8.56.0 eslint-visitor-keys: 3.4.3 + dev: true + + /@eslint-community/eslint-utils@4.5.0(eslint@8.57.1): + resolution: {integrity: sha512-RoV8Xs9eNwiDvhv7M+xcL4PWyRyIXRY/FLp3buU4h1EYfdF7unWUy3dOjPqb3C7rMUewIcqwW850PgS8h1o1yg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + dependencies: + eslint: 8.57.1 + eslint-visitor-keys: 3.4.3 + dev: true /@eslint-community/eslint-utils@4.5.0(eslint@9.22.0): resolution: {integrity: sha512-RoV8Xs9eNwiDvhv7M+xcL4PWyRyIXRY/FLp3buU4h1EYfdF7unWUy3dOjPqb3C7rMUewIcqwW850PgS8h1o1yg==} @@ -2140,6 +2184,12 @@ packages: /@eslint/js@8.56.0: resolution: {integrity: sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /@eslint/js@8.57.1: + resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true /@eslint/js@9.22.0: resolution: {integrity: sha512-vLFajx9o8d1/oL2ZkpMYbkLv8nDB6yaIwFNt7nI4+I80U/z03SxmfOMsLbvWr3p7C+Wnoh//aOu2pQW8cS0HCQ==} @@ -2269,6 +2319,18 @@ packages: transitivePeerDependencies: - supports-color + /@humanwhocodes/config-array@0.13.0: + resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead + dependencies: + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.4.0 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + dev: true + /@humanwhocodes/module-importer@1.0.1: resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} @@ -5027,6 +5089,7 @@ packages: typescript: 5.8.2 transitivePeerDependencies: - supports-color + dev: true /@typescript-eslint/parser@8.26.1(eslint@8.56.0)(typescript@5.8.2): resolution: {integrity: sha512-w6HZUV4NWxqd8BdeFf81t07d7/YV9s7TCWrQQbG5uhuvGUAW+fq1usZ1Hmz9UPNLniFnD8GLSsDpjP0hm1S4lQ==} @@ -5078,6 +5141,14 @@ packages: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/visitor-keys': 6.21.0 + /@typescript-eslint/scope-manager@7.18.0: + resolution: {integrity: sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==} + engines: {node: ^18.18.0 || >=20.0.0} + dependencies: + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/visitor-keys': 7.18.0 + dev: true + /@typescript-eslint/scope-manager@8.26.1: resolution: {integrity: sha512-6EIvbE5cNER8sqBu6V7+KeMZIC1664d2Yjt+B9EWUXrsyWpxx4lEZrmvxgSKRC6gX+efDL/UY9OpPZ267io3mg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -5187,6 +5258,11 @@ packages: resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} engines: {node: ^16.0.0 || >=18.0.0} + /@typescript-eslint/types@7.18.0: + resolution: {integrity: sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==} + engines: {node: ^18.18.0 || >=20.0.0} + dev: true + /@typescript-eslint/types@8.26.1: resolution: {integrity: sha512-n4THUQW27VmQMx+3P+B0Yptl7ydfceUj4ON/AQILAASwgYdZ/2dhfymRMh5egRUrvK5lSmaOm77Ry+lmXPOgBQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -5317,6 +5393,29 @@ packages: typescript: 5.8.2 transitivePeerDependencies: - supports-color + dev: true + + /@typescript-eslint/typescript-estree@7.18.0(typescript@5.6.3): + resolution: {integrity: sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/visitor-keys': 7.18.0 + debug: 4.4.0 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.1 + ts-api-utils: 1.4.3(typescript@5.6.3) + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + dev: true /@typescript-eslint/typescript-estree@8.26.1(typescript@5.8.2): resolution: {integrity: sha512-yUwPpUHDgdrv1QJ7YQal3cMVBGWfnuCdKbXw1yyjArax3353rEJP1ZA+4F8nOlQ3RfS2hUN/wze3nlY+ZOhvoA==} @@ -5452,6 +5551,22 @@ packages: - typescript dev: true + /@typescript-eslint/utils@7.18.0(eslint@8.57.1)(typescript@5.6.3): + resolution: {integrity: sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + dependencies: + '@eslint-community/eslint-utils': 4.5.0(eslint@8.57.1) + '@typescript-eslint/scope-manager': 7.18.0 + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.3) + eslint: 8.57.1 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + /@typescript-eslint/utils@8.26.1(eslint@8.56.0)(typescript@5.8.2): resolution: {integrity: sha512-V4Urxa/XtSUroUrnI7q6yUTD3hDtfJ2jzVfeT3VK0ciizfK2q/zGC0iDh1lFMUZR8cImRrep6/q0xd/1ZGPQpg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -5500,6 +5615,14 @@ packages: '@typescript-eslint/types': 6.21.0 eslint-visitor-keys: 3.4.3 + /@typescript-eslint/visitor-keys@7.18.0: + resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==} + engines: {node: ^18.18.0 || >=20.0.0} + dependencies: + '@typescript-eslint/types': 7.18.0 + eslint-visitor-keys: 3.4.3 + dev: true + /@typescript-eslint/visitor-keys@8.26.1: resolution: {integrity: sha512-AjOC3zfnxd6S4Eiy3jwktJPclqhFHNyd8L6Gycf9WUPoKZpgM5PjkxY1X7uSy61xVpiJDhhk7XT2NVsN3ALTWg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -5510,6 +5633,7 @@ packages: /@ungap/structured-clone@1.3.0: resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + dev: true /@upstash/redis@1.34.4: resolution: {integrity: sha512-AZx2iD5s1Pu/KCrRA7KVCffu3NSoaYnNY7N9YI7aLAYhcJfsriQKTe+8OxQWJqGqFbrvm17Lyr9HFnDLvqNpfA==} @@ -7347,7 +7471,7 @@ packages: peerDependencies: eslint-plugin-import: '>=1.4.0' dependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.8.6)(eslint@8.56.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.8.6)(eslint@8.48.0) /eslint-import-resolver-node@0.3.9: resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} @@ -7400,13 +7524,14 @@ packages: debug: 4.4.0 enhanced-resolve: 5.18.1 eslint: 8.56.0 - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.8.6)(eslint@8.56.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.26.1)(eslint-import-resolver-typescript@3.8.6)(eslint@8.56.0) get-tsconfig: 4.10.0 is-bun-module: 1.3.0 stable-hash: 0.0.4 tinyglobby: 0.2.12 transitivePeerDependencies: - supports-color + dev: true /eslint-import-resolver-typescript@3.8.6(eslint-plugin-import@2.31.0)(eslint@9.22.0): resolution: {integrity: sha512-d9UjvYpj/REmUoZvOtDEmayPlwyP4zOwwMBgtC6RtrpZta8u1AIVmxgZBYJIcCKKXwAcLs+DX2yn2LeMaTqKcQ==} @@ -7491,6 +7616,7 @@ packages: eslint-import-resolver-typescript: 3.8.6(eslint-plugin-import@2.31.0)(eslint@8.56.0) transitivePeerDependencies: - supports-color + dev: true /eslint-module-utils@2.12.0(@typescript-eslint/parser@8.26.1)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.8.6)(eslint@8.56.0): resolution: {integrity: sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==} @@ -7644,6 +7770,7 @@ packages: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color + dev: true /eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.26.1)(eslint-import-resolver-typescript@3.8.6)(eslint@8.56.0): resolution: {integrity: sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==} @@ -8096,6 +8223,27 @@ packages: strip-indent: 3.0.0 dev: true + /eslint-plugin-vitest@0.5.4(eslint@8.57.1)(typescript@5.6.3)(vitest@1.4.0): + resolution: {integrity: sha512-um+odCkccAHU53WdKAw39MY61+1x990uXjSPguUCq3VcEHdqJrOb8OTMrbYlY6f9jAKx7x98kLVlIe3RJeJqoQ==} + engines: {node: ^18.0.0 || >= 20.0.0} + peerDependencies: + '@typescript-eslint/eslint-plugin': '*' + eslint: ^8.57.0 || ^9.0.0 + vitest: '*' + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + vitest: + optional: true + dependencies: + '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.6.3) + eslint: 8.57.1 + vitest: 1.4.0(@types/node@20.11.17) + transitivePeerDependencies: + - supports-color + - typescript + dev: true + /eslint-scope@5.1.1: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} engines: {node: '>=8.0.0'} @@ -8223,6 +8371,55 @@ packages: text-table: 0.2.0 transitivePeerDependencies: - supports-color + dev: true + + /eslint@8.57.1: + resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + hasBin: true + dependencies: + '@eslint-community/eslint-utils': 4.5.0(eslint@8.57.1) + '@eslint-community/regexpp': 4.12.1 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.57.1 + '@humanwhocodes/config-array': 0.13.0 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.3.0 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.0 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.24.0 + graphemer: 1.4.0 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + dev: true /eslint@9.22.0: resolution: {integrity: sha512-9V/QURhsRN40xuHXWjV64yvrzMjcz7ZyNoF2jJFmy9j/SLk0u1OLSZgXi28MrXjymnjEGSR80WCdab3RGMDveQ==} @@ -10582,7 +10779,7 @@ packages: - babel-plugin-macros dev: false - /next@15.1.4(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@19.0.0)(react@19.1.0-canary-a4f9bd58-20250319): + /next@15.1.4(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@19.0.0)(react@19.1.0-canary-4280563b-20250326): resolution: {integrity: sha512-mTaq9dwaSuwwOrcu3ebjDYObekkxRnXpuVL21zotM8qE2W0HBOdVIdg2Li9QjMEZrj73LN96LcWcz62V19FjAg==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true @@ -10610,9 +10807,9 @@ packages: busboy: 1.6.0 caniuse-lite: 1.0.30001704 postcss: 8.4.31 - react: 19.1.0-canary-a4f9bd58-20250319 - react-dom: 19.0.0(react@19.1.0-canary-a4f9bd58-20250319) - styled-jsx: 5.1.6(@babel/core@7.26.10)(react@19.1.0-canary-a4f9bd58-20250319) + react: 19.1.0-canary-4280563b-20250326 + react-dom: 19.0.0(react@19.1.0-canary-4280563b-20250326) + styled-jsx: 5.1.6(@babel/core@7.26.10)(react@19.1.0-canary-4280563b-20250326) optionalDependencies: '@next/swc-darwin-arm64': 15.1.4 '@next/swc-darwin-x64': 15.1.4 @@ -11446,12 +11643,12 @@ packages: scheduler: 0.25.0 dev: false - /react-dom@19.0.0(react@19.1.0-canary-a4f9bd58-20250319): + /react-dom@19.0.0(react@19.1.0-canary-4280563b-20250326): resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==} peerDependencies: react: ^19.0.0 dependencies: - react: 19.1.0-canary-a4f9bd58-20250319 + react: 19.1.0-canary-4280563b-20250326 scheduler: 0.25.0 /react-dom@19.0.0-rc-02c0e824-20241028(react@19.0.0-rc-02c0e824-20241028): @@ -11556,8 +11753,8 @@ packages: engines: {node: '>=0.10.0'} dev: false - /react@19.1.0-canary-a4f9bd58-20250319: - resolution: {integrity: sha512-wRC4V2P/2yecBs/676cNhcQHv1UGfmNxizrqvEX6OjqNne79lzhe5oPbQ06JxNkzew5+2B2EtnTBEjs6M7vuKQ==} + /react@19.1.0-canary-4280563b-20250326: + resolution: {integrity: sha512-yk851gQ7gtt94E7j186hRT1C28LilmxsrfS/b2HE+urUVHlcLDkt5Fh338+9f9hvw/pttTTRCKPYUP+jsinipw==} engines: {node: '>=0.10.0'} /read-cache@1.0.0: @@ -12443,7 +12640,7 @@ packages: react: 19.0.0-rc.1 dev: false - /styled-jsx@5.1.6(@babel/core@7.26.10)(react@19.1.0-canary-a4f9bd58-20250319): + /styled-jsx@5.1.6(@babel/core@7.26.10)(react@19.1.0-canary-4280563b-20250326): resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} engines: {node: '>= 12.0.0'} peerDependencies: @@ -12458,7 +12655,7 @@ packages: dependencies: '@babel/core': 7.26.10 client-only: 0.0.1 - react: 19.1.0-canary-a4f9bd58-20250319 + react: 19.1.0-canary-4280563b-20250326 dev: true /sucrase@3.35.0: @@ -12744,6 +12941,7 @@ packages: typescript: '>=4.2.0' dependencies: typescript: 5.8.2 + dev: true /ts-api-utils@2.0.1(typescript@5.8.2): resolution: {integrity: sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==} @@ -13050,6 +13248,7 @@ packages: resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} engines: {node: '>=14.17'} hasBin: true + dev: true /ua-parser-js@1.0.40: resolution: {integrity: sha512-z6PJ8Lml+v3ichVojCiB8toQJBuwR42ySM4ezjXIqXK3M0HczmKQ3LF4rhU55PfD99KEEXQG6yb7iOMyvYuHew==} @@ -13420,7 +13619,7 @@ packages: strip-literal: 2.1.1 tinybench: 2.9.0 tinypool: 0.8.4 - vite: 5.1.1(@types/node@20.11.17) + vite: 5.4.14(@types/node@20.11.17) vite-node: 1.4.0(@types/node@20.11.17) why-is-node-running: 2.3.0 transitivePeerDependencies: