From dd48f4fddcca5d418adf5f4909b180d9e8fd7b5e Mon Sep 17 00:00:00 2001 From: Konstantin Wohlwend Date: Tue, 5 Aug 2025 17:31:04 -0700 Subject: [PATCH 01/17] Wildcard domains --- .claude/settings.json | 30 ++ CLAUDE-KNOWLEDGE.md | 125 +++++ CLAUDE.md | 27 +- .../oauth/callback/[provider_id]/route.tsx | 3 +- .../register/verification-code-handler.tsx | 30 +- .../sign-in/verification-code-handler.tsx | 29 +- apps/backend/src/lib/redirect-urls.test.tsx | 462 +++++++++++++++++ apps/backend/src/lib/redirect-urls.tsx | 99 +++- apps/backend/src/oauth/model.tsx | 10 +- .../[projectId]/domains/page-client.tsx | 20 +- .../auth/oauth/exact-domain-matching.test.ts | 284 ++++++++++ .../v1/auth/oauth/wildcard-domains.test.ts | 313 +++++++++++ .../v1/auth/passkey/wildcard-domains.test.ts | 484 ++++++++++++++++++ .../src/helpers/production-mode.ts | 4 +- packages/stack-shared/src/utils/urls.tsx | 142 +++++ 15 files changed, 1983 insertions(+), 79 deletions(-) create mode 100644 .claude/settings.json create mode 100644 CLAUDE-KNOWLEDGE.md create mode 100644 apps/backend/src/lib/redirect-urls.test.tsx create mode 100644 apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/exact-domain-matching.test.ts create mode 100644 apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts create mode 100644 apps/e2e/tests/backend/endpoints/api/v1/auth/passkey/wildcard-domains.test.ts diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000000..ee52ce43b2 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://json.schemastore.org/claude-code-settings.json", + "permissions": { + "allow": [ + "Bash(pnpm typecheck:*)", + "Bash(pnpm test:*)", + "Bash(pnpm lint:*)", + "Bash(find:*)", + "Bash(ls:*)", + "Bash(pnpm codegen)", + "Bash(pnpm vitest run:*)", + "Bash(pnpm eslint:*)" + ], + "deny": [] + }, + "includeCoAuthoredBy": false, + "hooks": { + "PostToolUse": [ + { + "matcher": "Edit|MultiEdit|Write", + "hooks": [ + { + "type": "command", + "command": "pnpm run lint --fix" + } + ] + } + ] + } +} diff --git a/CLAUDE-KNOWLEDGE.md b/CLAUDE-KNOWLEDGE.md new file mode 100644 index 0000000000..9857ba1196 --- /dev/null +++ b/CLAUDE-KNOWLEDGE.md @@ -0,0 +1,125 @@ +# CLAUDE-KNOWLEDGE.md + +This file documents key learnings from implementing wildcard domain support in Stack Auth, organized in Q&A format. + +## OAuth Flow and Validation + +### Q: Where does OAuth redirect URL validation happen in the flow? +A: The validation happens in the callback endpoint (`/api/v1/auth/oauth/callback/[provider_id]/route.tsx`), not in the authorize endpoint. The authorize endpoint just stores the redirect URL and redirects to the OAuth provider. The actual validation occurs when the OAuth provider calls back, and the oauth2-server library validates the redirect URL. + +### Q: How do you test OAuth flows that should fail? +A: Use `Auth.OAuth.getMaybeFailingAuthorizationCode()` instead of `Auth.OAuth.getAuthorizationCode()`. The latter expects success (status 303), while the former allows you to test failure cases. The failure happens at the callback stage with a 400 status and specific error message. + +### Q: What error is thrown for invalid redirect URLs in OAuth? +A: The callback endpoint returns a 400 status with the message: "Invalid redirect URI. The URL you are trying to redirect to is not trusted. If it should be, add it to the list of trusted domains in the Stack Auth dashboard." + +## Wildcard Pattern Implementation + +### Q: How do you handle ** vs * precedence in regex patterns? +A: Use a placeholder approach to prevent ** from being corrupted when replacing *: +```typescript +const doubleWildcardPlaceholder = '\x00DOUBLE_WILDCARD\x00'; +regexPattern = regexPattern.replace(/\*\*/g, doubleWildcardPlaceholder); +regexPattern = regexPattern.replace(/\*/g, '[^.]*'); +regexPattern = regexPattern.replace(new RegExp(doubleWildcardPlaceholder, 'g'), '.*'); +``` + +### Q: Why can't you use `new URL()` with wildcard domains? +A: Wildcard characters (* and **) are not valid in URLs and will cause parsing errors. For wildcard domains, you need to manually parse the URL components instead of using the URL constructor. + +### Q: How do you validate URLs with wildcards? +A: Extract the hostname pattern manually and use `matchHostnamePattern()`: +```typescript +const protocolEnd = domain.baseUrl.indexOf('://'); +const protocol = domain.baseUrl.substring(0, protocolEnd + 3); +const afterProtocol = domain.baseUrl.substring(protocolEnd + 3); +const pathStart = afterProtocol.indexOf('/'); +const hostnamePattern = pathStart === -1 ? afterProtocol : afterProtocol.substring(0, pathStart); +``` + +## Testing Best Practices + +### Q: How should you run multiple independent test commands? +A: Use parallel execution by batching tool calls together: +```typescript +// Good - runs in parallel +const [result1, result2] = await Promise.all([ + niceBackendFetch("/endpoint1"), + niceBackendFetch("/endpoint2") +]); + +// In E2E tests, the framework handles this automatically when you +// batch multiple tool calls in a single response +``` + +### Q: What's the correct way to update project configuration in E2E tests? +A: Use the `/api/v1/internal/config/override` endpoint with PATCH method and admin access token: +```typescript +await niceBackendFetch("/api/v1/internal/config/override", { + method: "PATCH", + accessType: "admin", + headers: { + 'x-stack-admin-access-token': adminAccessToken, + }, + body: { + config_override_string: JSON.stringify({ + 'domains.trustedDomains.name': { baseUrl: '...', handlerPath: '...' } + }), + }, +}); +``` + +## Code Organization + +### Q: Where does domain validation logic belong? +A: Core validation functions (`isValidHostnameWithWildcards`, `matchHostnamePattern`) belong in the shared utils package (`packages/stack-shared/src/utils/urls.tsx`) so they can be used by both frontend and backend. + +### Q: How do you simplify validation logic with wildcards? +A: Replace wildcards with valid placeholders before validation: +```typescript +const normalizedDomain = domain.replace(/\*+/g, 'wildcard-placeholder'); +url = new URL(normalizedDomain); // Now this won't throw +``` + +## Debugging E2E Tests + +### Q: What does "ECONNREFUSED" mean in E2E tests? +A: The backend server isn't running. Make sure to start the backend with `pnpm dev` before running E2E tests. + +### Q: How do you debug which stage of OAuth flow is failing? +A: Check the error location: +- Authorize endpoint (307 redirect) - Initial request succeeded +- Callback endpoint (400 error) - Validation failed during callback +- Token endpoint (400 error) - Validation failed during token exchange + +## Git and Development Workflow + +### Q: How should you format git commit messages in this project? +A: Use a HEREDOC to ensure proper formatting: +```bash +git commit -m "$(cat <<'EOF' +Commit message here. + +🤖 Generated with [Claude Code](https://claude.ai/code) + +Co-Authored-By: Claude +EOF +)" +``` + +### Q: What commands should you run before considering a task complete? +A: Always run: +1. `pnpm test run ` - Run tests +2. `pnpm lint` - Check for linting errors +3. `pnpm typecheck` - Check for TypeScript errors + +## Common Pitfalls + +### Q: Why might imports get removed after running lint --fix? +A: ESLint may remove "unused" imports. Always verify your changes after auto-fixing, especially if you're using imports in a way ESLint doesn't recognize (like in test expectations). + +### Q: What's a common linting error in test files? +A: Missing newline at end of file. ESLint requires files to end with a newline character. + +### Q: How do you handle TypeScript errors about missing exports? +A: Double-check that you're only importing what's actually exported from a module. The error "Module declares 'X' locally, but it is not exported" means you're trying to import something that isn't exported. \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 18b87fbb14..cac840b9a3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,18 +6,22 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ### Essential Commands - **Install dependencies**: `pnpm install` +- **Run tests**: `pnpm test run` (uses Vitest). You can filter with `pnpm test run `. The `run` is important to not trigger watch mode +- **Lint code**: `pnpm lint`. `pnpm lint --fix` will fix some of the linting errors, prefer that over fixing them manually. +- **Type check**: `pnpm typecheck` + +#### Extra commands +These commands are usually already called by the user, but you can remind them to run it for you if they forgot to. - **Build packages**: `pnpm build:packages` - **Generate code**: `pnpm codegen` - **Start dependencies**: `pnpm restart-deps` (resets & restarts Docker containers for DB, Inbucket, etc. Usually already called by the user) - **Run development**: `pnpm dev` (starts all services on different ports. Usually already started by the user in the background) - **Run minimal dev**: `pnpm dev:basic` (only backend and dashboard for resource-limited systems) -- **Run tests**: `pnpm test --no-watch` (uses Vitest). You can filter with `pnpm test --no-watch ` -- **Lint code**: `pnpm lint` -- **Type check**: `pnpm typecheck` ### Testing -- **Run all tests**: `pnpm test --no-watch` -- **Run some tests**: `pnpm test --no-watch ` +You should ALWAYS add new E2E tests when you change the API or SDK interface. Generally, err on the side of creating too many tests; it is super important that our codebase is well-tested, due to the nature of the industry we're building in. +- **Run all tests**: `pnpm test run` +- **Run some tests**: `pnpm test run ` ### Database Commands - **Generate migration**: `pnpm db:migration-gen` @@ -62,15 +66,12 @@ The API follows a RESTful design with routes organized by resource type: - OAuth providers: `/api/latest/oauth-providers/*` ### Development Ports -- 8100: Dev launchpad -- 8101: Dashboard -- 8102: Backend API -- 8103: Demo app -- 8104: Documentation -- 8105: Inbucket (email testing) -- 8106: Prisma Studio +To see all development ports, refer to the index.html of `apps/dev-launchpad/public/index.html`. ## Important Notes - Environment variables are pre-configured in `.env.development` files -- Code generation (`pnpm codegen`) must be run after schema changes +- Always run typecheck, lint, and test to make sure your changes are working as expected. You can save time by only linting and testing the files you've changed (and/or related E2E tests). - The project uses a custom route handler system in the backend for consistent API responses +- Sometimes, the typecheck will give errors along the line of "Cannot assign Buffer to Uint8Array" or similar, on changes that are completely unrelated to your own changes. If that happens, tell the user to run `pnpm clean && pnpm i && pnpm run codegen && pnpm build:packages`, and restart the dev server (you cannot run this yourself). After that's done, the typecheck should pass. +- When writing tests, prefer .toMatchInlineSnapshot over other selectors, if possible. You can check (and modify) the snapshot-serializer.ts file to see how the snapshots are formatted and how non-deterministic values are handled. +- Whenever you learn something new, or at the latest right before you call the `Stop` tool, write whatever you learned into the CLAUDE-KNOWLEDGE.md file, in the Q&A format in there. You will later be able to look up knowledge from there (based on the question you asked). diff --git a/apps/backend/src/app/api/latest/auth/oauth/callback/[provider_id]/route.tsx b/apps/backend/src/app/api/latest/auth/oauth/callback/[provider_id]/route.tsx index 62bafd0c4d..c675751b69 100644 --- a/apps/backend/src/app/api/latest/auth/oauth/callback/[provider_id]/route.tsx +++ b/apps/backend/src/app/api/latest/auth/oauth/callback/[provider_id]/route.tsx @@ -403,7 +403,8 @@ const handler = createSmartRouteHandler({ } catch (error) { if (error instanceof InvalidClientError) { if (error.message.includes("redirect_uri") || error.message.includes("redirectUri")) { - throw new StatusError(400, "Invalid redirect URI. You might have set the wrong redirect URI in the OAuth provider settings. (Please copy the redirect URI from the Stack Auth dashboard and paste it into the OAuth provider's dashboard)"); + console.log("User is trying to authorize OAuth with an invalid redirect URI", error, oauthRequest); + throw new StatusError(400, "Invalid redirect URI. The URL you are trying to redirect to is not trusted. If it should be, add it to the list of trusted domains in the Stack Auth dashboard."); } } else if (error instanceof InvalidScopeError) { // which scopes are being requested, and by whom? diff --git a/apps/backend/src/app/api/latest/auth/passkey/register/verification-code-handler.tsx b/apps/backend/src/app/api/latest/auth/passkey/register/verification-code-handler.tsx index a2de637c49..44ac869a17 100644 --- a/apps/backend/src/app/api/latest/auth/passkey/register/verification-code-handler.tsx +++ b/apps/backend/src/app/api/latest/auth/passkey/register/verification-code-handler.tsx @@ -1,3 +1,4 @@ +import { validateRedirectUrl } from "@/lib/redirect-urls"; import { getPrismaClientForTenancy, retryTransaction } from "@/prisma-client"; import { createVerificationCodeHandler } from "@/route-handlers/verification-code-handler"; import { VerificationCodeType } from "@prisma/client"; @@ -50,35 +51,16 @@ export const registerVerificationCodeHandler = createVerificationCodeHandler({ } // HACK: we validate origin and rpid outside of simpleauth, this should be replaced once we have a primary authentication domain - - let expectedRPID = ""; - let expectedOrigin = ""; const clientDataJSON = decodeClientDataJSON(credential.response.clientDataJSON); const { origin } = clientDataJSON; - const localhostAllowed = tenancy.config.domains.allowLocalhost; - const parsedOrigin = new URL(origin); - const isLocalhost = parsedOrigin.hostname === "localhost"; - - if (!localhostAllowed && isLocalhost) { - throw new KnownErrors.PasskeyAuthenticationFailed("Passkey registration failed because localhost is not allowed"); - } - if (localhostAllowed && isLocalhost) { - expectedRPID = parsedOrigin.hostname; - expectedOrigin = origin; + if (!validateRedirectUrl(origin, tenancy)) { + throw new KnownErrors.PasskeyRegistrationFailed("Passkey registration failed because the origin is not allowed"); } - if (!isLocalhost) { - if (!Object.values(tenancy.config.domains.trustedDomains) - .filter(e => e.baseUrl) - .map(e => e.baseUrl) - .includes(parsedOrigin.origin)) { - throw new KnownErrors.PasskeyAuthenticationFailed("Passkey registration failed because the origin is not allowed"); - } else { - expectedRPID = parsedOrigin.hostname; - expectedOrigin = origin; - } - } + const parsedOrigin = new URL(origin); + const expectedRPID = parsedOrigin.hostname; + const expectedOrigin = origin; let verification; diff --git a/apps/backend/src/app/api/latest/auth/passkey/sign-in/verification-code-handler.tsx b/apps/backend/src/app/api/latest/auth/passkey/sign-in/verification-code-handler.tsx index 690c0753a5..842bb3d4f7 100644 --- a/apps/backend/src/app/api/latest/auth/passkey/sign-in/verification-code-handler.tsx +++ b/apps/backend/src/app/api/latest/auth/passkey/sign-in/verification-code-handler.tsx @@ -1,3 +1,4 @@ +import { validateRedirectUrl } from "@/lib/redirect-urls"; import { createAuthTokens } from "@/lib/tokens"; import { getPrismaClientForTenancy } from "@/prisma-client"; import { createVerificationCodeHandler } from "@/route-handlers/verification-code-handler"; @@ -63,34 +64,16 @@ export const passkeySignInVerificationCodeHandler = createVerificationCodeHandle } // HACK: we validate origin and rpid outside of simpleauth, this should be replaced once we have a primary authentication domain - let expectedRPID = ""; - let expectedOrigin = ""; const clientDataJSON = decodeClientDataJSON(authentication_response.response.clientDataJSON); const { origin } = clientDataJSON; - const localhostAllowed = tenancy.config.domains.allowLocalhost; - const parsedOrigin = new URL(origin); - const isLocalhost = parsedOrigin.hostname === "localhost"; - - if (!localhostAllowed && isLocalhost) { - throw new KnownErrors.PasskeyAuthenticationFailed("Passkey authentication failed because localhost is not allowed"); - } - if (localhostAllowed && isLocalhost) { - expectedRPID = parsedOrigin.hostname; - expectedOrigin = origin; + if (!validateRedirectUrl(origin, tenancy)) { + throw new KnownErrors.PasskeyAuthenticationFailed("Passkey authentication failed because the origin is not allowed"); } - if (!isLocalhost) { - if (!Object.values(tenancy.config.domains.trustedDomains) - .filter(e => e.baseUrl) - .map(e => e.baseUrl) - .includes(parsedOrigin.origin)) { - throw new KnownErrors.PasskeyAuthenticationFailed("Passkey authentication failed because the origin is not allowed"); - } else { - expectedRPID = parsedOrigin.hostname; - expectedOrigin = origin; - } - } + const parsedOrigin = new URL(origin); + const expectedRPID = parsedOrigin.hostname; + const expectedOrigin = origin; let authVerify; authVerify = await verifyAuthenticationResponse({ diff --git a/apps/backend/src/lib/redirect-urls.test.tsx b/apps/backend/src/lib/redirect-urls.test.tsx new file mode 100644 index 0000000000..efa71819eb --- /dev/null +++ b/apps/backend/src/lib/redirect-urls.test.tsx @@ -0,0 +1,462 @@ +import { describe, it, expect } from 'vitest'; +import { validateRedirectUrl } from './redirect-urls'; +import { Tenancy } from './tenancies'; + +describe('validateRedirectUrl', () => { + const createMockTenancy = (config: Partial): Tenancy => { + return { + config: { + domains: { + allowLocalhost: false, + trustedDomains: {}, + ...config.domains, + }, + ...config, + }, + } as Tenancy; + }; + + describe('exact domain matching', () => { + it('should validate exact domain matches', () => { + const tenancy = createMockTenancy({ + domains: { + allowLocalhost: false, + trustedDomains: { + '1': { baseUrl: 'https://example.com', handlerPath: '/handler' }, + }, + }, + }); + + expect(validateRedirectUrl('https://example.com/handler', tenancy)).toBe(true); + expect(validateRedirectUrl('https://example.com/handler/callback', tenancy)).toBe(true); + expect(validateRedirectUrl('https://example.com/other', tenancy)).toBe(false); + expect(validateRedirectUrl('https://other.com/handler', tenancy)).toBe(false); + }); + + it('should validate protocol matching', () => { + const tenancy = createMockTenancy({ + domains: { + allowLocalhost: false, + trustedDomains: { + '1': { baseUrl: 'https://example.com', handlerPath: '/handler' }, + }, + }, + }); + + expect(validateRedirectUrl('https://example.com/handler', tenancy)).toBe(true); + expect(validateRedirectUrl('http://example.com/handler', tenancy)).toBe(false); + }); + }); + + describe('wildcard domain matching', () => { + it('should validate single wildcard subdomain patterns', () => { + const tenancy = createMockTenancy({ + domains: { + allowLocalhost: false, + trustedDomains: { + '1': { baseUrl: 'https://*.example.com', handlerPath: '/handler' }, + }, + }, + }); + + expect(validateRedirectUrl('https://api.example.com/handler', tenancy)).toBe(true); + expect(validateRedirectUrl('https://www.example.com/handler', tenancy)).toBe(true); + expect(validateRedirectUrl('https://staging.example.com/handler', tenancy)).toBe(true); + expect(validateRedirectUrl('https://example.com/handler', tenancy)).toBe(false); + expect(validateRedirectUrl('https://api.v2.example.com/handler', tenancy)).toBe(false); + }); + + it('should validate double wildcard patterns', () => { + const tenancy = createMockTenancy({ + domains: { + allowLocalhost: false, + trustedDomains: { + '1': { baseUrl: 'https://**.example.com', handlerPath: '/handler' }, + }, + }, + }); + + expect(validateRedirectUrl('https://api.example.com/handler', tenancy)).toBe(true); + expect(validateRedirectUrl('https://api.v2.example.com/handler', tenancy)).toBe(true); + expect(validateRedirectUrl('https://a.b.c.example.com/handler', tenancy)).toBe(true); + expect(validateRedirectUrl('https://example.com/handler', tenancy)).toBe(false); + }); + + it('should validate wildcard patterns with prefixes', () => { + const tenancy = createMockTenancy({ + domains: { + allowLocalhost: false, + trustedDomains: { + '1': { baseUrl: 'https://api-*.example.com', handlerPath: '/handler' }, + }, + }, + }); + + expect(validateRedirectUrl('https://api-v1.example.com/handler', tenancy)).toBe(true); + expect(validateRedirectUrl('https://api-v2.example.com/handler', tenancy)).toBe(true); + expect(validateRedirectUrl('https://api-prod.example.com/handler', tenancy)).toBe(true); + expect(validateRedirectUrl('https://api.example.com/handler', tenancy)).toBe(false); + expect(validateRedirectUrl('https://v1-api.example.com/handler', tenancy)).toBe(false); + }); + + it('should validate multiple wildcard patterns', () => { + const tenancy = createMockTenancy({ + domains: { + allowLocalhost: false, + trustedDomains: { + '1': { baseUrl: 'https://*.*.org', handlerPath: '/handler' }, + }, + }, + }); + + expect(validateRedirectUrl('https://mail.example.org/handler', tenancy)).toBe(true); + expect(validateRedirectUrl('https://api.company.org/handler', tenancy)).toBe(true); + expect(validateRedirectUrl('https://example.org/handler', tenancy)).toBe(false); + expect(validateRedirectUrl('https://a.b.c.org/handler', tenancy)).toBe(false); + }); + }); + + describe('localhost handling', () => { + it('should allow localhost when configured', () => { + const tenancy = createMockTenancy({ + domains: { + allowLocalhost: true, + trustedDomains: {}, + }, + }); + + expect(validateRedirectUrl('http://localhost/callback', tenancy)).toBe(true); + expect(validateRedirectUrl('http://localhost:3000/callback', tenancy)).toBe(true); + expect(validateRedirectUrl('http://127.0.0.1/callback', tenancy)).toBe(true); + expect(validateRedirectUrl('http://sub.localhost/callback', tenancy)).toBe(true); + }); + + it('should reject localhost when not configured', () => { + const tenancy = createMockTenancy({ + domains: { + allowLocalhost: false, + trustedDomains: {}, + }, + }); + + expect(validateRedirectUrl('http://localhost/callback', tenancy)).toBe(false); + expect(validateRedirectUrl('http://127.0.0.1/callback', tenancy)).toBe(false); + }); + }); + + describe('path validation', () => { + it('should validate handler path matching', () => { + const tenancy = createMockTenancy({ + domains: { + allowLocalhost: false, + trustedDomains: { + '1': { baseUrl: 'https://example.com', handlerPath: '/auth/handler' }, + }, + }, + }); + + expect(validateRedirectUrl('https://example.com/auth/handler', tenancy)).toBe(true); + expect(validateRedirectUrl('https://example.com/auth/handler/callback', tenancy)).toBe(true); + expect(validateRedirectUrl('https://example.com/auth', tenancy)).toBe(false); + expect(validateRedirectUrl('https://example.com/other/handler', tenancy)).toBe(false); + }); + + it('should work with wildcard domains and path validation', () => { + const tenancy = createMockTenancy({ + domains: { + allowLocalhost: false, + trustedDomains: { + '1': { baseUrl: 'https://*.example.com', handlerPath: '/api/auth' }, + }, + }, + }); + + expect(validateRedirectUrl('https://api.example.com/api/auth', tenancy)).toBe(true); + expect(validateRedirectUrl('https://app.example.com/api/auth/callback', tenancy)).toBe(true); + expect(validateRedirectUrl('https://api.example.com/api', tenancy)).toBe(false); + expect(validateRedirectUrl('https://api.example.com/other/auth', tenancy)).toBe(false); + }); + }); + + describe('port number handling with wildcards', () => { + it('should handle exact domain without port (defaults to standard ports)', () => { + const tenancy = createMockTenancy({ + domains: { + allowLocalhost: false, + trustedDomains: { + '1': { baseUrl: 'https://localhost', handlerPath: '/' }, + }, + }, + }); + + // https://localhost should match https://localhost:443 (default HTTPS port) + expect(validateRedirectUrl('https://localhost/', tenancy)).toBe(true); + expect(validateRedirectUrl('https://localhost:443/', tenancy)).toBe(true); + + // Should NOT match other ports + expect(validateRedirectUrl('https://localhost:3000/', tenancy)).toBe(false); + expect(validateRedirectUrl('https://localhost:8080/', tenancy)).toBe(false); + }); + + it('should handle http domain without port (defaults to port 80)', () => { + const tenancy = createMockTenancy({ + domains: { + allowLocalhost: false, + trustedDomains: { + '1': { baseUrl: 'http://localhost', handlerPath: '/' }, + }, + }, + }); + + // http://localhost should match http://localhost:80 (default HTTP port) + expect(validateRedirectUrl('http://localhost/', tenancy)).toBe(true); + expect(validateRedirectUrl('http://localhost:80/', tenancy)).toBe(true); + + // Should NOT match other ports + expect(validateRedirectUrl('http://localhost:3000/', tenancy)).toBe(false); + expect(validateRedirectUrl('http://localhost:8080/', tenancy)).toBe(false); + }); + + it('should handle wildcard with port pattern to match any port', () => { + const tenancy = createMockTenancy({ + domains: { + allowLocalhost: false, + trustedDomains: { + '1': { baseUrl: 'https://localhost:*', handlerPath: '/' }, + }, + }, + }); + + // Should match localhost on any port + expect(validateRedirectUrl('https://localhost/', tenancy)).toBe(true); + expect(validateRedirectUrl('https://localhost:443/', tenancy)).toBe(true); + expect(validateRedirectUrl('https://localhost:3000/', tenancy)).toBe(true); + expect(validateRedirectUrl('https://localhost:8080/', tenancy)).toBe(true); + expect(validateRedirectUrl('https://localhost:12345/', tenancy)).toBe(true); + + // Should NOT match different hostnames + expect(validateRedirectUrl('https://example.com:3000/', tenancy)).toBe(false); + }); + + it('should handle subdomain wildcard without affecting port matching', () => { + const tenancy = createMockTenancy({ + domains: { + allowLocalhost: false, + trustedDomains: { + '1': { baseUrl: 'https://*.localhost', handlerPath: '/' }, + }, + }, + }); + + // Should match subdomains on default port only + expect(validateRedirectUrl('https://api.localhost/', tenancy)).toBe(true); + expect(validateRedirectUrl('https://api.localhost:443/', tenancy)).toBe(true); + expect(validateRedirectUrl('https://app.localhost/', tenancy)).toBe(true); + + // Should NOT match subdomains on other ports + expect(validateRedirectUrl('https://api.localhost:3000/', tenancy)).toBe(false); + expect(validateRedirectUrl('https://app.localhost:8080/', tenancy)).toBe(false); + + // Should NOT match the base domain (no subdomain) + expect(validateRedirectUrl('https://localhost/', tenancy)).toBe(false); + }); + + it('should handle subdomain wildcard WITH port wildcard', () => { + const tenancy = createMockTenancy({ + domains: { + allowLocalhost: false, + trustedDomains: { + '1': { baseUrl: 'https://*.localhost:*', handlerPath: '/' }, + }, + }, + }); + + // Should match subdomains on any port + expect(validateRedirectUrl('https://api.localhost/', tenancy)).toBe(true); + expect(validateRedirectUrl('https://api.localhost:3000/', tenancy)).toBe(true); + expect(validateRedirectUrl('https://app.localhost:8080/', tenancy)).toBe(true); + expect(validateRedirectUrl('https://staging.localhost:12345/', tenancy)).toBe(true); + + // Should NOT match the base domain (no subdomain) + expect(validateRedirectUrl('https://localhost:3000/', tenancy)).toBe(false); + }); + + it('should handle TLD wildcard without affecting port', () => { + const tenancy = createMockTenancy({ + domains: { + allowLocalhost: false, + trustedDomains: { + '1': { baseUrl: 'https://localhost.*', handlerPath: '/' }, + }, + }, + }); + + // Should match different TLDs on default port + expect(validateRedirectUrl('https://localhost.de/', tenancy)).toBe(true); + expect(validateRedirectUrl('https://localhost.com/', tenancy)).toBe(true); + expect(validateRedirectUrl('https://localhost.org/', tenancy)).toBe(true); + expect(validateRedirectUrl('https://localhost.de:443/', tenancy)).toBe(true); + + // Should NOT match different TLDs on other ports + expect(validateRedirectUrl('https://localhost.de:3000/', tenancy)).toBe(false); + expect(validateRedirectUrl('https://localhost.com:8080/', tenancy)).toBe(false); + + // Should NOT match without TLD + expect(validateRedirectUrl('https://localhost/', tenancy)).toBe(false); + }); + + it('should handle specific port in wildcard pattern', () => { + const tenancy = createMockTenancy({ + domains: { + allowLocalhost: false, + trustedDomains: { + '1': { baseUrl: 'https://*.example.com:8080', handlerPath: '/' }, + }, + }, + }); + + // Should match subdomains only on port 8080 + expect(validateRedirectUrl('https://api.example.com:8080/', tenancy)).toBe(true); + expect(validateRedirectUrl('https://app.example.com:8080/', tenancy)).toBe(true); + + // Should NOT match on other ports + expect(validateRedirectUrl('https://api.example.com/', tenancy)).toBe(false); + expect(validateRedirectUrl('https://api.example.com:443/', tenancy)).toBe(false); + expect(validateRedirectUrl('https://api.example.com:3000/', tenancy)).toBe(false); + }); + + it('should handle double wildcard with port patterns', () => { + const tenancy = createMockTenancy({ + domains: { + allowLocalhost: false, + trustedDomains: { + '1': { baseUrl: 'https://**.example.com:*', handlerPath: '/' }, + }, + }, + }); + + // Should match any subdomain depth on any port + expect(validateRedirectUrl('https://api.example.com:3000/', tenancy)).toBe(true); + expect(validateRedirectUrl('https://api.v2.example.com:8080/', tenancy)).toBe(true); + expect(validateRedirectUrl('https://staging.api.v2.example.com:12345/', tenancy)).toBe(true); + + // Should NOT match base domain + expect(validateRedirectUrl('https://example.com:3000/', tenancy)).toBe(false); + }); + + it('should handle single wildcard (*:*) pattern correctly', () => { + const tenancy = createMockTenancy({ + domains: { + allowLocalhost: false, + trustedDomains: { + '1': { baseUrl: 'http://*:*', handlerPath: '/' }, + }, + }, + }); + + // * matches single level (no dots), so should match simple hostnames on any port + expect(validateRedirectUrl('http://localhost:3000/', tenancy)).toBe(true); + expect(validateRedirectUrl('http://localhost:8080/', tenancy)).toBe(true); + expect(validateRedirectUrl('http://app:12345/', tenancy)).toBe(true); + + // Should NOT match hostnames with dots (need ** for that) + expect(validateRedirectUrl('http://example.com:8080/', tenancy)).toBe(false); + expect(validateRedirectUrl('http://api.test.com:12345/', tenancy)).toBe(false); + + // Should NOT match https (different protocol) + expect(validateRedirectUrl('https://localhost:3000/', tenancy)).toBe(false); + }); + + it('should handle double wildcard (**:*) pattern to match any hostname on any port', () => { + const tenancy = createMockTenancy({ + domains: { + allowLocalhost: false, + trustedDomains: { + '1': { baseUrl: 'http://**:*', handlerPath: '/' }, + }, + }, + }); + + // ** matches any characters including dots, so should match any hostname on any port + expect(validateRedirectUrl('http://localhost:3000/', tenancy)).toBe(true); + expect(validateRedirectUrl('http://example.com:8080/', tenancy)).toBe(true); + expect(validateRedirectUrl('http://api.test.com:12345/', tenancy)).toBe(true); + expect(validateRedirectUrl('http://192.168.1.1:80/', tenancy)).toBe(true); + expect(validateRedirectUrl('http://deeply.nested.subdomain.example.com:9999/', tenancy)).toBe(true); + + // Should NOT match https (different protocol) + expect(validateRedirectUrl('https://localhost:3000/', tenancy)).toBe(false); + }); + + it('should correctly distinguish between port wildcard and subdomain wildcard', () => { + const tenancy = createMockTenancy({ + domains: { + allowLocalhost: false, + trustedDomains: { + '1': { baseUrl: 'https://app-*.example.com', handlerPath: '/' }, + '2': { baseUrl: 'https://api.example.com:*', handlerPath: '/' }, + }, + }, + }); + + // First pattern should match app-* subdomains on default port + expect(validateRedirectUrl('https://app-v1.example.com/', tenancy)).toBe(true); + expect(validateRedirectUrl('https://app-staging.example.com/', tenancy)).toBe(true); + expect(validateRedirectUrl('https://app-v1.example.com:3000/', tenancy)).toBe(false); + + // Second pattern should match api.example.com on any port + expect(validateRedirectUrl('https://api.example.com/', tenancy)).toBe(true); + expect(validateRedirectUrl('https://api.example.com:3000/', tenancy)).toBe(true); + expect(validateRedirectUrl('https://api.example.com:8080/', tenancy)).toBe(true); + expect(validateRedirectUrl('https://api-v1.example.com:3000/', tenancy)).toBe(false); + }); + }); + + describe('edge cases', () => { + it('should handle invalid URLs', () => { + const tenancy = createMockTenancy({ + domains: { + allowLocalhost: false, + trustedDomains: { + '1': { baseUrl: 'https://example.com', handlerPath: '/handler' }, + }, + }, + }); + + expect(validateRedirectUrl('not-a-url', tenancy)).toBe(false); + expect(validateRedirectUrl('', tenancy)).toBe(false); + expect(validateRedirectUrl('javascript:alert(1)', tenancy)).toBe(false); + }); + + it('should handle missing baseUrl in domain config', () => { + const tenancy = createMockTenancy({ + domains: { + allowLocalhost: false, + trustedDomains: { + '1': { baseUrl: undefined as any, handlerPath: '/handler' }, + }, + }, + }); + + expect(validateRedirectUrl('https://example.com/handler', tenancy)).toBe(false); + }); + + it('should handle multiple trusted domains with wildcards', () => { + const tenancy = createMockTenancy({ + domains: { + allowLocalhost: false, + trustedDomains: { + '1': { baseUrl: 'https://example.com', handlerPath: '/handler' }, + '2': { baseUrl: 'https://*.staging.com', handlerPath: '/auth' }, + '3': { baseUrl: 'https://**.production.com', handlerPath: '/callback' }, + }, + }, + }); + + expect(validateRedirectUrl('https://example.com/handler', tenancy)).toBe(true); + expect(validateRedirectUrl('https://api.staging.com/auth', tenancy)).toBe(true); + expect(validateRedirectUrl('https://api.v2.production.com/callback', tenancy)).toBe(true); + expect(validateRedirectUrl('https://other.com/handler', tenancy)).toBe(false); + }); + }); +}); diff --git a/apps/backend/src/lib/redirect-urls.tsx b/apps/backend/src/lib/redirect-urls.tsx index 33acfa8135..23c6d0c164 100644 --- a/apps/backend/src/lib/redirect-urls.tsx +++ b/apps/backend/src/lib/redirect-urls.tsx @@ -1,5 +1,5 @@ import { StackAssertionError, captureError } from "@stackframe/stack-shared/dist/utils/errors"; -import { createUrlIfValid, isLocalhost } from "@stackframe/stack-shared/dist/utils/urls"; +import { createUrlIfValid, isLocalhost, matchHostnamePattern } from "@stackframe/stack-shared/dist/utils/urls"; import { Tenancy } from "./tenancies"; export function validateRedirectUrl( @@ -17,17 +17,94 @@ export function validateRedirectUrl( } const testUrl = url; - const baseUrl = createUrlIfValid(domain.baseUrl); - if (!baseUrl) { - captureError("invalid-redirect-domain", new StackAssertionError("Invalid redirect domain; maybe this should be fixed in the database", { - domain: domain.baseUrl, - })); - return false; - } - const sameOrigin = baseUrl.protocol === testUrl.protocol && baseUrl.hostname === testUrl.hostname; - const isSubPath = testUrl.pathname.startsWith(baseUrl.pathname); + // Check if the domain uses wildcards + const hasWildcard = domain.baseUrl.includes('*'); + + if (hasWildcard) { + // For wildcard domains, we need to parse the pattern manually + // Extract protocol, hostname pattern, and path + const protocolEnd = domain.baseUrl.indexOf('://'); + if (protocolEnd === -1) { + captureError("invalid-redirect-domain", new StackAssertionError("Invalid domain format; missing protocol", { + domain: domain.baseUrl, + })); + return false; + } + + const protocol = domain.baseUrl.substring(0, protocolEnd + 3); + const afterProtocol = domain.baseUrl.substring(protocolEnd + 3); + const pathStart = afterProtocol.indexOf('/'); + const hostPattern = pathStart === -1 ? afterProtocol : afterProtocol.substring(0, pathStart); + const basePath = pathStart === -1 ? '/' : afterProtocol.substring(pathStart); + + // Check protocol + if (testUrl.protocol + '//' !== protocol) { + return false; + } + + // Check host (including port) with wildcard pattern + // We need to handle port matching correctly + const hasPortInPattern = hostPattern.includes(':'); + + if (hasPortInPattern) { + // Pattern includes port - match against full host (hostname:port) + // Need to normalize for default ports + let normalizedTestHost = testUrl.host; + if (testUrl.port === '' || + (testUrl.protocol === 'https:' && testUrl.port === '443') || + (testUrl.protocol === 'http:' && testUrl.port === '80')) { + // Add default port explicitly for matching when pattern has a port + const defaultPort = testUrl.protocol === 'https:' ? '443' : '80'; + normalizedTestHost = testUrl.hostname + ':' + (testUrl.port || defaultPort); + } + + if (!matchHostnamePattern(hostPattern, normalizedTestHost)) { + return false; + } + } else { + // Pattern doesn't include port - match hostname only and check port separately + if (!matchHostnamePattern(hostPattern, testUrl.hostname)) { + return false; + } - return sameOrigin && isSubPath; + // When no port is specified in pattern, only allow default ports + const isDefaultPort = + (testUrl.protocol === 'https:' && (testUrl.port === '' || testUrl.port === '443')) || + (testUrl.protocol === 'http:' && (testUrl.port === '' || testUrl.port === '80')); + + if (!isDefaultPort) { + return false; + } + } + + // Check path + const handlerPath = domain.handlerPath || '/'; + const fullBasePath = basePath === '/' ? handlerPath : basePath + handlerPath; + return testUrl.pathname.startsWith(fullBasePath); + } else { + // For non-wildcard domains, use the original logic + const baseUrl = createUrlIfValid(domain.baseUrl); + if (!baseUrl) { + captureError("invalid-redirect-domain", new StackAssertionError("Invalid redirect domain; maybe this should be fixed in the database", { + domain: domain.baseUrl, + })); + return false; + } + + const protocolMatches = baseUrl.protocol === testUrl.protocol; + const hostnameMatches = baseUrl.hostname === testUrl.hostname; + + // Check port matching for non-wildcard domains + const portMatches = baseUrl.port === testUrl.port || + (baseUrl.port === '' && testUrl.protocol === 'https:' && testUrl.port === '443') || + (baseUrl.port === '' && testUrl.protocol === 'http:' && testUrl.port === '80') || + (testUrl.port === '' && baseUrl.protocol === 'https:' && baseUrl.port === '443') || + (testUrl.port === '' && baseUrl.protocol === 'http:' && baseUrl.port === '80'); + + const pathMatches = testUrl.pathname.startsWith(domain.handlerPath || '/'); + + return protocolMatches && hostnameMatches && portMatches && pathMatches; + } }); } diff --git a/apps/backend/src/oauth/model.tsx b/apps/backend/src/oauth/model.tsx index fd049393fc..eff9bd497b 100644 --- a/apps/backend/src/oauth/model.tsx +++ b/apps/backend/src/oauth/model.tsx @@ -52,9 +52,13 @@ export class OAuthModel implements AuthorizationCodeModel { let redirectUris: string[] = []; try { - redirectUris = Object.entries(tenancy.config.domains.trustedDomains).map( - ([_, domain]) => new URL(domain.handlerPath, domain.baseUrl).toString() - ); + redirectUris = Object.entries(tenancy.config.domains.trustedDomains) + // note that this may include wildcard domains, which is fine because we correctly account for them in + // model.validateRedirectUri(...) + .filter(([_, domain]) => { + return domain.baseUrl; + }) + .map(([_, domain]) => new URL(domain.handlerPath, domain.baseUrl).toString()); } catch (e) { captureError("get-oauth-redirect-urls", { error: e, diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/domains/page-client.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/domains/page-client.tsx index 7cd55b2e82..99fdee72b4 100644 --- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/domains/page-client.tsx +++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/domains/page-client.tsx @@ -5,7 +5,7 @@ import { SettingCard, SettingSwitch } from "@/components/settings"; import { AdminDomainConfig, AdminProject } from "@stackframe/stack"; import { yupString } from "@stackframe/stack-shared/dist/schema-fields"; import { StackAssertionError } from "@stackframe/stack-shared/dist/utils/errors"; -import { isValidHostname, isValidUrl } from "@stackframe/stack-shared/dist/utils/urls"; +import { isValidHostnameWithWildcards, isValidUrl } from "@stackframe/stack-shared/dist/utils/urls"; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, ActionCell, ActionDialog, Alert, Button, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Typography } from "@stackframe/stack-ui"; import React from "react"; import * as yup from "yup"; @@ -35,7 +35,7 @@ function EditDialog(props: { .test({ name: 'domain', message: (params) => `Invalid domain`, - test: (value) => value == null || isValidHostname(value) + test: (value) => value == null || isValidHostnameWithWildcards(value) }) .test({ name: 'unique-domain', @@ -77,6 +77,11 @@ function EditDialog(props: { return false; } + // Don't allow adding www. to wildcard domains + if (domain.includes('*')) { + return false; + } + const httpsUrl = 'https://' + domain; if (!isValidUrl(httpsUrl)) { return false; @@ -153,7 +158,16 @@ function EditDialog(props: { render={(form) => ( <> - Please ensure you own or have control over this domain. Also note that each subdomain (e.g. blog.example.com, app.example.com) is treated as a distinct domain. +
+

Please ensure you own or have control over this domain. Also note that each subdomain (e.g. blog.example.com, app.example.com) is treated as a distinct domain.

+

Wildcard domains: You can use wildcards to match multiple domains:

+
    +
  • *.example.com - matches any single subdomain (e.g., api.example.com, www.example.com)
  • +
  • **.example.com - matches any subdomain level (e.g., api.v2.example.com)
  • +
  • api-*.example.com - matches api-v1.example.com, api-prod.example.com, etc.
  • +
  • *.*.org - matches mail.example.org, but not example.org
  • +
+
{ + it("should allow OAuth with exact matching domain", async ({ expect }) => { + const { adminAccessToken } = await Project.createAndSwitch({ + config: { + oauth_providers: [{ id: "spotify", type: "shared" }], + } + }); + await InternalApiKey.createAndSetProjectKeys(); + + // Add exact domain that matches our redirect URL + const configResponse = await niceBackendFetch("/api/v1/internal/config/override", { + method: "PATCH", + accessType: "admin", + headers: { + 'x-stack-admin-access-token': adminAccessToken, + }, + body: { + config_override_string: JSON.stringify({ + 'domains.trustedDomains.exact': { + baseUrl: 'http://localhost:8107', + handlerPath: '/handler', + }, + 'domains.allowLocalhost': true, + }), + }, + }); + expect(configResponse.status).toBe(200); + + // OAuth flow should succeed + const response = await Auth.OAuth.signIn(); + expect(response.tokenResponse.status).toBe(200); + await Auth.expectToBeSignedIn(); + }); + + it("should reject OAuth with non-matching exact domain", async ({ expect }) => { + const { adminAccessToken } = await Project.createAndSwitch({ + config: { + oauth_providers: [{ id: "spotify", type: "shared" }], + } + }); + await InternalApiKey.createAndSetProjectKeys(); + + // Add exact domain that does NOT match + const configResponse = await niceBackendFetch("/api/v1/internal/config/override", { + method: "PATCH", + accessType: "admin", + headers: { + 'x-stack-admin-access-token': adminAccessToken, + }, + body: { + config_override_string: JSON.stringify({ + 'domains.trustedDomains.production': { + baseUrl: 'https://app.production.com', + handlerPath: '/auth/handler', + }, + 'domains.allowLocalhost': false, // Ensure we only check exact domains + }), + }, + }); + expect(configResponse.status).toBe(200); + + // Try to complete the OAuth flow - it should fail at the callback stage + const { response } = await Auth.OAuth.getMaybeFailingAuthorizationCode(); + expect(response.status).toBe(400); + expect(response.body).toBe("Invalid redirect URI. The URL you are trying to redirect to is not trusted. If it should be, add it to the list of trusted domains in the Stack Auth dashboard."); + }); + + it("should match exact subdomain but not other subdomains", async ({ expect }) => { + const { adminAccessToken } = await Project.createAndSwitch({ + config: { + oauth_providers: [{ id: "spotify", type: "shared" }], + } + }); + await InternalApiKey.createAndSetProjectKeys(); + + // Add exact subdomain + const configResponse = await niceBackendFetch("/api/v1/internal/config/override", { + method: "PATCH", + accessType: "admin", + headers: { + 'x-stack-admin-access-token': adminAccessToken, + }, + body: { + config_override_string: JSON.stringify({ + 'domains.trustedDomains.subdomain': { + baseUrl: 'https://app.example.com', + handlerPath: '/handler', + }, + 'domains.allowLocalhost': false, + }), + }, + }); + expect(configResponse.status).toBe(200); + + // Try to complete the OAuth flow - it should fail at the callback stage + const { response } = await Auth.OAuth.getMaybeFailingAuthorizationCode(); + expect(response.status).toBe(400); + expect(response.body).toBe("Invalid redirect URI. The URL you are trying to redirect to is not trusted. If it should be, add it to the list of trusted domains in the Stack Auth dashboard."); + }); + + it("should require exact port matching", async ({ expect }) => { + const { adminAccessToken } = await Project.createAndSwitch({ + config: { + oauth_providers: [{ id: "spotify", type: "shared" }], + } + }); + await InternalApiKey.createAndSetProjectKeys(); + + // Add domain with specific port + const configResponse = await niceBackendFetch("/api/v1/internal/config/override", { + method: "PATCH", + accessType: "admin", + headers: { + 'x-stack-admin-access-token': adminAccessToken, + }, + body: { + config_override_string: JSON.stringify({ + 'domains.trustedDomains.withport': { + baseUrl: 'http://localhost:3000', + handlerPath: '/handler', + }, + 'domains.allowLocalhost': false, + }), + }, + }); + expect(configResponse.status).toBe(200); + + // Try to complete the OAuth flow - it should fail at the callback stage + const { response } = await Auth.OAuth.getMaybeFailingAuthorizationCode(); + expect(response.status).toBe(400); + expect(response.body).toBe("Invalid redirect URI. The URL you are trying to redirect to is not trusted. If it should be, add it to the list of trusted domains in the Stack Auth dashboard."); + }); + + it("should require exact protocol matching", async ({ expect }) => { + const { adminAccessToken } = await Project.createAndSwitch({ + config: { + oauth_providers: [{ id: "spotify", type: "shared" }], + } + }); + await InternalApiKey.createAndSetProjectKeys(); + + // Add HTTPS domain + const configResponse = await niceBackendFetch("/api/v1/internal/config/override", { + method: "PATCH", + accessType: "admin", + headers: { + 'x-stack-admin-access-token': adminAccessToken, + }, + body: { + config_override_string: JSON.stringify({ + 'domains.trustedDomains.https': { + baseUrl: 'https://localhost:8107', + handlerPath: '/handler', + }, + 'domains.allowLocalhost': false, + }), + }, + }); + expect(configResponse.status).toBe(200); + + // Try to complete the OAuth flow - it should fail at the callback stage + const { response } = await Auth.OAuth.getMaybeFailingAuthorizationCode(); + expect(response.status).toBe(400); + expect(response.body).toBe("Invalid redirect URI. The URL you are trying to redirect to is not trusted. If it should be, add it to the list of trusted domains in the Stack Auth dashboard."); + }); + + it("should match path prefix correctly", async ({ expect }) => { + const { adminAccessToken } = await Project.createAndSwitch({ + config: { + oauth_providers: [{ id: "spotify", type: "shared" }], + } + }); + await InternalApiKey.createAndSetProjectKeys(); + + // Add domain with specific handler path + const configResponse = await niceBackendFetch("/api/v1/internal/config/override", { + method: "PATCH", + accessType: "admin", + headers: { + 'x-stack-admin-access-token': adminAccessToken, + }, + body: { + config_override_string: JSON.stringify({ + 'domains.trustedDomains.withpath': { + baseUrl: 'http://localhost:8107', + handlerPath: '/auth/oauth/callback', // Different path than default /handler + }, + 'domains.allowLocalhost': false, + }), + }, + }); + expect(configResponse.status).toBe(200); + + // Try to complete the OAuth flow - it should fail at the callback stage + const { response } = await Auth.OAuth.getMaybeFailingAuthorizationCode(); + expect(response.status).toBe(400); + expect(response.body).toBe("Invalid redirect URI. The URL you are trying to redirect to is not trusted. If it should be, add it to the list of trusted domains in the Stack Auth dashboard."); + }); + + it("should work with multiple exact domains where one matches", async ({ expect }) => { + const { adminAccessToken } = await Project.createAndSwitch({ + config: { + oauth_providers: [{ id: "spotify", type: "shared" }], + } + }); + await InternalApiKey.createAndSetProjectKeys(); + + // Add multiple domains + const configResponse = await niceBackendFetch("/api/v1/internal/config/override", { + method: "PATCH", + accessType: "admin", + headers: { + 'x-stack-admin-access-token': adminAccessToken, + }, + body: { + config_override_string: JSON.stringify({ + 'domains.trustedDomains.prod': { + baseUrl: 'https://app.production.com', + handlerPath: '/handler', + }, + 'domains.trustedDomains.staging': { + baseUrl: 'https://app.staging.com', + handlerPath: '/handler', + }, + 'domains.trustedDomains.local': { + baseUrl: 'http://localhost:8107', // This one matches! + handlerPath: '/handler', + }, + 'domains.allowLocalhost': true, + }), + }, + }); + expect(configResponse.status).toBe(200); + + // OAuth should succeed with the matching domain + const response = await Auth.OAuth.signIn(); + expect(response.tokenResponse.status).toBe(200); + }); + + it("should fail when no exact domains match", async ({ expect }) => { + const { adminAccessToken } = await Project.createAndSwitch({ + config: { + oauth_providers: [{ id: "spotify", type: "shared" }], + } + }); + await InternalApiKey.createAndSetProjectKeys(); + + // Add multiple domains, none match localhost:8107 + const configResponse = await niceBackendFetch("/api/v1/internal/config/override", { + method: "PATCH", + accessType: "admin", + headers: { + 'x-stack-admin-access-token': adminAccessToken, + }, + body: { + config_override_string: JSON.stringify({ + 'domains.trustedDomains.prod': { + baseUrl: 'https://app.production.com', + handlerPath: '/handler', + }, + 'domains.trustedDomains.staging': { + baseUrl: 'https://app.staging.com', + handlerPath: '/handler', + }, + 'domains.trustedDomains.differentPort': { + baseUrl: 'http://localhost:3000', // Different port + handlerPath: '/handler', + }, + 'domains.allowLocalhost': false, + }), + }, + }); + expect(configResponse.status).toBe(200); + + // Try to complete the OAuth flow - it should fail at the callback stage + const { response } = await Auth.OAuth.getMaybeFailingAuthorizationCode(); + expect(response.status).toBe(400); + expect(response.body).toBe("Invalid redirect URI. The URL you are trying to redirect to is not trusted. If it should be, add it to the list of trusted domains in the Stack Auth dashboard."); + }); +}); diff --git a/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts b/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts new file mode 100644 index 0000000000..a3bc418778 --- /dev/null +++ b/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts @@ -0,0 +1,313 @@ +import { describe } from "vitest"; +import { it, localRedirectUrl } from "../../../../../../helpers"; +import { Auth, InternalApiKey, Project, niceBackendFetch } from "../../../../../backend-helpers"; + +describe("OAuth with wildcard domains", () => { + it("should work with exact domain configuration", async ({ expect }) => { + const { adminAccessToken } = await Project.createAndSwitch({ + config: { + oauth_providers: [{ id: "spotify", type: "shared" }], + } + }); + await InternalApiKey.createAndSetProjectKeys(); + + // Add exact domain matching our test redirect URL + const configResponse = await niceBackendFetch("/api/v1/internal/config/override", { + method: "PATCH", + accessType: "admin", + headers: { + 'x-stack-admin-access-token': adminAccessToken, + }, + body: { + config_override_string: JSON.stringify({ + 'domains.trustedDomains.exact': { + baseUrl: 'http://localhost:8107', + handlerPath: '/handler', + }, + 'domains.allowLocalhost': true, + }), + }, + }); + expect(configResponse.status).toBe(200); + + // OAuth flow should work + const response = await Auth.OAuth.signIn(); + expect(response.tokenResponse.status).toBe(200); + }); + + it("should FAIL with exact domain that doesn't match", async ({ expect }) => { + const { adminAccessToken } = await Project.createAndSwitch({ + config: { + oauth_providers: [{ id: "spotify", type: "shared" }], + } + }); + await InternalApiKey.createAndSetProjectKeys(); + + // Add exact domain that DOESN'T match our test redirect URL + const configResponse = await niceBackendFetch("/api/v1/internal/config/override", { + method: "PATCH", + accessType: "admin", + headers: { + 'x-stack-admin-access-token': adminAccessToken, + }, + body: { + config_override_string: JSON.stringify({ + 'domains.trustedDomains.exact': { + baseUrl: 'https://app.example.com', + handlerPath: '/handler', + }, + 'domains.allowLocalhost': false, // Disable localhost to ensure exact matching + }), + }, + }); + expect(configResponse.status).toBe(200); + + // Try to complete the OAuth flow - it should fail at the callback stage + const { response } = await Auth.OAuth.getMaybeFailingAuthorizationCode(); + expect(response.status).toBe(400); + expect(response.body).toBe("Invalid redirect URI. The URL you are trying to redirect to is not trusted. If it should be, add it to the list of trusted domains in the Stack Auth dashboard."); + }); + + it("should work with single wildcard domain", async ({ expect }) => { + const { adminAccessToken } = await Project.createAndSwitch({ + config: { + oauth_providers: [{ id: "spotify", type: "shared" }], + } + }); + await InternalApiKey.createAndSetProjectKeys(); + + // Add wildcard domain + const configResponse = await niceBackendFetch("/api/v1/internal/config/override", { + method: "PATCH", + accessType: "admin", + headers: { + 'x-stack-admin-access-token': adminAccessToken, + }, + body: { + config_override_string: JSON.stringify({ + 'domains.trustedDomains.wildcard': { + baseUrl: 'http://*.localhost:8107', + handlerPath: '/handler', + }, + 'domains.allowLocalhost': true, + }), + }, + }); + expect(configResponse.status).toBe(200); + + // OAuth flow should work with localhost + const response = await Auth.OAuth.signIn(); + expect(response.tokenResponse.status).toBe(200); + }); + + it("should FAIL with single wildcard that doesn't match", async ({ expect }) => { + const { adminAccessToken } = await Project.createAndSwitch({ + config: { + oauth_providers: [{ id: "spotify", type: "shared" }], + } + }); + await InternalApiKey.createAndSetProjectKeys(); + + // Add wildcard domain that doesn't match localhost pattern + const configResponse = await niceBackendFetch("/api/v1/internal/config/override", { + method: "PATCH", + accessType: "admin", + headers: { + 'x-stack-admin-access-token': adminAccessToken, + }, + body: { + config_override_string: JSON.stringify({ + 'domains.trustedDomains.wildcard': { + baseUrl: 'https://*.example.com', + handlerPath: '/handler', + }, + 'domains.allowLocalhost': false, + }), + }, + }); + expect(configResponse.status).toBe(200); + + // Try to complete the OAuth flow - it should fail at the callback stage + const { response } = await Auth.OAuth.getMaybeFailingAuthorizationCode(); + expect(response.status).toBe(400); + expect(response.body).toBe("Invalid redirect URI. The URL you are trying to redirect to is not trusted. If it should be, add it to the list of trusted domains in the Stack Auth dashboard."); + }); + + it("should work with double wildcard domain", async ({ expect }) => { + const { adminAccessToken } = await Project.createAndSwitch({ + config: { + oauth_providers: [{ id: "spotify", type: "shared" }], + } + }); + await InternalApiKey.createAndSetProjectKeys(); + + // Add double wildcard domain + const configResponse = await niceBackendFetch("/api/v1/internal/config/override", { + method: "PATCH", + accessType: "admin", + headers: { + 'x-stack-admin-access-token': adminAccessToken, + }, + body: { + config_override_string: JSON.stringify({ + 'domains.trustedDomains.double': { + baseUrl: 'http://**.localhost:8107', + handlerPath: '/handler', + }, + 'domains.allowLocalhost': true, + }), + }, + }); + expect(configResponse.status).toBe(200); + + // OAuth flow should work + const response = await Auth.OAuth.signIn(); + expect(response.tokenResponse.status).toBe(200); + }); + + it("should FAIL with double wildcard that doesn't match", async ({ expect }) => { + const { adminAccessToken } = await Project.createAndSwitch({ + config: { + oauth_providers: [{ id: "spotify", type: "shared" }], + } + }); + await InternalApiKey.createAndSetProjectKeys(); + + // Add double wildcard for different TLD + const configResponse = await niceBackendFetch("/api/v1/internal/config/override", { + method: "PATCH", + accessType: "admin", + headers: { + 'x-stack-admin-access-token': adminAccessToken, + }, + body: { + config_override_string: JSON.stringify({ + 'domains.trustedDomains.double': { + baseUrl: 'https://**.example.org', // Different TLD - won't match localhost + handlerPath: '/handler', + }, + 'domains.allowLocalhost': false, + }), + }, + }); + expect(configResponse.status).toBe(200); + + // Try to complete the OAuth flow - it should fail at the callback stage + const { response } = await Auth.OAuth.getMaybeFailingAuthorizationCode(); + expect(response.status).toBe(400); + expect(response.body).toBe("Invalid redirect URI. The URL you are trying to redirect to is not trusted. If it should be, add it to the list of trusted domains in the Stack Auth dashboard."); + }); + + it("should match prefix wildcard patterns correctly", async ({ expect }) => { + const { adminAccessToken } = await Project.createAndSwitch({ + config: { + oauth_providers: [{ id: "spotify", type: "shared" }], + } + }); + await InternalApiKey.createAndSetProjectKeys(); + + // Add prefix wildcard that should match "localhost" + const configResponse = await niceBackendFetch("/api/v1/internal/config/override", { + method: "PATCH", + accessType: "admin", + headers: { + 'x-stack-admin-access-token': adminAccessToken, + }, + body: { + config_override_string: JSON.stringify({ + 'domains.trustedDomains.prefix': { + baseUrl: 'http://local*:8107', // Should match localhost + handlerPath: '/handler', + }, + 'domains.allowLocalhost': true, + }), + }, + }); + expect(configResponse.status).toBe(200); + + // OAuth flow should work + const response = await Auth.OAuth.signIn(); + expect(response.tokenResponse.status).toBe(200); + }); + + it("should FAIL with prefix wildcard that doesn't match", async ({ expect }) => { + const { adminAccessToken } = await Project.createAndSwitch({ + config: { + oauth_providers: [{ id: "spotify", type: "shared" }], + } + }); + await InternalApiKey.createAndSetProjectKeys(); + + // Add prefix wildcard that won't match localhost + const configResponse = await niceBackendFetch("/api/v1/internal/config/override", { + method: "PATCH", + accessType: "admin", + headers: { + 'x-stack-admin-access-token': adminAccessToken, + }, + body: { + config_override_string: JSON.stringify({ + 'domains.trustedDomains.prefix': { + baseUrl: 'http://api-*:8107', // Won't match localhost + handlerPath: '/handler', + }, + 'domains.allowLocalhost': false, + }), + }, + }); + expect(configResponse.status).toBe(200); + + // Try to complete the OAuth flow - it should fail at the callback stage + const { response } = await Auth.OAuth.getMaybeFailingAuthorizationCode(); + expect(response.status).toBe(400); + expect(response.body).toBe("Invalid redirect URI. The URL you are trying to redirect to is not trusted. If it should be, add it to the list of trusted domains in the Stack Auth dashboard."); + }); + + it("should properly validate multiple domains with wildcards", async ({ expect }) => { + const { adminAccessToken } = await Project.createAndSwitch({ + config: { + oauth_providers: [{ id: "spotify", type: "shared" }], + } + }); + + // Configure multiple domains, only one matches + const configResponse = await niceBackendFetch("/api/v1/internal/config/override", { + method: "PATCH", + accessType: "admin", + headers: { + 'x-stack-admin-access-token': adminAccessToken, + }, + body: { + config_override_string: JSON.stringify({ + 'domains.trustedDomains.prod': { + baseUrl: 'https://app.production.com', + handlerPath: '/handler', + }, + 'domains.trustedDomains.staging': { + baseUrl: 'https://*.staging.com', + handlerPath: '/handler', + }, + 'domains.trustedDomains.test': { + baseUrl: 'http://localhost:8107', // This one matches! + handlerPath: '/handler', + }, + 'domains.allowLocalhost': true, + }), + }, + }); + expect(configResponse.status).toBe(200); + + // Get the config to verify all domains are stored + const getResponse = await niceBackendFetch("/api/v1/internal/config", { + accessType: "admin", + headers: { + 'x-stack-admin-access-token': adminAccessToken, + }, + method: "GET", + }); + expect(getResponse.status).toBe(200); + + const config = JSON.parse(getResponse.body.config_string); + expect(Object.keys(config.domains.trustedDomains).length).toBe(3); + }); +}); diff --git a/apps/e2e/tests/backend/endpoints/api/v1/auth/passkey/wildcard-domains.test.ts b/apps/e2e/tests/backend/endpoints/api/v1/auth/passkey/wildcard-domains.test.ts new file mode 100644 index 0000000000..75932f2c79 --- /dev/null +++ b/apps/e2e/tests/backend/endpoints/api/v1/auth/passkey/wildcard-domains.test.ts @@ -0,0 +1,484 @@ +import { describe } from "vitest"; +import { it } from "../../../../../../helpers"; +import { Auth, InternalApiKey, Project, niceBackendFetch } from "../../../../../backend-helpers"; + +describe("Passkey with wildcard domains", () => { + it("should store wildcard domains in config correctly", async ({ expect }) => { + const { adminAccessToken } = await Project.createAndSwitch({ + config: { + passkey_enabled: true, + } + }); + + // Configure various wildcard domains + const configResponse = await niceBackendFetch("/api/v1/internal/config/override", { + method: "PATCH", + accessType: "admin", + headers: { + 'x-stack-admin-access-token': adminAccessToken, + }, + body: { + config_override_string: JSON.stringify({ + 'domains.trustedDomains.exact': { + baseUrl: 'https://app.example.com', + handlerPath: '/handler', + }, + 'domains.trustedDomains.single-wildcard': { + baseUrl: 'https://*.example.com', + handlerPath: '/handler', + }, + 'domains.trustedDomains.prefix-wildcard': { + baseUrl: 'https://api-*.example.com', + handlerPath: '/handler', + }, + 'domains.trustedDomains.double-wildcard': { + baseUrl: 'https://**.example.com', + handlerPath: '/handler', + }, + 'domains.trustedDomains.multi-level': { + baseUrl: 'https://*.*.test.com', + handlerPath: '/handler', + }, + }), + }, + }); + expect(configResponse.status).toBe(200); + + // Get the config to verify wildcards are stored + const getResponse = await niceBackendFetch("/api/v1/internal/config", { + accessType: "admin", + headers: { + 'x-stack-admin-access-token': adminAccessToken, + }, + method: "GET", + }); + expect(getResponse.status).toBe(200); + + const config = JSON.parse(getResponse.body.config_string); + expect(config.domains.trustedDomains).toMatchObject({ + 'exact': { + baseUrl: 'https://app.example.com', + handlerPath: '/handler', + }, + 'single-wildcard': { + baseUrl: 'https://*.example.com', + handlerPath: '/handler', + }, + 'prefix-wildcard': { + baseUrl: 'https://api-*.example.com', + handlerPath: '/handler', + }, + 'double-wildcard': { + baseUrl: 'https://**.example.com', + handlerPath: '/handler', + }, + 'multi-level': { + baseUrl: 'https://*.*.test.com', + handlerPath: '/handler', + }, + }); + }); + + it("should successfully register passkey with matching wildcard domain", async ({ expect }) => { + await Project.createAndSwitch({ + config: { + passkey_enabled: true, + magic_link_enabled: true + } + }); + await InternalApiKey.createAndSetProjectKeys(); + + // Sign up a user first + const res = await Auth.Password.signUpWithEmail(); + + // Configure wildcard domain that matches our test origin + const configResponse = await niceBackendFetch("/api/v1/internal/config/override", { + method: "PATCH", + accessType: "admin", + body: { + config_override_string: JSON.stringify({ + 'domains.trustedDomains.wildcard': { + baseUrl: 'http://*:8103', // Will match http://localhost:8103 and any host on port 8103 + handlerPath: '/', + }, + 'domains.allowLocalhost': false, // Disable default localhost to test wildcard + }), + }, + }); + expect(configResponse.status).toBe(200); + + // Initiate passkey registration + const initiateResponse = await niceBackendFetch("/api/v1/auth/passkey/initiate-passkey-registration", { + method: "POST", + accessType: "client", + body: {}, + }); + expect(initiateResponse.status).toBe(200); + const { code } = initiateResponse.body; + + // Register passkey with origin matching wildcard + const registerResponse = await niceBackendFetch("/api/v1/auth/passkey/register", { + method: "POST", + accessType: "client", + body: { + "credential": { + "id": "WILDCARD_TEST_ID", + "rawId": "WILDCARD_TEST_ID", + "response": { + "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViYSZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NdAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAQWGAfwysz2R5taOiCxqOkpP3AXpQECAyYgASFYIO7JJihe93CDhZOPFp9pVefZyBvy62JMjSs47id1q0vpIlggNMjLAQG7ESYqRZsBQbX07WWIImEzYFDsJgBOSYiQZL8", + "clientDataJSON": btoa(JSON.stringify({ + type: "webauthn.create", + challenge: "TU9DSw", + origin: "http://localhost:8103", // Matches wildcard *:8103 + crossOrigin: false + })), + "transports": ["hybrid", "internal"], + "publicKeyAlgorithm": -7, + "publicKey": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7skmKF73cIOFk48Wn2lV59nIG_LrYkyNKzjuJ3WrS-k0yMsBAbsRJipFmwFBtfTtZYgiYTNgUOwmAE5JiJBkvw", + "authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NdAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAQWGAfwysz2R5taOiCxqOkpP3AXpQECAyYgASFYIO7JJihe93CDhZOPFp9pVefZyBvy62JMjSs47id1q0vpIlggNMjLAQG7ESYqRZsBQbX07WWIImEzYFDsJgBOSYiQZL8" + }, + "type": "public-key", + "clientExtensionResults": { + "credProps": { + "rk": true + } + }, + "authenticatorAttachment": "platform" + }, + "code": code, + }, + }); + + if (registerResponse.status !== 200) { + console.log("Register failed with:", registerResponse.body); + } + expect(registerResponse.status).toBe(200); + expect(registerResponse.body).toHaveProperty("user_handle"); + }); + + it("should successfully sign in with passkey using matching double wildcard domain", async ({ expect }) => { + await Project.createAndSwitch({ + config: { + passkey_enabled: true, + } + }); + await InternalApiKey.createAndSetProjectKeys(); + + // Sign up and register passkey with default localhost allowed + const res = await Auth.Password.signUpWithEmail(); + const expectedUserId = res.userId; + await Auth.Passkey.register(); // This uses http://localhost:8103 + await Auth.signOut(); + + // Configure double wildcard domain that matches localhost:8103 + const configResponse = await niceBackendFetch("/api/v1/internal/config/override", { + method: "PATCH", + accessType: "admin", + body: { + config_override_string: JSON.stringify({ + 'domains.trustedDomains.double': { + baseUrl: 'http://**host:8103', // Will match localhost:8103 + handlerPath: '/', + }, + 'domains.allowLocalhost': false, + }), + }, + }); + expect(configResponse.status).toBe(200); + + // Initiate authentication + const initiateResponse = await niceBackendFetch("/api/v1/auth/passkey/initiate-passkey-authentication", { + method: "POST", + accessType: "client", + body: {}, + }); + expect(initiateResponse.status).toBe(200); + const { code } = initiateResponse.body; + + // Sign in with passkey using deeply nested subdomain + const signinResponse = await niceBackendFetch("/api/v1/auth/passkey/sign-in", { + method: "POST", + accessType: "client", + body: { + "authentication_response": { + "id": "BBYYB_DKzPZHm1o6ILGo6Sk_cBc", + "rawId": "BBYYB_DKzPZHm1o6ILGo6Sk_cBc", + "response": { + "authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MdAAAAAA", + "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiVFU5RFN3Iiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDo4MTAzIiwiY3Jvc3NPcmlnaW4iOmZhbHNlLCJvdGhlcl9rZXlzX2Nhbl9iZV9hZGRlZF9oZXJlIjoiZG8gbm90IGNvbXBhcmUgY2xpZW50RGF0YUpTT04gYWdhaW5zdCBhIHRlbXBsYXRlLiBTZWUgaHR0cHM6Ly9nb28uZ2wveWFiUGV4In0", // Matches **host:8103 + "signature": "MEUCIQDPFYXxm-ALPZVuP4YdXBr1INrfObXR6hukxTttYNnegAIgEfy5MlnIi10VwmilOmuT1TuuDBLw9GDSv9DQuIRZXRE", + "userHandle": "YzE3YzJjNjMtMTkxZi00MWZmLTlkNjEtYzBjOGVlMmVlMGQ0" + }, + "type": "public-key", + "clientExtensionResults": {}, + "authenticatorAttachment": "platform" + }, + "code": code, + }, + }); + + expect(signinResponse.status).toBe(200); + expect(signinResponse.body.user_id).toBe(expectedUserId); + }); + + it("should FAIL passkey registration with non-matching exact domain", async ({ expect }) => { + await Project.createAndSwitch({ + config: { + passkey_enabled: true, + } + }); + await InternalApiKey.createAndSetProjectKeys(); + + // Sign up a user first + await Auth.Password.signUpWithEmail(); + + // Configure exact domain that doesn't match + const configResponse = await niceBackendFetch("/api/v1/internal/config/override", { + method: "PATCH", + accessType: "admin", + body: { + config_override_string: JSON.stringify({ + 'domains.trustedDomains.exact': { + baseUrl: 'https://app.production.com', + handlerPath: '/handler', + }, + 'domains.allowLocalhost': false, + }), + }, + }); + expect(configResponse.status).toBe(200); + + // Initiate passkey registration + const initiateResponse = await niceBackendFetch("/api/v1/auth/passkey/initiate-passkey-registration", { + method: "POST", + accessType: "client", + body: {}, + }); + expect(initiateResponse.status).toBe(200); + const { code } = initiateResponse.body; + + // Try to register passkey with non-matching origin + const registerResponse = await niceBackendFetch("/api/v1/auth/passkey/register", { + method: "POST", + accessType: "client", + body: { + "credential": { + "id": "FAIL_TEST_ID", + "rawId": "FAIL_TEST_ID", + "response": { + "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViYSZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NdAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAQWGAfwysz2R5taOiCxqOkpP3AXpQECAyYgASFYIO7JJihe93CDhZOPFp9pVefZyBvy62JMjSs47id1q0vpIlggNMjLAQG7ESYqRZsBQbX07WWIImEzYFDsJgBOSYiQZL8", + "clientDataJSON": btoa(JSON.stringify({ + type: "webauthn.create", + challenge: "TU9DSw", + origin: "http://localhost:8103", // Doesn't match https://app.production.com + crossOrigin: false + })), + "transports": ["hybrid", "internal"], + "publicKeyAlgorithm": -7, + "publicKey": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7skmKF73cIOFk48Wn2lV59nIG_LrYkyNKzjuJ3WrS-k0yMsBAbsRJipFmwFBtfTtZYgiYTNgUOwmAE5JiJBkvw", + "authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NdAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAQWGAfwysz2R5taOiCxqOkpP3AXpQECAyYgASFYIO7JJihe93CDhZOPFp9pVefZyBvy62JMjSs47id1q0vpIlggNMjLAQG7ESYqRZsBQbX07WWIImEzYFDsJgBOSYiQZL8" + }, + "type": "public-key", + "clientExtensionResults": { + "credProps": { + "rk": true + } + }, + "authenticatorAttachment": "platform" + }, + "code": code, + }, + }); + + expect(registerResponse.status).toBe(400); + expect(registerResponse.body).toMatchObject({ + code: "PASSKEY_REGISTRATION_FAILED", + error: expect.stringContaining("origin is not allowed") + }); + }); + + it("should FAIL passkey sign-in with non-matching wildcard domain", async ({ expect }) => { + await Project.createAndSwitch({ + config: { + passkey_enabled: true, + } + }); + await InternalApiKey.createAndSetProjectKeys(); + + // Sign up and register passkey with default localhost allowed + const res = await Auth.Password.signUpWithEmail(); + await Auth.Passkey.register(); + await Auth.signOut(); + + // Configure wildcard that doesn't match localhost + const configResponse = await niceBackendFetch("/api/v1/internal/config/override", { + method: "PATCH", + accessType: "admin", + body: { + config_override_string: JSON.stringify({ + 'domains.trustedDomains.wildcard': { + baseUrl: 'https://*.example.com', + handlerPath: '/handler', + }, + 'domains.allowLocalhost': false, + }), + }, + }); + expect(configResponse.status).toBe(200); + + // Initiate authentication + const initiateResponse = await niceBackendFetch("/api/v1/auth/passkey/initiate-passkey-authentication", { + method: "POST", + accessType: "client", + body: {}, + }); + expect(initiateResponse.status).toBe(200); + const { code } = initiateResponse.body; + + // Try to sign in with non-matching origin + const signinResponse = await niceBackendFetch("/api/v1/auth/passkey/sign-in", { + method: "POST", + accessType: "client", + body: { + "authentication_response": { + "id": "BBYYB_DKzPZHm1o6ILGo6Sk_cBc", + "rawId": "BBYYB_DKzPZHm1o6ILGo6Sk_cBc", + "response": { + "authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MdAAAAAA", + "clientDataJSON": btoa(JSON.stringify({ + type: "webauthn.get", + challenge: "TU9DSw", + origin: "http://localhost:8103", // Doesn't match *.example.com + crossOrigin: false + })), + "signature": "MEUCIQDPFYXxm-ALPZVuP4YdXBr1INrfObXR6hukxTttYNnegAIgEfy5MlnIi10VwmilOmuT1TuuDBLw9GDSv9DQuIRZXRE", + "userHandle": "YzE3YzJjNjMtMTkxZi00MWZmLTlkNjEtYzBjOGVlMmVlMGQ0" + }, + "type": "public-key", + "clientExtensionResults": {}, + "authenticatorAttachment": "platform" + }, + "code": code, + }, + }); + + expect(signinResponse.status).toBe(400); + expect(signinResponse.body).toMatchObject({ + code: "PASSKEY_AUTHENTICATION_FAILED", + error: expect.stringContaining("origin is not allowed") + }); + }); + + it("should work with prefix wildcard pattern for passkey", async ({ expect }) => { + await Project.createAndSwitch({ + config: { + passkey_enabled: true, + } + }); + await InternalApiKey.createAndSetProjectKeys(); + + // Sign up and register passkey with default localhost allowed + const res = await Auth.Password.signUpWithEmail(); + await Auth.Passkey.register(); // This uses http://localhost:8103 + await Auth.signOut(); + + // Configure wildcard that matches localhost + const configResponse = await niceBackendFetch("/api/v1/internal/config/override", { + method: "PATCH", + accessType: "admin", + body: { + config_override_string: JSON.stringify({ + 'domains.trustedDomains.wildcard': { + baseUrl: 'http://*:8103', // Will match localhost:8103 + handlerPath: '/', + }, + 'domains.allowLocalhost': false, + }), + }, + }); + expect(configResponse.status).toBe(200); + + // Initiate authentication + const initiateResponse = await niceBackendFetch("/api/v1/auth/passkey/initiate-passkey-authentication", { + method: "POST", + accessType: "client", + body: {}, + }); + expect(initiateResponse.status).toBe(200); + const { code } = initiateResponse.body; + + // Sign in with matching prefix pattern + const signinResponse = await niceBackendFetch("/api/v1/auth/passkey/sign-in", { + method: "POST", + accessType: "client", + body: { + "authentication_response": { + "id": "BBYYB_DKzPZHm1o6ILGo6Sk_cBc", + "rawId": "BBYYB_DKzPZHm1o6ILGo6Sk_cBc", + "response": { + "authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MdAAAAAA", + "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiVFU5RFN3Iiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDo4MTAzIiwiY3Jvc3NPcmlnaW4iOmZhbHNlLCJvdGhlcl9rZXlzX2Nhbl9iZV9hZGRlZF9oZXJlIjoiZG8gbm90IGNvbXBhcmUgY2xpZW50RGF0YUpTT04gYWdhaW5zdCBhIHRlbXBsYXRlLiBTZWUgaHR0cHM6Ly9nb28uZ2wveWFiUGV4In0", // Matches *:8103 + "signature": "MEUCIQDPFYXxm-ALPZVuP4YdXBr1INrfObXR6hukxTttYNnegAIgEfy5MlnIi10VwmilOmuT1TuuDBLw9GDSv9DQuIRZXRE", + "userHandle": "YzE3YzJjNjMtMTkxZi00MWZmLTlkNjEtYzBjOGVlMmVlMGQ0" + }, + "type": "public-key", + "clientExtensionResults": {}, + "authenticatorAttachment": "platform" + }, + "code": code, + }, + }); + + expect(signinResponse.status).toBe(200); + expect(signinResponse.body).toHaveProperty("user_id"); + }); + + it("should handle complex wildcard patterns correctly", async ({ expect }) => { + const { adminAccessToken } = await Project.createAndSwitch({ + config: { + passkey_enabled: true, + } + }); + + // Configure complex wildcard patterns + const configResponse = await niceBackendFetch("/api/v1/internal/config/override", { + method: "PATCH", + accessType: "admin", + headers: { + 'x-stack-admin-access-token': adminAccessToken, + }, + body: { + config_override_string: JSON.stringify({ + 'domains.trustedDomains.complex1': { + baseUrl: 'https://api-*.*.example.com', + handlerPath: '/handler', + }, + 'domains.trustedDomains.complex2': { + baseUrl: 'https://**.api.example.com', + handlerPath: '/handler', + }, + 'domains.trustedDomains.complex3': { + baseUrl: 'https://*-staging.example.com', + handlerPath: '/handler', + }, + }), + }, + }); + expect(configResponse.status).toBe(200); + + // Verify the complex patterns are stored correctly + const getResponse = await niceBackendFetch("/api/v1/internal/config", { + accessType: "admin", + headers: { + 'x-stack-admin-access-token': adminAccessToken, + }, + method: "GET", + }); + expect(getResponse.status).toBe(200); + + const config = JSON.parse(getResponse.body.config_string); + expect(config.domains.trustedDomains.complex1.baseUrl).toBe('https://api-*.*.example.com'); + expect(config.domains.trustedDomains.complex2.baseUrl).toBe('https://**.api.example.com'); + expect(config.domains.trustedDomains.complex3.baseUrl).toBe('https://*-staging.example.com'); + }); +}); diff --git a/packages/stack-shared/src/helpers/production-mode.ts b/packages/stack-shared/src/helpers/production-mode.ts index 35f8393fbd..1ab7c5ac04 100644 --- a/packages/stack-shared/src/helpers/production-mode.ts +++ b/packages/stack-shared/src/helpers/production-mode.ts @@ -21,7 +21,9 @@ export function getProductionModeErrors(project: ProjectsCrud["Admin"]["Read"]): for (const { domain } of project.config.domains) { let url; try { - url = new URL(domain); + // For wildcard domains, replace wildcards with a valid placeholder to validate the URL structure + const normalizedDomain = domain.replace(/\*+/g, 'wildcard-placeholder'); + url = new URL(normalizedDomain); } catch (e) { captureError("production-mode-domain-not-valid", new StackAssertionError("Domain was somehow not a valid URL; we should've caught this when setting the domain in the first place", { domain, diff --git a/packages/stack-shared/src/utils/urls.tsx b/packages/stack-shared/src/utils/urls.tsx index b600d55254..64ae4c2e6d 100644 --- a/packages/stack-shared/src/utils/urls.tsx +++ b/packages/stack-shared/src/utils/urls.tsx @@ -36,6 +36,11 @@ import.meta.vitest?.test("isValidUrl", ({ expect }) => { }); export function isValidHostname(hostname: string) { + // Basic validation + if (!hostname || hostname.startsWith('.') || hostname.endsWith('.') || hostname.includes('..')) { + return false; + } + const url = createUrlIfValid(`https://${hostname}`); if (!url) return false; return url.hostname === hostname; @@ -54,6 +59,143 @@ import.meta.vitest?.test("isValidHostname", ({ expect }) => { expect(isValidHostname("example com")).toBe(false); }); +export function isValidHostnameWithWildcards(hostname: string) { + // Empty hostnames are invalid + if (!hostname) return false; + + // Check if it contains wildcards + const hasWildcard = hostname.includes('*'); + + if (!hasWildcard) { + // If no wildcards, validate as a normal hostname + return isValidHostname(hostname); + } + + // Basic validation checks that apply even with wildcards + // - Hostname cannot start or end with a dot + if (hostname.startsWith('.') || hostname.endsWith('.')) { + return false; + } + + // - No consecutive dots + if (hostname.includes('..')) { + return false; + } + + // For wildcard validation, check that non-wildcard parts contain valid characters + // Replace wildcards with a valid placeholder to check the rest + const testHostname = hostname.replace(/\*+/g, 'wildcard'); + + // Check if the resulting string would be a valid hostname + if (!/^[a-zA-Z0-9.-]+$/.test(testHostname)) { + return false; + } + + // Additional check: ensure the pattern makes sense + // Check each segment between wildcards + const segments = hostname.split(/\*+/); + for (let i = 0; i < segments.length; i++) { + const segment = segments[i]; + if (segment === '') continue; // Empty segments are OK (consecutive wildcards) + + // First segment can't start with dot + if (i === 0 && segment.startsWith('.')) { + return false; + } + + // Last segment can't end with dot + if (i === segments.length - 1 && segment.endsWith('.')) { + return false; + } + + // No segment should have consecutive dots + if (segment.includes('..')) { + return false; + } + } + + return true; +} +import.meta.vitest?.test("isValidHostnameWithWildcards", ({ expect }) => { + // Test with valid regular hostnames + expect(isValidHostnameWithWildcards("example.com")).toBe(true); + expect(isValidHostnameWithWildcards("localhost")).toBe(true); + expect(isValidHostnameWithWildcards("sub.domain.example.com")).toBe(true); + + // Test with valid wildcard hostnames + expect(isValidHostnameWithWildcards("*.example.com")).toBe(true); + expect(isValidHostnameWithWildcards("a-*.example.com")).toBe(true); + expect(isValidHostnameWithWildcards("*.*.org")).toBe(true); + expect(isValidHostnameWithWildcards("**.example.com")).toBe(true); + expect(isValidHostnameWithWildcards("sub.**.com")).toBe(true); + expect(isValidHostnameWithWildcards("*-api.*.com")).toBe(true); + + // Test with invalid hostnames + expect(isValidHostnameWithWildcards("")).toBe(false); + expect(isValidHostnameWithWildcards("example.com/path")).toBe(false); + expect(isValidHostnameWithWildcards("https://example.com")).toBe(false); + expect(isValidHostnameWithWildcards("example com")).toBe(false); + expect(isValidHostnameWithWildcards(".example.com")).toBe(false); + expect(isValidHostnameWithWildcards("example.com.")).toBe(false); + expect(isValidHostnameWithWildcards("example..com")).toBe(false); + expect(isValidHostnameWithWildcards("*.example..com")).toBe(false); +}); + +export function matchHostnamePattern(pattern: string, hostname: string): boolean { + // If no wildcards, it's an exact match + if (!pattern.includes('*')) { + return pattern === hostname; + } + + // Convert the pattern to a regex + // First, escape all regex special characters except * + let regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&'); + + // Use a placeholder for ** to handle it separately from single * + const doubleWildcardPlaceholder = '\x00DOUBLE_WILDCARD\x00'; + regexPattern = regexPattern.replace(/\*\*/g, doubleWildcardPlaceholder); + + // Replace single * with a pattern that matches anything except dots + regexPattern = regexPattern.replace(/\*/g, '[^.]*'); + + // Replace the double wildcard placeholder with a pattern that matches anything including dots + regexPattern = regexPattern.replace(new RegExp(doubleWildcardPlaceholder, 'g'), '.*'); + + // Anchor the pattern to match the entire hostname + regexPattern = '^' + regexPattern + '$'; + + try { + const regex = new RegExp(regexPattern); + return regex.test(hostname); + } catch { + return false; + } +} +import.meta.vitest?.test("matchHostnamePattern", ({ expect }) => { + // Test exact matches + expect(matchHostnamePattern("example.com", "example.com")).toBe(true); + expect(matchHostnamePattern("example.com", "other.com")).toBe(false); + + // Test single wildcard matches + expect(matchHostnamePattern("*.example.com", "api.example.com")).toBe(true); + expect(matchHostnamePattern("*.example.com", "www.example.com")).toBe(true); + expect(matchHostnamePattern("*.example.com", "example.com")).toBe(false); + expect(matchHostnamePattern("*.example.com", "api.v2.example.com")).toBe(false); + + // Test double wildcard matches + expect(matchHostnamePattern("**.example.com", "api.example.com")).toBe(true); + expect(matchHostnamePattern("**.example.com", "api.v2.example.com")).toBe(true); + expect(matchHostnamePattern("**.example.com", "a.b.c.example.com")).toBe(true); + expect(matchHostnamePattern("**.example.com", "example.com")).toBe(false); + + // Test complex patterns + expect(matchHostnamePattern("api-*.example.com", "api-v1.example.com")).toBe(true); + expect(matchHostnamePattern("api-*.example.com", "api-v2.example.com")).toBe(true); + expect(matchHostnamePattern("api-*.example.com", "api.example.com")).toBe(false); + expect(matchHostnamePattern("*.*.org", "mail.example.org")).toBe(true); + expect(matchHostnamePattern("*.*.org", "example.org")).toBe(false); +}); + export function isLocalhost(urlOrString: string | URL) { const url = createUrlIfValid(urlOrString); if (!url) return false; From 0ae980b75a51c53b3c0256b2d9cb1afe5780cece Mon Sep 17 00:00:00 2001 From: Konstantin Wohlwend Date: Tue, 5 Aug 2025 17:40:23 -0700 Subject: [PATCH 02/17] fix --- apps/backend/src/lib/redirect-urls.tsx | 166 +++++++++++-------------- 1 file changed, 75 insertions(+), 91 deletions(-) diff --git a/apps/backend/src/lib/redirect-urls.tsx b/apps/backend/src/lib/redirect-urls.tsx index 23c6d0c164..8947b057f2 100644 --- a/apps/backend/src/lib/redirect-urls.tsx +++ b/apps/backend/src/lib/redirect-urls.tsx @@ -2,109 +2,93 @@ import { StackAssertionError, captureError } from "@stackframe/stack-shared/dist import { createUrlIfValid, isLocalhost, matchHostnamePattern } from "@stackframe/stack-shared/dist/utils/urls"; import { Tenancy } from "./tenancies"; -export function validateRedirectUrl( - urlOrString: string | URL, - tenancy: Tenancy, -): boolean { - const url = createUrlIfValid(urlOrString); - if (!url) return false; - if (tenancy.config.domains.allowLocalhost && isLocalhost(url)) { - return true; - } - return Object.values(tenancy.config.domains.trustedDomains).some((domain) => { - if (!domain.baseUrl) { - return false; - } - - const testUrl = url; - - // Check if the domain uses wildcards - const hasWildcard = domain.baseUrl.includes('*'); - - if (hasWildcard) { - // For wildcard domains, we need to parse the pattern manually - // Extract protocol, hostname pattern, and path - const protocolEnd = domain.baseUrl.indexOf('://'); - if (protocolEnd === -1) { - captureError("invalid-redirect-domain", new StackAssertionError("Invalid domain format; missing protocol", { - domain: domain.baseUrl, - })); - return false; - } - - const protocol = domain.baseUrl.substring(0, protocolEnd + 3); - const afterProtocol = domain.baseUrl.substring(protocolEnd + 3); - const pathStart = afterProtocol.indexOf('/'); - const hostPattern = pathStart === -1 ? afterProtocol : afterProtocol.substring(0, pathStart); - const basePath = pathStart === -1 ? '/' : afterProtocol.substring(pathStart); +/** + * Normalizes a URL to include explicit default ports for comparison + */ +function normalizePort(url: URL): string { + const defaultPorts: Record = { 'https:': '443', 'http:': '80' }; + const port = url.port || defaultPorts[url.protocol] || ''; + return port ? `${url.hostname}:${port}` : url.hostname; +} - // Check protocol - if (testUrl.protocol + '//' !== protocol) { - return false; - } +/** + * Checks if a URL uses the default port for its protocol + */ +function isDefaultPort(url: URL): boolean { + return !url.port || + (url.protocol === 'https:' && url.port === '443') || + (url.protocol === 'http:' && url.port === '80'); +} - // Check host (including port) with wildcard pattern - // We need to handle port matching correctly - const hasPortInPattern = hostPattern.includes(':'); +/** + * Checks if two URLs have matching ports (considering default ports) + */ +function portsMatch(url1: URL, url2: URL): boolean { + return normalizePort(url1) === normalizePort(url2); +} - if (hasPortInPattern) { - // Pattern includes port - match against full host (hostname:port) - // Need to normalize for default ports - let normalizedTestHost = testUrl.host; - if (testUrl.port === '' || - (testUrl.protocol === 'https:' && testUrl.port === '443') || - (testUrl.protocol === 'http:' && testUrl.port === '80')) { - // Add default port explicitly for matching when pattern has a port - const defaultPort = testUrl.protocol === 'https:' ? '443' : '80'; - normalizedTestHost = testUrl.hostname + ':' + (testUrl.port || defaultPort); - } +/** + * Validates a URL against a domain pattern (with or without wildcards) + */ +function matchesDomain(testUrl: URL, pattern: string, handlerPath: string): boolean { + const baseUrl = createUrlIfValid(pattern); + + // If pattern is invalid as a URL, it might contain wildcards + if (!baseUrl || pattern.includes('*')) { + // Parse wildcard pattern manually + const match = pattern.match(/^([^:]+:\/\/)([^/]*)(.*)$/); + if (!match) { + captureError("invalid-redirect-domain", new StackAssertionError("Invalid domain pattern", { pattern })); + return false; + } - if (!matchHostnamePattern(hostPattern, normalizedTestHost)) { - return false; - } - } else { - // Pattern doesn't include port - match hostname only and check port separately - if (!matchHostnamePattern(hostPattern, testUrl.hostname)) { - return false; - } + const [, protocol, hostPattern, basePath] = match; - // When no port is specified in pattern, only allow default ports - const isDefaultPort = - (testUrl.protocol === 'https:' && (testUrl.port === '' || testUrl.port === '443')) || - (testUrl.protocol === 'http:' && (testUrl.port === '' || testUrl.port === '80')); + // Check protocol + if (testUrl.protocol + '//' !== protocol) { + return false; + } - if (!isDefaultPort) { - return false; - } + // Check host with wildcard pattern + const hasPortInPattern = hostPattern.includes(':'); + if (hasPortInPattern) { + // Pattern includes port - match against normalized host:port + if (!matchHostnamePattern(hostPattern, normalizePort(testUrl))) { + return false; } - - // Check path - const handlerPath = domain.handlerPath || '/'; - const fullBasePath = basePath === '/' ? handlerPath : basePath + handlerPath; - return testUrl.pathname.startsWith(fullBasePath); } else { - // For non-wildcard domains, use the original logic - const baseUrl = createUrlIfValid(domain.baseUrl); - if (!baseUrl) { - captureError("invalid-redirect-domain", new StackAssertionError("Invalid redirect domain; maybe this should be fixed in the database", { - domain: domain.baseUrl, - })); + // Pattern doesn't include port - match hostname only, require default port + if (!matchHostnamePattern(hostPattern, testUrl.hostname) || !isDefaultPort(testUrl)) { return false; } + } - const protocolMatches = baseUrl.protocol === testUrl.protocol; - const hostnameMatches = baseUrl.hostname === testUrl.hostname; + // Check path + const fullPath = basePath === '/' ? handlerPath : basePath + handlerPath; + return testUrl.pathname.startsWith(fullPath || '/'); + } - // Check port matching for non-wildcard domains - const portMatches = baseUrl.port === testUrl.port || - (baseUrl.port === '' && testUrl.protocol === 'https:' && testUrl.port === '443') || - (baseUrl.port === '' && testUrl.protocol === 'http:' && testUrl.port === '80') || - (testUrl.port === '' && baseUrl.protocol === 'https:' && baseUrl.port === '443') || - (testUrl.port === '' && baseUrl.protocol === 'http:' && baseUrl.port === '80'); + // For non-wildcard patterns, use URL comparison + return baseUrl.protocol === testUrl.protocol && + baseUrl.hostname === testUrl.hostname && + portsMatch(baseUrl, testUrl) && + testUrl.pathname.startsWith(handlerPath || '/'); +} - const pathMatches = testUrl.pathname.startsWith(domain.handlerPath || '/'); +export function validateRedirectUrl( + urlOrString: string | URL, + tenancy: Tenancy, +): boolean { + const url = createUrlIfValid(urlOrString); + if (!url) return false; - return protocolMatches && hostnameMatches && portMatches && pathMatches; - } - }); + // Check localhost permission + if (tenancy.config.domains.allowLocalhost && isLocalhost(url)) { + return true; + } + + // Check trusted domains + return Object.values(tenancy.config.domains.trustedDomains).some(domain => + domain.baseUrl && matchesDomain(url, domain.baseUrl, domain.handlerPath || '/') + ); } From 8524a8af01f28719d4c8c54a2e0231951c167ea6 Mon Sep 17 00:00:00 2001 From: Konsti Wohlwend Date: Wed, 13 Aug 2025 17:45:08 -0700 Subject: [PATCH 03/17] Update apps/backend/src/app/api/latest/auth/oauth/callback/[provider_id]/route.tsx Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- .../app/api/latest/auth/oauth/callback/[provider_id]/route.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/backend/src/app/api/latest/auth/oauth/callback/[provider_id]/route.tsx b/apps/backend/src/app/api/latest/auth/oauth/callback/[provider_id]/route.tsx index c675751b69..5d878e6fe5 100644 --- a/apps/backend/src/app/api/latest/auth/oauth/callback/[provider_id]/route.tsx +++ b/apps/backend/src/app/api/latest/auth/oauth/callback/[provider_id]/route.tsx @@ -403,7 +403,7 @@ const handler = createSmartRouteHandler({ } catch (error) { if (error instanceof InvalidClientError) { if (error.message.includes("redirect_uri") || error.message.includes("redirectUri")) { - console.log("User is trying to authorize OAuth with an invalid redirect URI", error, oauthRequest); + console.log("User is trying to authorize OAuth with an invalid redirect URI", error, { redirectUri: oauthRequest.query?.redirect_uri, clientId: oauthRequest.query?.client_id }); throw new StatusError(400, "Invalid redirect URI. The URL you are trying to redirect to is not trusted. If it should be, add it to the list of trusted domains in the Stack Auth dashboard."); } } else if (error instanceof InvalidScopeError) { From 05f5e44338f17af415bf938507eb0b470aa567a8 Mon Sep 17 00:00:00 2001 From: Konstantin Wohlwend Date: Wed, 13 Aug 2025 17:50:31 -0700 Subject: [PATCH 04/17] move CLAUDE-KNOWLEDGE --- CLAUDE-KNOWLEDGE.md => .claude/CLAUDE-KNOWLEDGE.md | 0 CLAUDE.md | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename CLAUDE-KNOWLEDGE.md => .claude/CLAUDE-KNOWLEDGE.md (100%) diff --git a/CLAUDE-KNOWLEDGE.md b/.claude/CLAUDE-KNOWLEDGE.md similarity index 100% rename from CLAUDE-KNOWLEDGE.md rename to .claude/CLAUDE-KNOWLEDGE.md diff --git a/CLAUDE.md b/CLAUDE.md index cac840b9a3..89e16f0ffc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -74,4 +74,4 @@ To see all development ports, refer to the index.html of `apps/dev-launchpad/pub - The project uses a custom route handler system in the backend for consistent API responses - Sometimes, the typecheck will give errors along the line of "Cannot assign Buffer to Uint8Array" or similar, on changes that are completely unrelated to your own changes. If that happens, tell the user to run `pnpm clean && pnpm i && pnpm run codegen && pnpm build:packages`, and restart the dev server (you cannot run this yourself). After that's done, the typecheck should pass. - When writing tests, prefer .toMatchInlineSnapshot over other selectors, if possible. You can check (and modify) the snapshot-serializer.ts file to see how the snapshots are formatted and how non-deterministic values are handled. -- Whenever you learn something new, or at the latest right before you call the `Stop` tool, write whatever you learned into the CLAUDE-KNOWLEDGE.md file, in the Q&A format in there. You will later be able to look up knowledge from there (based on the question you asked). +- Whenever you learn something new, or at the latest right before you call the `Stop` tool, write whatever you learned into the ./claude/CLAUDE-KNOWLEDGE.md file, in the Q&A format in there. You will later be able to look up knowledge from there (based on the question you asked). From ee9be7dec740b7d098722ecee9261910c2b13631 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Thu, 14 Aug 2025 00:53:20 +0000 Subject: [PATCH 05/17] refactor: use Map for defaultPorts to avoid prototype pollution Replace Record object with Map for defaultPorts to prevent potential prototype pollution vulnerabilities. Co-authored-by: Konsti Wohlwend --- apps/backend/src/lib/redirect-urls.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/backend/src/lib/redirect-urls.tsx b/apps/backend/src/lib/redirect-urls.tsx index 8947b057f2..27a6696939 100644 --- a/apps/backend/src/lib/redirect-urls.tsx +++ b/apps/backend/src/lib/redirect-urls.tsx @@ -6,8 +6,8 @@ import { Tenancy } from "./tenancies"; * Normalizes a URL to include explicit default ports for comparison */ function normalizePort(url: URL): string { - const defaultPorts: Record = { 'https:': '443', 'http:': '80' }; - const port = url.port || defaultPorts[url.protocol] || ''; + const defaultPorts = new Map([['https:', '443'], ['http:', '80']]); + const port = url.port || defaultPorts.get(url.protocol) || ''; return port ? `${url.hostname}:${port}` : url.hostname; } From b9a0b09eff19130a3568c907a238d95b395522e3 Mon Sep 17 00:00:00 2001 From: Konsti Wohlwend Date: Wed, 13 Aug 2025 18:03:47 -0700 Subject: [PATCH 06/17] Update apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/exact-domain-matching.test.ts --- .../endpoints/api/v1/auth/oauth/exact-domain-matching.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/exact-domain-matching.test.ts b/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/exact-domain-matching.test.ts index dcc9027563..05ff4e7a34 100644 --- a/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/exact-domain-matching.test.ts +++ b/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/exact-domain-matching.test.ts @@ -24,7 +24,6 @@ describe("OAuth with exact domain matching", () => { baseUrl: 'http://localhost:8107', handlerPath: '/handler', }, - 'domains.allowLocalhost': true, }), }, }); From 17b7e2b40007c41f085acc0cf987dfe98cc82ac7 Mon Sep 17 00:00:00 2001 From: Konsti Wohlwend Date: Wed, 13 Aug 2025 18:05:38 -0700 Subject: [PATCH 07/17] Update apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/exact-domain-matching.test.ts --- .../endpoints/api/v1/auth/oauth/exact-domain-matching.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/exact-domain-matching.test.ts b/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/exact-domain-matching.test.ts index 05ff4e7a34..0acc1c99e1 100644 --- a/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/exact-domain-matching.test.ts +++ b/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/exact-domain-matching.test.ts @@ -24,6 +24,7 @@ describe("OAuth with exact domain matching", () => { baseUrl: 'http://localhost:8107', handlerPath: '/handler', }, + 'domains.allowLocalhost': false, }), }, }); From 3ed23dacb239bc8c083026c04799dfb2c116b0b0 Mon Sep 17 00:00:00 2001 From: Konsti Wohlwend Date: Wed, 13 Aug 2025 18:06:03 -0700 Subject: [PATCH 08/17] Update apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/exact-domain-matching.test.ts --- .../endpoints/api/v1/auth/oauth/exact-domain-matching.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/exact-domain-matching.test.ts b/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/exact-domain-matching.test.ts index 0acc1c99e1..160b8bc511 100644 --- a/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/exact-domain-matching.test.ts +++ b/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/exact-domain-matching.test.ts @@ -230,7 +230,7 @@ describe("OAuth with exact domain matching", () => { baseUrl: 'http://localhost:8107', // This one matches! handlerPath: '/handler', }, - 'domains.allowLocalhost': true, + 'domains.allowLocalhost': false, }), }, }); From a56ae736039b772785aa55b962cc5a1b3c2b1ed7 Mon Sep 17 00:00:00 2001 From: Konsti Wohlwend Date: Wed, 13 Aug 2025 18:07:04 -0700 Subject: [PATCH 09/17] Update apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts --- .../endpoints/api/v1/auth/oauth/wildcard-domains.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts b/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts index a3bc418778..1cb080d0b5 100644 --- a/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts +++ b/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts @@ -89,7 +89,7 @@ describe("OAuth with wildcard domains", () => { baseUrl: 'http://*.localhost:8107', handlerPath: '/handler', }, - 'domains.allowLocalhost': true, + 'domains.allowLocalhost': false, }), }, }); From d22367f34632de8a8c0eaa474252c533ed34fe6e Mon Sep 17 00:00:00 2001 From: Konsti Wohlwend Date: Wed, 13 Aug 2025 18:12:13 -0700 Subject: [PATCH 10/17] Update apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts --- .../endpoints/api/v1/auth/oauth/wildcard-domains.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts b/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts index 1cb080d0b5..dbc89899dd 100644 --- a/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts +++ b/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts @@ -219,7 +219,7 @@ describe("OAuth with wildcard domains", () => { baseUrl: 'http://local*:8107', // Should match localhost handlerPath: '/handler', }, - 'domains.allowLocalhost': true, + 'domains.allowLocalhost': false, }), }, }); From 0e4bb559a061c6bb81a236880b37d3cbce8fc41b Mon Sep 17 00:00:00 2001 From: Konsti Wohlwend Date: Wed, 13 Aug 2025 18:12:26 -0700 Subject: [PATCH 11/17] Update apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts --- .../endpoints/api/v1/auth/oauth/wildcard-domains.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts b/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts index dbc89899dd..9a68b5fe0c 100644 --- a/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts +++ b/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts @@ -154,7 +154,7 @@ describe("OAuth with wildcard domains", () => { baseUrl: 'http://**.localhost:8107', handlerPath: '/handler', }, - 'domains.allowLocalhost': true, + 'domains.allowLocalhost': false, }), }, }); From 120dfc163fd271f7c134b90f8e659df69f7d76d0 Mon Sep 17 00:00:00 2001 From: Konsti Wohlwend Date: Wed, 13 Aug 2025 18:12:51 -0700 Subject: [PATCH 12/17] Update apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- .../endpoints/api/v1/auth/oauth/wildcard-domains.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts b/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts index 9a68b5fe0c..e1fe8cf983 100644 --- a/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts +++ b/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts @@ -269,7 +269,8 @@ describe("OAuth with wildcard domains", () => { oauth_providers: [{ id: "spotify", type: "shared" }], } }); - +}); + await InternalApiKey.createAndSetProjectKeys(); // Configure multiple domains, only one matches const configResponse = await niceBackendFetch("/api/v1/internal/config/override", { method: "PATCH", From da6d04e3a256fa29a3ef6210c9b96de15cc01751 Mon Sep 17 00:00:00 2001 From: Konsti Wohlwend Date: Wed, 13 Aug 2025 18:13:07 -0700 Subject: [PATCH 13/17] Update apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts --- .../endpoints/api/v1/auth/oauth/wildcard-domains.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts b/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts index e1fe8cf983..d7e28e8abd 100644 --- a/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts +++ b/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts @@ -292,7 +292,7 @@ describe("OAuth with wildcard domains", () => { baseUrl: 'http://localhost:8107', // This one matches! handlerPath: '/handler', }, - 'domains.allowLocalhost': true, + 'domains.allowLocalhost': false, }), }, }); From 3eb48f15507461bae9378a1d50fb8d72f9951686 Mon Sep 17 00:00:00 2001 From: Konstantin Wohlwend Date: Wed, 13 Aug 2025 18:20:03 -0700 Subject: [PATCH 14/17] fix --- CLAUDE.md | 3 + apps/backend/src/lib/redirect-urls.test.tsx | 62 +++++++++++++-------- apps/backend/src/lib/redirect-urls.tsx | 21 ++----- 3 files changed, 47 insertions(+), 39 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 89e16f0ffc..743c189775 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -75,3 +75,6 @@ To see all development ports, refer to the index.html of `apps/dev-launchpad/pub - Sometimes, the typecheck will give errors along the line of "Cannot assign Buffer to Uint8Array" or similar, on changes that are completely unrelated to your own changes. If that happens, tell the user to run `pnpm clean && pnpm i && pnpm run codegen && pnpm build:packages`, and restart the dev server (you cannot run this yourself). After that's done, the typecheck should pass. - When writing tests, prefer .toMatchInlineSnapshot over other selectors, if possible. You can check (and modify) the snapshot-serializer.ts file to see how the snapshots are formatted and how non-deterministic values are handled. - Whenever you learn something new, or at the latest right before you call the `Stop` tool, write whatever you learned into the ./claude/CLAUDE-KNOWLEDGE.md file, in the Q&A format in there. You will later be able to look up knowledge from there (based on the question you asked). + +### Code-related +- Use ES6 maps instead of records wherever you can. diff --git a/apps/backend/src/lib/redirect-urls.test.tsx b/apps/backend/src/lib/redirect-urls.test.tsx index efa71819eb..4e37b7cc06 100644 --- a/apps/backend/src/lib/redirect-urls.test.tsx +++ b/apps/backend/src/lib/redirect-urls.test.tsx @@ -1,4 +1,4 @@ -import { describe, it, expect } from 'vitest'; +import { describe, expect, it } from 'vitest'; import { validateRedirectUrl } from './redirect-urls'; import { Tenancy } from './tenancies'; @@ -29,8 +29,10 @@ describe('validateRedirectUrl', () => { expect(validateRedirectUrl('https://example.com/handler', tenancy)).toBe(true); expect(validateRedirectUrl('https://example.com/handler/callback', tenancy)).toBe(true); - expect(validateRedirectUrl('https://example.com/other', tenancy)).toBe(false); - expect(validateRedirectUrl('https://other.com/handler', tenancy)).toBe(false); + expect(validateRedirectUrl('https://example.com/other', tenancy)).toBe(true); // Any path on trusted domain is valid + expect(validateRedirectUrl('https://example.com/', tenancy)).toBe(true); // Root path is also valid + expect(validateRedirectUrl('https://other.com/handler', tenancy)).toBe(false); // Different domain is not trusted + expect(validateRedirectUrl('https://example.com.other.com/handler', tenancy)).toBe(false); // Similar different domain is also not trusted }); it('should validate protocol matching', () => { @@ -44,7 +46,8 @@ describe('validateRedirectUrl', () => { }); expect(validateRedirectUrl('https://example.com/handler', tenancy)).toBe(true); - expect(validateRedirectUrl('http://example.com/handler', tenancy)).toBe(false); + expect(validateRedirectUrl('https://example.com/any/path', tenancy)).toBe(true); // Any path is valid + expect(validateRedirectUrl('http://example.com/handler', tenancy)).toBe(false); // Wrong protocol }); }); @@ -60,10 +63,11 @@ describe('validateRedirectUrl', () => { }); expect(validateRedirectUrl('https://api.example.com/handler', tenancy)).toBe(true); - expect(validateRedirectUrl('https://www.example.com/handler', tenancy)).toBe(true); - expect(validateRedirectUrl('https://staging.example.com/handler', tenancy)).toBe(true); - expect(validateRedirectUrl('https://example.com/handler', tenancy)).toBe(false); - expect(validateRedirectUrl('https://api.v2.example.com/handler', tenancy)).toBe(false); + expect(validateRedirectUrl('https://api.example.com/any/path', tenancy)).toBe(true); // Any path is valid + expect(validateRedirectUrl('https://www.example.com/', tenancy)).toBe(true); // Root path is valid + expect(validateRedirectUrl('https://staging.example.com/other', tenancy)).toBe(true); + expect(validateRedirectUrl('https://example.com/handler', tenancy)).toBe(false); // Not a subdomain + expect(validateRedirectUrl('https://api.v2.example.com/handler', tenancy)).toBe(false); // Too many subdomains for single * }); it('should validate double wildcard patterns', () => { @@ -77,9 +81,10 @@ describe('validateRedirectUrl', () => { }); expect(validateRedirectUrl('https://api.example.com/handler', tenancy)).toBe(true); - expect(validateRedirectUrl('https://api.v2.example.com/handler', tenancy)).toBe(true); - expect(validateRedirectUrl('https://a.b.c.example.com/handler', tenancy)).toBe(true); - expect(validateRedirectUrl('https://example.com/handler', tenancy)).toBe(false); + expect(validateRedirectUrl('https://api.example.com/', tenancy)).toBe(true); // Root path is valid + expect(validateRedirectUrl('https://api.v2.example.com/other/path', tenancy)).toBe(true); // Any path is valid + expect(validateRedirectUrl('https://a.b.c.example.com/deep/nested/path', tenancy)).toBe(true); + expect(validateRedirectUrl('https://example.com/handler', tenancy)).toBe(false); // Not a subdomain }); it('should validate wildcard patterns with prefixes', () => { @@ -93,10 +98,10 @@ describe('validateRedirectUrl', () => { }); expect(validateRedirectUrl('https://api-v1.example.com/handler', tenancy)).toBe(true); - expect(validateRedirectUrl('https://api-v2.example.com/handler', tenancy)).toBe(true); - expect(validateRedirectUrl('https://api-prod.example.com/handler', tenancy)).toBe(true); - expect(validateRedirectUrl('https://api.example.com/handler', tenancy)).toBe(false); - expect(validateRedirectUrl('https://v1-api.example.com/handler', tenancy)).toBe(false); + expect(validateRedirectUrl('https://api-v2.example.com/any/path', tenancy)).toBe(true); // Any path is valid + expect(validateRedirectUrl('https://api-prod.example.com/', tenancy)).toBe(true); // Root path is valid + expect(validateRedirectUrl('https://api.example.com/handler', tenancy)).toBe(false); // Missing prefix + expect(validateRedirectUrl('https://v1-api.example.com/handler', tenancy)).toBe(false); // Wrong prefix position }); it('should validate multiple wildcard patterns', () => { @@ -110,9 +115,10 @@ describe('validateRedirectUrl', () => { }); expect(validateRedirectUrl('https://mail.example.org/handler', tenancy)).toBe(true); - expect(validateRedirectUrl('https://api.company.org/handler', tenancy)).toBe(true); - expect(validateRedirectUrl('https://example.org/handler', tenancy)).toBe(false); - expect(validateRedirectUrl('https://a.b.c.org/handler', tenancy)).toBe(false); + expect(validateRedirectUrl('https://mail.example.org/any/path', tenancy)).toBe(true); // Any path is valid + expect(validateRedirectUrl('https://api.company.org/', tenancy)).toBe(true); // Root path is valid + expect(validateRedirectUrl('https://example.org/handler', tenancy)).toBe(false); // Not enough subdomain levels + expect(validateRedirectUrl('https://a.b.c.org/handler', tenancy)).toBe(false); // Too many subdomain levels }); }); @@ -145,7 +151,7 @@ describe('validateRedirectUrl', () => { }); describe('path validation', () => { - it('should validate handler path matching', () => { + it('should allow any path on trusted domains (handlerPath is only a default)', () => { const tenancy = createMockTenancy({ domains: { allowLocalhost: false, @@ -155,13 +161,15 @@ describe('validateRedirectUrl', () => { }, }); + // All paths on the trusted domain should be valid expect(validateRedirectUrl('https://example.com/auth/handler', tenancy)).toBe(true); expect(validateRedirectUrl('https://example.com/auth/handler/callback', tenancy)).toBe(true); - expect(validateRedirectUrl('https://example.com/auth', tenancy)).toBe(false); - expect(validateRedirectUrl('https://example.com/other/handler', tenancy)).toBe(false); + expect(validateRedirectUrl('https://example.com/auth', tenancy)).toBe(true); // Any path is valid + expect(validateRedirectUrl('https://example.com/other/handler', tenancy)).toBe(true); // Any path is valid + expect(validateRedirectUrl('https://example.com/', tenancy)).toBe(true); // Root is valid }); - it('should work with wildcard domains and path validation', () => { + it('should work with wildcard domains (any path is valid)', () => { const tenancy = createMockTenancy({ domains: { allowLocalhost: false, @@ -171,10 +179,12 @@ describe('validateRedirectUrl', () => { }, }); + // All paths on matched domains should be valid expect(validateRedirectUrl('https://api.example.com/api/auth', tenancy)).toBe(true); expect(validateRedirectUrl('https://app.example.com/api/auth/callback', tenancy)).toBe(true); - expect(validateRedirectUrl('https://api.example.com/api', tenancy)).toBe(false); - expect(validateRedirectUrl('https://api.example.com/other/auth', tenancy)).toBe(false); + expect(validateRedirectUrl('https://api.example.com/api', tenancy)).toBe(true); // Any path is valid + expect(validateRedirectUrl('https://api.example.com/other/auth', tenancy)).toBe(true); // Any path is valid + expect(validateRedirectUrl('https://api.example.com/', tenancy)).toBe(true); // Root is valid }); }); @@ -453,9 +463,13 @@ describe('validateRedirectUrl', () => { }, }); + // Any path on trusted domains should be valid expect(validateRedirectUrl('https://example.com/handler', tenancy)).toBe(true); + expect(validateRedirectUrl('https://example.com/any/path', tenancy)).toBe(true); expect(validateRedirectUrl('https://api.staging.com/auth', tenancy)).toBe(true); + expect(validateRedirectUrl('https://api.staging.com/different/path', tenancy)).toBe(true); expect(validateRedirectUrl('https://api.v2.production.com/callback', tenancy)).toBe(true); + expect(validateRedirectUrl('https://api.v2.production.com/', tenancy)).toBe(true); expect(validateRedirectUrl('https://other.com/handler', tenancy)).toBe(false); }); }); diff --git a/apps/backend/src/lib/redirect-urls.tsx b/apps/backend/src/lib/redirect-urls.tsx index 27a6696939..a7d486c074 100644 --- a/apps/backend/src/lib/redirect-urls.tsx +++ b/apps/backend/src/lib/redirect-urls.tsx @@ -30,7 +30,7 @@ function portsMatch(url1: URL, url2: URL): boolean { /** * Validates a URL against a domain pattern (with or without wildcards) */ -function matchesDomain(testUrl: URL, pattern: string, handlerPath: string): boolean { +function matchesDomain(testUrl: URL, pattern: string): boolean { const baseUrl = createUrlIfValid(pattern); // If pattern is invalid as a URL, it might contain wildcards @@ -42,7 +42,7 @@ function matchesDomain(testUrl: URL, pattern: string, handlerPath: string): bool return false; } - const [, protocol, hostPattern, basePath] = match; + const [, protocol, hostPattern] = match; // Check protocol if (testUrl.protocol + '//' !== protocol) { @@ -53,26 +53,17 @@ function matchesDomain(testUrl: URL, pattern: string, handlerPath: string): bool const hasPortInPattern = hostPattern.includes(':'); if (hasPortInPattern) { // Pattern includes port - match against normalized host:port - if (!matchHostnamePattern(hostPattern, normalizePort(testUrl))) { - return false; - } + return matchHostnamePattern(hostPattern, normalizePort(testUrl)); } else { // Pattern doesn't include port - match hostname only, require default port - if (!matchHostnamePattern(hostPattern, testUrl.hostname) || !isDefaultPort(testUrl)) { - return false; - } + return matchHostnamePattern(hostPattern, testUrl.hostname) && isDefaultPort(testUrl); } - - // Check path - const fullPath = basePath === '/' ? handlerPath : basePath + handlerPath; - return testUrl.pathname.startsWith(fullPath || '/'); } // For non-wildcard patterns, use URL comparison return baseUrl.protocol === testUrl.protocol && baseUrl.hostname === testUrl.hostname && - portsMatch(baseUrl, testUrl) && - testUrl.pathname.startsWith(handlerPath || '/'); + portsMatch(baseUrl, testUrl); } export function validateRedirectUrl( @@ -89,6 +80,6 @@ export function validateRedirectUrl( // Check trusted domains return Object.values(tenancy.config.domains.trustedDomains).some(domain => - domain.baseUrl && matchesDomain(url, domain.baseUrl, domain.handlerPath || '/') + domain.baseUrl && matchesDomain(url, domain.baseUrl) ); } From 414536c1c1b13ff696ec37b96298a02276d4720c Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Thu, 14 Aug 2025 01:26:00 +0000 Subject: [PATCH 15/17] Fix test issues: replace JSON utils, remove unused imports, fix config - Replace JSON.parse/JSON.stringify with parseJson/stringifyJson utilities - Remove unused localRedirectUrl imports from OAuth test files - Remove debug console.log statement in passkey test - Fix syntax error in OAuth wildcard test (missing closing brace) - Set proper allowLocalhost configuration values - Add proper imports for JSON utilities Co-authored-by: Konsti Wohlwend --- .../auth/oauth/exact-domain-matching.test.ts | 19 +++++++------- .../v1/auth/oauth/wildcard-domains.test.ts | 26 +++++++++---------- .../v1/auth/passkey/wildcard-domains.test.ts | 22 +++++++--------- 3 files changed, 33 insertions(+), 34 deletions(-) diff --git a/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/exact-domain-matching.test.ts b/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/exact-domain-matching.test.ts index 160b8bc511..810e9a2d5c 100644 --- a/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/exact-domain-matching.test.ts +++ b/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/exact-domain-matching.test.ts @@ -1,6 +1,7 @@ import { describe } from "vitest"; -import { it, localRedirectUrl } from "../../../../../../helpers"; +import { it } from "../../../../../../helpers"; import { Auth, InternalApiKey, Project, niceBackendFetch } from "../../../../../backend-helpers"; +import { stringifyJson } from "@stackframe/stack-shared/dist/utils/json"; describe("OAuth with exact domain matching", () => { it("should allow OAuth with exact matching domain", async ({ expect }) => { @@ -19,7 +20,7 @@ describe("OAuth with exact domain matching", () => { 'x-stack-admin-access-token': adminAccessToken, }, body: { - config_override_string: JSON.stringify({ + config_override_string: stringifyJson({ 'domains.trustedDomains.exact': { baseUrl: 'http://localhost:8107', handlerPath: '/handler', @@ -52,7 +53,7 @@ describe("OAuth with exact domain matching", () => { 'x-stack-admin-access-token': adminAccessToken, }, body: { - config_override_string: JSON.stringify({ + config_override_string: stringifyJson({ 'domains.trustedDomains.production': { baseUrl: 'https://app.production.com', handlerPath: '/auth/handler', @@ -85,7 +86,7 @@ describe("OAuth with exact domain matching", () => { 'x-stack-admin-access-token': adminAccessToken, }, body: { - config_override_string: JSON.stringify({ + config_override_string: stringifyJson({ 'domains.trustedDomains.subdomain': { baseUrl: 'https://app.example.com', handlerPath: '/handler', @@ -118,7 +119,7 @@ describe("OAuth with exact domain matching", () => { 'x-stack-admin-access-token': adminAccessToken, }, body: { - config_override_string: JSON.stringify({ + config_override_string: stringifyJson({ 'domains.trustedDomains.withport': { baseUrl: 'http://localhost:3000', handlerPath: '/handler', @@ -151,7 +152,7 @@ describe("OAuth with exact domain matching", () => { 'x-stack-admin-access-token': adminAccessToken, }, body: { - config_override_string: JSON.stringify({ + config_override_string: stringifyJson({ 'domains.trustedDomains.https': { baseUrl: 'https://localhost:8107', handlerPath: '/handler', @@ -184,7 +185,7 @@ describe("OAuth with exact domain matching", () => { 'x-stack-admin-access-token': adminAccessToken, }, body: { - config_override_string: JSON.stringify({ + config_override_string: stringifyJson({ 'domains.trustedDomains.withpath': { baseUrl: 'http://localhost:8107', handlerPath: '/auth/oauth/callback', // Different path than default /handler @@ -217,7 +218,7 @@ describe("OAuth with exact domain matching", () => { 'x-stack-admin-access-token': adminAccessToken, }, body: { - config_override_string: JSON.stringify({ + config_override_string: stringifyJson({ 'domains.trustedDomains.prod': { baseUrl: 'https://app.production.com', handlerPath: '/handler', @@ -257,7 +258,7 @@ describe("OAuth with exact domain matching", () => { 'x-stack-admin-access-token': adminAccessToken, }, body: { - config_override_string: JSON.stringify({ + config_override_string: stringifyJson({ 'domains.trustedDomains.prod': { baseUrl: 'https://app.production.com', handlerPath: '/handler', diff --git a/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts b/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts index d7e28e8abd..7ef22578cb 100644 --- a/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts +++ b/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts @@ -1,6 +1,7 @@ import { describe } from "vitest"; -import { it, localRedirectUrl } from "../../../../../../helpers"; +import { it } from "../../../../../../helpers"; import { Auth, InternalApiKey, Project, niceBackendFetch } from "../../../../../backend-helpers"; +import { parseJson, stringifyJson } from "@stackframe/stack-shared/dist/utils/json"; describe("OAuth with wildcard domains", () => { it("should work with exact domain configuration", async ({ expect }) => { @@ -19,12 +20,12 @@ describe("OAuth with wildcard domains", () => { 'x-stack-admin-access-token': adminAccessToken, }, body: { - config_override_string: JSON.stringify({ + config_override_string: stringifyJson({ 'domains.trustedDomains.exact': { baseUrl: 'http://localhost:8107', handlerPath: '/handler', }, - 'domains.allowLocalhost': true, + 'domains.allowLocalhost': false, }), }, }); @@ -51,7 +52,7 @@ describe("OAuth with wildcard domains", () => { 'x-stack-admin-access-token': adminAccessToken, }, body: { - config_override_string: JSON.stringify({ + config_override_string: stringifyJson({ 'domains.trustedDomains.exact': { baseUrl: 'https://app.example.com', handlerPath: '/handler', @@ -84,7 +85,7 @@ describe("OAuth with wildcard domains", () => { 'x-stack-admin-access-token': adminAccessToken, }, body: { - config_override_string: JSON.stringify({ + config_override_string: stringifyJson({ 'domains.trustedDomains.wildcard': { baseUrl: 'http://*.localhost:8107', handlerPath: '/handler', @@ -116,7 +117,7 @@ describe("OAuth with wildcard domains", () => { 'x-stack-admin-access-token': adminAccessToken, }, body: { - config_override_string: JSON.stringify({ + config_override_string: stringifyJson({ 'domains.trustedDomains.wildcard': { baseUrl: 'https://*.example.com', handlerPath: '/handler', @@ -149,7 +150,7 @@ describe("OAuth with wildcard domains", () => { 'x-stack-admin-access-token': adminAccessToken, }, body: { - config_override_string: JSON.stringify({ + config_override_string: stringifyJson({ 'domains.trustedDomains.double': { baseUrl: 'http://**.localhost:8107', handlerPath: '/handler', @@ -181,7 +182,7 @@ describe("OAuth with wildcard domains", () => { 'x-stack-admin-access-token': adminAccessToken, }, body: { - config_override_string: JSON.stringify({ + config_override_string: stringifyJson({ 'domains.trustedDomains.double': { baseUrl: 'https://**.example.org', // Different TLD - won't match localhost handlerPath: '/handler', @@ -214,7 +215,7 @@ describe("OAuth with wildcard domains", () => { 'x-stack-admin-access-token': adminAccessToken, }, body: { - config_override_string: JSON.stringify({ + config_override_string: stringifyJson({ 'domains.trustedDomains.prefix': { baseUrl: 'http://local*:8107', // Should match localhost handlerPath: '/handler', @@ -246,7 +247,7 @@ describe("OAuth with wildcard domains", () => { 'x-stack-admin-access-token': adminAccessToken, }, body: { - config_override_string: JSON.stringify({ + config_override_string: stringifyJson({ 'domains.trustedDomains.prefix': { baseUrl: 'http://api-*:8107', // Won't match localhost handlerPath: '/handler', @@ -269,7 +270,6 @@ describe("OAuth with wildcard domains", () => { oauth_providers: [{ id: "spotify", type: "shared" }], } }); -}); await InternalApiKey.createAndSetProjectKeys(); // Configure multiple domains, only one matches const configResponse = await niceBackendFetch("/api/v1/internal/config/override", { @@ -279,7 +279,7 @@ describe("OAuth with wildcard domains", () => { 'x-stack-admin-access-token': adminAccessToken, }, body: { - config_override_string: JSON.stringify({ + config_override_string: stringifyJson({ 'domains.trustedDomains.prod': { baseUrl: 'https://app.production.com', handlerPath: '/handler', @@ -308,7 +308,7 @@ describe("OAuth with wildcard domains", () => { }); expect(getResponse.status).toBe(200); - const config = JSON.parse(getResponse.body.config_string); + const config = parseJson(getResponse.body.config_string); expect(Object.keys(config.domains.trustedDomains).length).toBe(3); }); }); diff --git a/apps/e2e/tests/backend/endpoints/api/v1/auth/passkey/wildcard-domains.test.ts b/apps/e2e/tests/backend/endpoints/api/v1/auth/passkey/wildcard-domains.test.ts index 75932f2c79..e26907ab6b 100644 --- a/apps/e2e/tests/backend/endpoints/api/v1/auth/passkey/wildcard-domains.test.ts +++ b/apps/e2e/tests/backend/endpoints/api/v1/auth/passkey/wildcard-domains.test.ts @@ -1,6 +1,7 @@ import { describe } from "vitest"; import { it } from "../../../../../../helpers"; import { Auth, InternalApiKey, Project, niceBackendFetch } from "../../../../../backend-helpers"; +import { parseJson, stringifyJson } from "@stackframe/stack-shared/dist/utils/json"; describe("Passkey with wildcard domains", () => { it("should store wildcard domains in config correctly", async ({ expect }) => { @@ -18,7 +19,7 @@ describe("Passkey with wildcard domains", () => { 'x-stack-admin-access-token': adminAccessToken, }, body: { - config_override_string: JSON.stringify({ + config_override_string: stringifyJson({ 'domains.trustedDomains.exact': { baseUrl: 'https://app.example.com', handlerPath: '/handler', @@ -54,7 +55,7 @@ describe("Passkey with wildcard domains", () => { }); expect(getResponse.status).toBe(200); - const config = JSON.parse(getResponse.body.config_string); + const config = parseJson(getResponse.body.config_string); expect(config.domains.trustedDomains).toMatchObject({ 'exact': { baseUrl: 'https://app.example.com', @@ -96,7 +97,7 @@ describe("Passkey with wildcard domains", () => { method: "PATCH", accessType: "admin", body: { - config_override_string: JSON.stringify({ + config_override_string: stringifyJson({ 'domains.trustedDomains.wildcard': { baseUrl: 'http://*:8103', // Will match http://localhost:8103 and any host on port 8103 handlerPath: '/', @@ -149,9 +150,6 @@ describe("Passkey with wildcard domains", () => { }, }); - if (registerResponse.status !== 200) { - console.log("Register failed with:", registerResponse.body); - } expect(registerResponse.status).toBe(200); expect(registerResponse.body).toHaveProperty("user_handle"); }); @@ -175,7 +173,7 @@ describe("Passkey with wildcard domains", () => { method: "PATCH", accessType: "admin", body: { - config_override_string: JSON.stringify({ + config_override_string: stringifyJson({ 'domains.trustedDomains.double': { baseUrl: 'http://**host:8103', // Will match localhost:8103 handlerPath: '/', @@ -237,7 +235,7 @@ describe("Passkey with wildcard domains", () => { method: "PATCH", accessType: "admin", body: { - config_override_string: JSON.stringify({ + config_override_string: stringifyJson({ 'domains.trustedDomains.exact': { baseUrl: 'https://app.production.com', handlerPath: '/handler', @@ -315,7 +313,7 @@ describe("Passkey with wildcard domains", () => { method: "PATCH", accessType: "admin", body: { - config_override_string: JSON.stringify({ + config_override_string: stringifyJson({ 'domains.trustedDomains.wildcard': { baseUrl: 'https://*.example.com', handlerPath: '/handler', @@ -387,7 +385,7 @@ describe("Passkey with wildcard domains", () => { method: "PATCH", accessType: "admin", body: { - config_override_string: JSON.stringify({ + config_override_string: stringifyJson({ 'domains.trustedDomains.wildcard': { baseUrl: 'http://*:8103', // Will match localhost:8103 handlerPath: '/', @@ -448,7 +446,7 @@ describe("Passkey with wildcard domains", () => { 'x-stack-admin-access-token': adminAccessToken, }, body: { - config_override_string: JSON.stringify({ + config_override_string: stringifyJson({ 'domains.trustedDomains.complex1': { baseUrl: 'https://api-*.*.example.com', handlerPath: '/handler', @@ -476,7 +474,7 @@ describe("Passkey with wildcard domains", () => { }); expect(getResponse.status).toBe(200); - const config = JSON.parse(getResponse.body.config_string); + const config = parseJson(getResponse.body.config_string); expect(config.domains.trustedDomains.complex1.baseUrl).toBe('https://api-*.*.example.com'); expect(config.domains.trustedDomains.complex2.baseUrl).toBe('https://**.api.example.com'); expect(config.domains.trustedDomains.complex3.baseUrl).toBe('https://*-staging.example.com'); From 3eb11cc0477a4454cbcaa829301a6cbfcd06b811 Mon Sep 17 00:00:00 2001 From: Konstantin Wohlwend Date: Tue, 19 Aug 2025 00:50:35 -0700 Subject: [PATCH 16/17] Serializable JS --- .claude/settings.json | 13 +- .vscode/settings.json | 1 + .../src/route-handlers/smart-response.tsx | 4 +- .../v1/auth/oauth/wildcard-domains.test.ts | 4 +- .../v1/auth/passkey/wildcard-domains.test.ts | 6 +- packages/stack-shared/package.json | 13 + .../src/helpers/js-serializer/README.md | 3 + .../src/helpers/js-serializer/js-template.js | 0 .../helpers/js-serializer/runtime-strings.ts | 15 + .../js-serializer/serialization-helpers.ts | 698 +++++ .../js-serializer/serialization-tests.test.ts | 1713 +++++++++++++ .../helpers/js-serializer/transpiler.test.ts | 1966 ++++++++++++++ .../src/helpers/js-serializer/transpiler.ts | 897 +++++++ packages/stack-shared/tsconfig.json | 1 + pnpm-lock.yaml | 2273 ++++++++++------- 15 files changed, 6607 insertions(+), 1000 deletions(-) create mode 100644 packages/stack-shared/src/helpers/js-serializer/README.md create mode 100644 packages/stack-shared/src/helpers/js-serializer/js-template.js create mode 100644 packages/stack-shared/src/helpers/js-serializer/runtime-strings.ts create mode 100644 packages/stack-shared/src/helpers/js-serializer/serialization-helpers.ts create mode 100644 packages/stack-shared/src/helpers/js-serializer/serialization-tests.test.ts create mode 100644 packages/stack-shared/src/helpers/js-serializer/transpiler.test.ts create mode 100644 packages/stack-shared/src/helpers/js-serializer/transpiler.ts diff --git a/.claude/settings.json b/.claude/settings.json index ee52ce43b2..4923235d05 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -4,6 +4,7 @@ "allow": [ "Bash(pnpm typecheck:*)", "Bash(pnpm test:*)", + "Bash(pnpm build:*)", "Bash(pnpm lint:*)", "Bash(find:*)", "Bash(ls:*)", @@ -21,7 +22,17 @@ "hooks": [ { "type": "command", - "command": "pnpm run lint --fix" + "command": "jq -r '.tool_input.file_path' | { read file_path; if [[ \"$file_path\" =~ \\.(js|jsx|ts|tsx)$ ]]; then pnpm run lint --fix \"$file_path\" || true; fi }" + } + ] + } + ], + "Stop": [ + { + "hooks": [ + { + "type": "command", + "command": "(pnpm run typecheck 1>&2 || exit 2) && (pnpm run lint 1>&2 || exit 2)" } ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 775973a21e..5524e887ce 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -96,6 +96,7 @@ "Uncapitalize", "unindexed", "Unmigrated", + "unserialized", "unsubscribers", "upsert", "Upvotes", diff --git a/apps/backend/src/route-handlers/smart-response.tsx b/apps/backend/src/route-handlers/smart-response.tsx index e64206a008..84cb4f9e9e 100644 --- a/apps/backend/src/route-handlers/smart-response.tsx +++ b/apps/backend/src/route-handlers/smart-response.tsx @@ -14,7 +14,7 @@ export type SmartResponse = { } & ( | { bodyType?: undefined, - body?: ArrayBuffer | Json | undefined, + body?: Uint8Array | ArrayBuffer | Json | undefined, } | { bodyType: "empty", @@ -30,7 +30,7 @@ export type SmartResponse = { } | { bodyType: "binary", - body: ArrayBuffer, + body: Uint8Array | ArrayBuffer, } | { bodyType: "success", diff --git a/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts b/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts index 7ef22578cb..7923f8a1cc 100644 --- a/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts +++ b/apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/wildcard-domains.test.ts @@ -1,7 +1,7 @@ +import { stringifyJson } from "@stackframe/stack-shared/dist/utils/json"; import { describe } from "vitest"; import { it } from "../../../../../../helpers"; import { Auth, InternalApiKey, Project, niceBackendFetch } from "../../../../../backend-helpers"; -import { parseJson, stringifyJson } from "@stackframe/stack-shared/dist/utils/json"; describe("OAuth with wildcard domains", () => { it("should work with exact domain configuration", async ({ expect }) => { @@ -308,7 +308,7 @@ describe("OAuth with wildcard domains", () => { }); expect(getResponse.status).toBe(200); - const config = parseJson(getResponse.body.config_string); + const config = JSON.parse(getResponse.body.config_string); expect(Object.keys(config.domains.trustedDomains).length).toBe(3); }); }); diff --git a/apps/e2e/tests/backend/endpoints/api/v1/auth/passkey/wildcard-domains.test.ts b/apps/e2e/tests/backend/endpoints/api/v1/auth/passkey/wildcard-domains.test.ts index e26907ab6b..e752cc53ce 100644 --- a/apps/e2e/tests/backend/endpoints/api/v1/auth/passkey/wildcard-domains.test.ts +++ b/apps/e2e/tests/backend/endpoints/api/v1/auth/passkey/wildcard-domains.test.ts @@ -1,7 +1,7 @@ +import { stringifyJson } from "@stackframe/stack-shared/dist/utils/json"; import { describe } from "vitest"; import { it } from "../../../../../../helpers"; import { Auth, InternalApiKey, Project, niceBackendFetch } from "../../../../../backend-helpers"; -import { parseJson, stringifyJson } from "@stackframe/stack-shared/dist/utils/json"; describe("Passkey with wildcard domains", () => { it("should store wildcard domains in config correctly", async ({ expect }) => { @@ -55,7 +55,7 @@ describe("Passkey with wildcard domains", () => { }); expect(getResponse.status).toBe(200); - const config = parseJson(getResponse.body.config_string); + const config = JSON.parse(getResponse.body.config_string); expect(config.domains.trustedDomains).toMatchObject({ 'exact': { baseUrl: 'https://app.example.com', @@ -474,7 +474,7 @@ describe("Passkey with wildcard domains", () => { }); expect(getResponse.status).toBe(200); - const config = parseJson(getResponse.body.config_string); + const config = JSON.parse(getResponse.body.config_string); expect(config.domains.trustedDomains.complex1.baseUrl).toBe('https://api-*.*.example.com'); expect(config.domains.trustedDomains.complex2.baseUrl).toBe('https://**.api.example.com'); expect(config.domains.trustedDomains.complex3.baseUrl).toBe('https://*-staging.example.com'); diff --git a/packages/stack-shared/package.json b/packages/stack-shared/package.json index fb05f4c0e7..e28412b4c3 100644 --- a/packages/stack-shared/package.json +++ b/packages/stack-shared/package.json @@ -53,6 +53,15 @@ } }, "dependencies": { + "@babel/core": "^7.28.3", + "@babel/generator": "^7.28.3", + "@babel/plugin-transform-async-to-generator": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.28.3", + "@babel/plugin-transform-runtime": "^7.28.3", + "@babel/preset-env": "^7.28.3", + "@babel/runtime": "^7.28.3", + "@babel/types": "^7.28.2", "@opentelemetry/api": "^1.9.0", "@simplewebauthn/browser": "^11.0.0", "async-mutex": "^0.5.0", @@ -63,12 +72,16 @@ "ip-regex": "^5.0.0", "jose": "^5.2.2", "oauth4webapi": "^2.10.3", + "regenerator-runtime": "^0.14.1", "semver": "^7.6.3", "uuid": "^9.0.1" }, "devDependencies": { "@sentry/nextjs": "^8.40.0", "@simplewebauthn/types": "^11.0.0", + "@types/babel-generator": "^6.25.8", + "@types/babel__core": "^7.20.5", + "@types/babel__generator": "^7.27.0", "@types/bcryptjs": "^3.0.0", "@types/elliptic": "^6.4.18", "@types/semver": "^7.5.8", diff --git a/packages/stack-shared/src/helpers/js-serializer/README.md b/packages/stack-shared/src/helpers/js-serializer/README.md new file mode 100644 index 0000000000..fab7d41ec1 --- /dev/null +++ b/packages/stack-shared/src/helpers/js-serializer/README.md @@ -0,0 +1,3 @@ +This is so dumb. I hope you don't have to touch this code. + +If you do, prepare some painkillers and ask Konsti for help diff --git a/packages/stack-shared/src/helpers/js-serializer/js-template.js b/packages/stack-shared/src/helpers/js-serializer/js-template.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/stack-shared/src/helpers/js-serializer/runtime-strings.ts b/packages/stack-shared/src/helpers/js-serializer/runtime-strings.ts new file mode 100644 index 0000000000..7257804849 --- /dev/null +++ b/packages/stack-shared/src/helpers/js-serializer/runtime-strings.ts @@ -0,0 +1,15 @@ +/** + * Bundled runtime helpers as minified JavaScript strings + */ + +/** + * AsyncToGenerator helper functions (minified) + * Includes asyncGeneratorStep and _asyncToGenerator + */ +export const ASYNC_TO_GENERATOR_RUNTIME = 'function asyncGeneratorStep(n,t,e,r,o,a,c){try{var i=n[a](c),u=i.value}catch(n){return void e(n)}i.done?t(u):Promise.resolve(u).then(r,o)}function _asyncToGenerator(n){return function(){var t=this,e=arguments;return new Promise(function(r,o){var a=n.apply(t,e);function _next(n){asyncGeneratorStep(a,r,o,_next,_throw,"next",n)}function _throw(n){asyncGeneratorStep(a,r,o,_next,_throw,"throw",n)}_next(void 0)})}}'; + +/** + * Complete regenerator runtime (minified) + * This is the full regenerator-runtime library bundled as a single line + */ +export const REGENERATOR_RUNTIME = 'var runtime=(function(exports){"use strict";var Op=Object.prototype;var hasOwn=Op.hasOwnProperty;var defineProperty=Object.defineProperty||function(obj,key,desc){obj[key]=desc.value};var undefined;var $Symbol=typeof Symbol==="function"?Symbol:{};var iteratorSymbol=$Symbol.iterator||"@@iterator";var asyncIteratorSymbol=$Symbol.asyncIterator||"@@asyncIterator";var toStringTagSymbol=$Symbol.toStringTag||"@@toStringTag";function define(obj,key,value){Object.defineProperty(obj,key,{value:value,enumerable:true,configurable:true,writable:true});return obj[key]}try{define({},"")}catch(err){define=function(obj,key,value){return obj[key]=value}}function wrap(innerFn,outerFn,self,tryLocsList){var protoGenerator=outerFn&&outerFn.prototype instanceof Generator?outerFn:Generator;var generator=Object.create(protoGenerator.prototype);var context=new Context(tryLocsList||[]);defineProperty(generator,"_invoke",{value:makeInvokeMethod(innerFn,self,context)});return generator}exports.wrap=wrap;function tryCatch(fn,obj,arg){try{return{type:"normal",arg:fn.call(obj,arg)}}catch(err){return{type:"throw",arg:err}}}var GenStateSuspendedStart="suspendedStart";var GenStateSuspendedYield="suspendedYield";var GenStateExecuting="executing";var GenStateCompleted="completed";var ContinueSentinel={};function Generator(){}function GeneratorFunction(){}function GeneratorFunctionPrototype(){}var IteratorPrototype={};define(IteratorPrototype,iteratorSymbol,function(){return this});var getProto=Object.getPrototypeOf;var NativeIteratorPrototype=getProto&&getProto(getProto(values([])));if(NativeIteratorPrototype&&NativeIteratorPrototype!==Op&&hasOwn.call(NativeIteratorPrototype,iteratorSymbol)){IteratorPrototype=NativeIteratorPrototype}var Gp=GeneratorFunctionPrototype.prototype=Generator.prototype=Object.create(IteratorPrototype);GeneratorFunction.prototype=GeneratorFunctionPrototype;defineProperty(Gp,"constructor",{value:GeneratorFunctionPrototype,configurable:true});defineProperty(GeneratorFunctionPrototype,"constructor",{value:GeneratorFunction,configurable:true});GeneratorFunction.displayName=define(GeneratorFunctionPrototype,toStringTagSymbol,"GeneratorFunction");function defineIteratorMethods(prototype){["next","throw","return"].forEach(function(method){define(prototype,method,function(arg){return this._invoke(method,arg)})})}exports.isGeneratorFunction=function(genFun){var ctor=typeof genFun==="function"&&genFun.constructor;return ctor?ctor===GeneratorFunction||(ctor.displayName||ctor.name)==="GeneratorFunction":false};exports.mark=function(genFun){if(Object.setPrototypeOf){Object.setPrototypeOf(genFun,GeneratorFunctionPrototype)}else{genFun.__proto__=GeneratorFunctionPrototype;define(genFun,toStringTagSymbol,"GeneratorFunction")}genFun.prototype=Object.create(Gp);return genFun};exports.awrap=function(arg){return{__await:arg}};function AsyncIterator(generator,PromiseImpl){function invoke(method,arg,resolve,reject){var record=tryCatch(generator[method],generator,arg);if(record.type==="throw"){reject(record.arg)}else{var result=record.arg;var value=result.value;if(value&&typeof value==="object"&&hasOwn.call(value,"__await")){return PromiseImpl.resolve(value.__await).then(function(value){invoke("next",value,resolve,reject)},function(err){invoke("throw",err,resolve,reject)})}return PromiseImpl.resolve(value).then(function(unwrapped){result.value=unwrapped;resolve(result)},function(error){return invoke("throw",error,resolve,reject)})}}var previousPromise;function enqueue(method,arg){function callInvokeWithMethodAndArg(){return new PromiseImpl(function(resolve,reject){invoke(method,arg,resolve,reject)})}return previousPromise=previousPromise?previousPromise.then(callInvokeWithMethodAndArg,callInvokeWithMethodAndArg):callInvokeWithMethodAndArg()}defineProperty(this,"_invoke",{value:enqueue})}defineIteratorMethods(AsyncIterator.prototype);define(AsyncIterator.prototype,asyncIteratorSymbol,function(){return this});exports.AsyncIterator=AsyncIterator;exports.async=function(innerFn,outerFn,self,tryLocsList,PromiseImpl){if(PromiseImpl===void 0)PromiseImpl=Promise;var iter=new AsyncIterator(wrap(innerFn,outerFn,self,tryLocsList),PromiseImpl);return exports.isGeneratorFunction(outerFn)?iter:iter.next().then(function(result){return result.done?result.value:iter.next()})};function makeInvokeMethod(innerFn,self,context){var state=GenStateSuspendedStart;return function invoke(method,arg){if(state===GenStateExecuting){throw new Error("Generator is already running")}if(state===GenStateCompleted){if(method==="throw"){throw arg}return doneResult()}context.method=method;context.arg=arg;while(true){var delegate=context.delegate;if(delegate){var delegateResult=maybeInvokeDelegate(delegate,context);if(delegateResult){if(delegateResult===ContinueSentinel)continue;return delegateResult}}if(context.method==="next"){context.sent=context._sent=context.arg}else if(context.method==="throw"){if(state===GenStateSuspendedStart){state=GenStateCompleted;throw context.arg}context.dispatchException(context.arg)}else if(context.method==="return"){context.abrupt("return",context.arg)}state=GenStateExecuting;var record=tryCatch(innerFn,self,context);if(record.type==="normal"){state=context.done?GenStateCompleted:GenStateSuspendedYield;if(record.arg===ContinueSentinel){continue}return{value:record.arg,done:context.done}}else if(record.type==="throw"){state=GenStateCompleted;context.method="throw";context.arg=record.arg}}}}function maybeInvokeDelegate(delegate,context){var methodName=context.method;var method=delegate.iterator[methodName];if(method===undefined){context.delegate=null;if(methodName==="throw"&&delegate.iterator["return"]){context.method="return";context.arg=undefined;maybeInvokeDelegate(delegate,context);if(context.method==="throw"){return ContinueSentinel}}if(methodName!=="return"){context.method="throw";context.arg=new TypeError("The iterator does not provide a \'"+ methodName+"\' method")}return ContinueSentinel}var record=tryCatch(method,delegate.iterator,context.arg);if(record.type==="throw"){context.method="throw";context.arg=record.arg;context.delegate=null;return ContinueSentinel}var info=record.arg;if(!info){context.method="throw";context.arg=new TypeError("iterator result is not an object");context.delegate=null;return ContinueSentinel}if(info.done){context[delegate.resultName]=info.value;context.next=delegate.nextLoc;if(context.method!=="return"){context.method="next";context.arg=undefined}}else{return info}context.delegate=null;return ContinueSentinel}defineIteratorMethods(Gp);define(Gp,toStringTagSymbol,"Generator");define(Gp,iteratorSymbol,function(){return this});define(Gp,"toString",function(){return"[object Generator]"});function pushTryEntry(locs){var entry={tryLoc:locs[0]};if(1 in locs){entry.catchLoc=locs[1]}if(2 in locs){entry.finallyLoc=locs[2];entry.afterLoc=locs[3]}this.tryEntries.push(entry)}function resetTryEntry(entry){var record=entry.completion||{};record.type="normal";delete record.arg;entry.completion=record}function Context(tryLocsList){this.tryEntries=[{tryLoc:"root"}];tryLocsList.forEach(pushTryEntry,this);this.reset(true)}exports.keys=function(val){var object=Object(val);var keys=[];for(var key in object){keys.push(key)}keys.reverse();return function next(){while(keys.length){var key=keys.pop();if(key in object){next.value=key;next.done=false;return next}}next.done=true;return next}};function values(iterable){if(iterable!=null){var iteratorMethod=iterable[iteratorSymbol];if(iteratorMethod){return iteratorMethod.call(iterable)}if(typeof iterable.next==="function"){return iterable}if(!isNaN(iterable.length)){var i=-1,next=function next(){while(++i=0;--i){var entry=this.tryEntries[i];var record=entry.completion;if(entry.tryLoc==="root"){return handle("end")}if(entry.tryLoc<=this.prev){var hasCatch=hasOwn.call(entry,"catchLoc");var hasFinally=hasOwn.call(entry,"finallyLoc");if(hasCatch&&hasFinally){if(this.prev=0;--i){var entry=this.tryEntries[i];if(entry.tryLoc<=this.prev&&hasOwn.call(entry,"finallyLoc")&&this.prev=0;--i){var entry=this.tryEntries[i];if(entry.finallyLoc===finallyLoc){this.complete(entry.completion,entry.afterLoc);resetTryEntry(entry);return ContinueSentinel}}},"catch":function(tryLoc){for(var i=this.tryEntries.length-1;i>=0;--i){var entry=this.tryEntries[i];if(entry.tryLoc===tryLoc){var record=entry.completion;if(record.type==="throw"){var thrown=record.arg;resetTryEntry(entry)}return thrown}}throw new Error("illegal catch attempt")},delegateYield:function(iterable,resultName,nextLoc){this.delegate={iterator:values(iterable),resultName:resultName,nextLoc:nextLoc};if(this.method==="next"){this.arg=undefined}return ContinueSentinel}};return exports}(typeof module==="object"?module.exports:{}));try{regeneratorRuntime=runtime}catch(accidentalStrictMode){if(typeof globalThis==="object"){globalThis.regeneratorRuntime=runtime}else{Function("r","regeneratorRuntime = r")(runtime)}}'; diff --git a/packages/stack-shared/src/helpers/js-serializer/serialization-helpers.ts b/packages/stack-shared/src/helpers/js-serializer/serialization-helpers.ts new file mode 100644 index 0000000000..167003ecd1 --- /dev/null +++ b/packages/stack-shared/src/helpers/js-serializer/serialization-helpers.ts @@ -0,0 +1,698 @@ +export function getSerializationHelpers() { + const func = () => { + const generateRandomId = () => crypto.randomUUID(); + + const references = new Map(); + const valuesByReference = new Map(); + const getReference = (value: any): string => { + if (references.has(value)) { + return references.get(value)!; + } + const id = generateRandomId(); + references.set(value, id); + valuesByReference.set(id, value); + return id; + }; + const getOrCreateValuesByReference = (ref: string, createFunc: () => any) => { + if (valuesByReference.has(ref)) { + return valuesByReference.get(ref); + } + const obj = createFunc(); + valuesByReference.set(ref, obj); + references.set(obj, ref); + return obj; + }; + + const hostObjectPaths = new Map(); + + const functionDataByReference = new Map< + string, + | { + functionType: "syntactic", + scope: () => Record, + source: string, + } + | { + functionType: "class", + class: Function, + expression: string, + } + >(); + + // Class prototypes are created by the class definition, so we take extra care to not overwrite them + const classPrototypes = new Map(); + + const scopeMap = new Map>(); + + const throwErr = (message: string): never => { + throw new Error(message); + }; + + const getKeyExpressionOrNull = (key: string | symbol) => { + if (typeof key === "string") { + return JSON.stringify(key); + } + return Symbol.keyFor(key) ?? null; + }; + + const isShallowEqual = (a: any, b: any) => { + if (a === b) return true; + if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) return false; + const keysA = Object.keys(a); + const keysB = Object.keys(b); + if (keysA.length !== keysB.length) return false; + for (const key of keysA) { + if (a[key] !== b[key]) return false; + } + return true; + }; + + const definePropertyIfNotAlreadyDefined = (obj: any, key: string | symbol, descriptor: PropertyDescriptor) => { + if (isShallowEqual(Object.getOwnPropertyDescriptor(obj, key), descriptor)) { + return; + } + console.log("defining property", key, descriptor, Object.getOwnPropertyDescriptor(obj, key)); + Object.defineProperty(obj, key, descriptor); + }; + + type HeapEntry = { + objectType: string, + prototype: any, + isExtensible: boolean, + ownProperties: [any, any][], + data: any, + }; + + type ObjectHeapEntrySerializer = { + objectType: string, + check: (obj: any) => boolean, + serialize: (recurse: (path: null | ((old: string) => string), value: any) => any, obj: T) => D, + initializationExpression: (recurse: (data: any) => string, data: D) => string, + postProcessingFunction?: (recurse: (data: any) => string, data: D) => string, + propertyFilter?: (value: any, key: string | symbol) => boolean, + }; + const objectHeapEntrySerializers: ObjectHeapEntrySerializer[] = [ + { + objectType: "class-prototype", + check: (obj) => classPrototypes.has(obj), + serialize: (recurse, obj) => ({ + class: recurse(null, classPrototypes.get(obj)!.class), + }), + initializationExpression: (recurse, data) => `${recurse(data.class)}.prototype`, + }, + { + objectType: "function", + check: (obj) => typeof obj === "function", + serialize: (recurse, obj) => { + const ref = getReference(obj); + const functionData = functionDataByReference.get(ref); + if (functionData === undefined) { + if (hostObjectPaths.has(obj)) { + // HACK make registerHostObject work + return {} as any; + } + throw new Error(`Function data not found for function: ${obj}. Cannot serialize functions that were not registered; it's possible that you tried to serialize an object that is not known to the serializer.`); + } + return { + ...functionData.functionType === "syntactic" ? { + functionType: "syntactic", + scope: Object.entries(functionData.scope()).map(([key, value]) => [recurse(null, key), recurse(null, value)]), + source: recurse(null, functionData.source), + scopeMapId: generateRandomId(), + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + } : functionData.functionType === "class" ? { + functionType: "class", + class: recurse(null, functionData.class), + expression: functionData.expression, + } : throwErr(`Unknown function type: ${(functionData as any).functionType}`), + }; + }, + initializationExpression: (recurse, data) => { + switch (data.functionType) { + case "syntactic": { + return ` + (() => { + const __$getFromScope = (key) => { + const scm = scopeMap.get(${JSON.stringify(data.scopeMapId)}); + if (scm) return scm.get(key); + else { + // function/class declaration called before postProcessing; let's greedily read the variable + // this may cause a circular dependency, but that's also true in regular JS + return new Map([ + ${data.scope.map(([key, value]) => `[${recurse(key)}, () => (${recurse(value)})]`).join(",\n")} + ]).get(key)(); + } + }; + return new Function("__$getFromScope", "return (" + ${recurse(data.source)} + ");")(__$getFromScope); + })() + `; + } + case "class": { + return ` + (() => { + const C = ${recurse(data.class)}; + return new Function("C", ${JSON.stringify(`return (${data.expression});`)})(C); + })() + `; + } + default: { + throw new Error(`Unknown function type: ${(data as any).functionType}. This serialized object may be invalid.`); + } + } + }, + postProcessingFunction: (recurse, data) => { + switch (data.functionType) { + case "syntactic": { + return ` + (() => { + scopeMap.set(${JSON.stringify(data.scopeMapId)}, new Map([ + ${data.scope.map(([key, value]) => `[${recurse(key)}, ${recurse(value)}]`).join(",\n")} + ])); + }) + `; + } + case "class": { + return "() => {}"; + } + default: { + throw new Error(`Unknown function type: ${(data as any).functionType}. This serialized object may be invalid.`); + } + } + }, + // arguments, caller, and callee will be set automatically, so we don't want to serialize & set them again. they also have awfully awkward behavior so serializing makes no sense + propertyFilter: (obj, key) => !["arguments", "caller", "callee"].includes(key as string), + } satisfies ObjectHeapEntrySerializer< + Function, + | { functionType: "syntactic", scope: [string, any][], source: string, scopeMapId: string } + | { functionType: "class", class: any, expression: string } + >, + { + objectType: "array", + check: (obj) => Array.isArray(obj), + // Since we assign fields later, we don't actually need to serialize anything about the array, just the fact that it is one + serialize: (recurse, obj) => ({}), + initializationExpression: (recurse, data) => "[]", + } satisfies ObjectHeapEntrySerializer, + { + objectType: "map", + check: (obj) => obj instanceof Map, + serialize: (recurse, obj) => { + return { + entries: [...obj.entries()].map(([key, value], i) => { + return [ + recurse(null, key), + recurse(null, value), + ]; + }), + }; + }, + initializationExpression: (recurse, data) => `new Map()`, + postProcessingFunction: (recurse, data) => ` + (map) => { + ${data.entries.map(([key, value], i) => `map.set(${recurse(key)}, ${recurse(value)});`).join("\n")} + } + `, + } satisfies ObjectHeapEntrySerializer, { entries: [string, any][] }>, + { + objectType: "set", + check: (obj) => obj instanceof Set, + serialize: (recurse, obj) => ({ + elements: [...obj.values()].map((element, i) => recurse(p => `[...${p}.values()][${i}]`, element)), + }), + initializationExpression: (recurse, data) => `new Set()`, + postProcessingFunction: (recurse, data) => ` + (set) => { + ${data.elements.map((element, i) => `set.add(${recurse(element)});`).join("\n")} + } + `, + } satisfies ObjectHeapEntrySerializer, { elements: any[] }>, + { + objectType: "primitive-wrapper", + check: (obj) => obj instanceof Number || obj instanceof String || obj instanceof Boolean || obj instanceof BigInt || obj instanceof Symbol, + serialize: (recurse, obj) => ({ + value: recurse(p => `${p}.valueOf()`, obj), + }), + initializationExpression: (recurse, data) => `Object(${recurse(data.value)})`, + } satisfies ObjectHeapEntrySerializer, + { + objectType: "regexp", + check: (obj) => obj instanceof RegExp, + serialize: (recurse, obj) => ({ + source: obj.source, + flags: obj.flags, + }), + initializationExpression: (recurse, data) => `new RegExp(${JSON.stringify(data.source)}, ${JSON.stringify(data.flags)})`, + } satisfies ObjectHeapEntrySerializer, + { + objectType: "error", + check: (obj) => obj instanceof Error, + serialize: (recurse, obj) => ({ + message: obj.message, + name: obj.name, + stack: obj.stack, + }), + initializationExpression: (recurse, data) => ` + (() => { + const res = new Error(${JSON.stringify(data.message)}); + res.name = ${JSON.stringify(data.name)}; + res.stack = ${JSON.stringify(data.stack)}; + return res; + })() + `, + } satisfies ObjectHeapEntrySerializer, + { + objectType: "promise", + check: (obj) => obj instanceof Promise, + serialize: (recurse, obj) => ({ + // TODO: actually implement this instead of just rejecting every promise + }), + initializationExpression: (recurse, data) => ` + (() => { + return Promise.reject(new Error("Putting a VM to sleep rejects all current promises. Please resolve all promises before putting the VM to sleep.")); + })() + `, + } satisfies ObjectHeapEntrySerializer, {}>, + { + objectType: "date", + check: (obj) => obj instanceof Date, + serialize: (recurse, obj) => ({ + ms: obj.getTime(), + }), + initializationExpression: (recurse, data) => `new Date(${data.ms})`, + } satisfies ObjectHeapEntrySerializer, + { + // note: ArrayBuffer has a bunch of 2024/2025 features that are not part of the 2021 tsconfig lib we use, so we need a bunch of `any`s + objectType: "array-buffer", + check: (obj) => obj instanceof ArrayBuffer, + serialize: (recurse, obj) => ({ + detached: (obj as any).detached ?? false, + resizable: (obj as any).resizable ?? false, + byteLength: (obj as any).byteLength ?? null, + maxByteLength: (obj as any).maxByteLength ?? null, + content: recurse(null, [...new Uint8Array(obj)]), + }), + initializationExpression: (recurse, data) => `new ArrayBuffer(${data.byteLength}, ${data.resizable ? `{ maxByteLength: ${JSON.stringify(data.maxByteLength)} }` : "{}"})`, + postProcessingFunction: (recurse, data) => ` + ((arrayBuffer) => { + const content = ${recurse(data.content)}; + const view = new Uint8Array(arrayBuffer); + for (let i = 0; i < content.length; i++) { + view[i] = content[i]; + } + if (${data.detached}) { + arrayBuffer.transfer(); + } + }) + `, + } satisfies ObjectHeapEntrySerializer, + // TODO: missing: Proxy, module namespace objects, iterator objects, WeakMap/WeakSet/WeakRef/FinalizationRegistry, private class methods + // TODO: `arguments` is technically supported, but it doesn't auto-sync, and some other things may not work with it + // TODO: private fields are kinda bugged right now, particularly when accessing them from a class getter I believe. we need better testing here + // TODO: I also think that right now we don't update class methods correctly, if you create a class with some methods, then later set the methods to another function, but serialize the original method somewhere, then instead we will serialize the new method + ]; + + function createHeapEntryForObjectLike(recurse: (path: null | ((old: string) => string), value: any) => any, obj: any): HeapEntry { + if (!["object", "function"].includes(typeof obj)) { + throw new Error(`Expected object or function in createHeapEntryForObjectLike, got ${typeof obj}`); + } + if (obj === null) { + throw new Error("Expected object or function in createHeapEntryForObjectLike, got null"); + } + const matchingObjectTypes = objectHeapEntrySerializers.filter((serializer) => serializer.check(obj)); + let ownPropertyDescriptors = Object.entries(Object.getOwnPropertyDescriptors(obj)) + .filter(([key]) => matchingObjectTypes[0]?.propertyFilter?.(obj, key) ?? true); + return { + objectType: matchingObjectTypes[0]?.objectType ?? "not-special", + isExtensible: Object.isExtensible(obj), + ownProperties: ownPropertyDescriptors.map(([key, descriptor]) => { + const keySerialized = recurse(null, key); + const keyExpression = typeof key === "string" ? JSON.stringify(key) : (Symbol.keyFor(key) ?? null); + return [keySerialized, { + ...("writable" in descriptor ? { writable: descriptor.writable } : {}), + ...("enumerable" in descriptor ? { enumerable: descriptor.enumerable } : {}), + ...("configurable" in descriptor ? { configurable: descriptor.configurable } : {}), + ...("value" in descriptor ? { value: recurse(keyExpression === null ? null : (p => `${p}[${keyExpression}]`), descriptor.value) } : {}), + ...("get" in descriptor ? { get: recurse(keyExpression === null ? null : (p => `Object.getOwnPropertyDescriptor(${p}, ${keyExpression}).get`), descriptor.get) } : {}), + ...("set" in descriptor ? { set: recurse(keyExpression === null ? null : (p => `Object.getOwnPropertyDescriptor(${p}, ${keyExpression}).set`), descriptor.set) } : {}), + } as const]; + }), + prototype: recurse(p => `Object.getPrototypeOf(${p})`, Object.getPrototypeOf(obj)), + data: matchingObjectTypes[0]?.serialize(recurse, obj) ?? null, + }; + } + + function createExpressionFromHeapEntry(recurse: (value: any) => any, heapEntry: HeapEntry): { initializationFunc: () => string, postProcessingFunc: () => string } { + let initialExpressionFunc: () => string; + let postProcessingFunctionFunc: () => string | undefined; + if (heapEntry.objectType === "not-special") { + initialExpressionFunc = () => "{}"; + postProcessingFunctionFunc = () => undefined; + } else { + const heapEntrySerializers = objectHeapEntrySerializers.filter((serializer) => serializer.objectType === heapEntry.objectType); + if (heapEntrySerializers.length === 0) { + throw new Error(`Unknown object type: ${heapEntry.objectType}. The serialized object may be invalid.`); + } + if (heapEntrySerializers.length > 1) { + throw new Error(`Multiple serializers found for object type: ${heapEntry.objectType}. Found: ${heapEntrySerializers.map((serializer) => serializer.objectType).join(", ")}`); + } + initialExpressionFunc = () => heapEntrySerializers[0].initializationExpression(recurse, heapEntry.data); + postProcessingFunctionFunc = () => heapEntrySerializers[0].postProcessingFunction?.(recurse, heapEntry.data); + } + return { + initializationFunc: initialExpressionFunc, + postProcessingFunc: () => ` + (obj) => { + (${postProcessingFunctionFunc() ?? "() => {}"})(obj); + + Object.setPrototypeOf(obj, ${recurse(heapEntry.prototype)}); + + ${heapEntry.ownProperties.map(([key, descriptor]) => ` + definePropertyIfNotAlreadyDefined(obj, ${recurse(key)}, { + ${"value" in descriptor ? `value: ${recurse(descriptor.value)},` : ""} + ${"get" in descriptor ? `get: ${recurse(descriptor.get)},` : ""} + ${"set" in descriptor ? `set: ${recurse(descriptor.set)},` : ""} + ${"writable" in descriptor ? `writable: ${descriptor.writable},` : ""} + ${"enumerable" in descriptor ? `enumerable: ${descriptor.enumerable},` : ""} + ${"configurable" in descriptor ? `configurable: ${descriptor.configurable},` : ""} + }); + `).join("\n\n")} + + ${heapEntry.isExtensible ? "" : "Object.preventExtensions(obj);"} + } + `, + }; + } + + function serialize(obj: any, options?: { heap?: Record, hostObjectPath?: string, stopIfUnreachableFromHost?: boolean }) { + const heap: Record = options?.heap ?? {}; + + const getSerialized = (obj: any, hostObjectPath?: string) => { + if (hostObjectPaths.has(obj)) { + return { + type: "host-object", + path: hostObjectPaths.get(obj), + }; + } + + if (typeof obj === "function" && options?.stopIfUnreachableFromHost && !obj.toString().includes("[native code]")) { + // this is probably a user-defined function that is polluting the global scope (most likely a function declaration at the top level of a script) + // let's skip it instead of adding it as a host object + return { + type: "manual-stop", + }; + } + + if (hostObjectPath && ["symbol", "function", "object"].includes(typeof obj) && obj !== null) { + if (!hostObjectPaths.has(obj)) { + hostObjectPaths.set(obj, hostObjectPath); + } + } + + switch (typeof obj) { + case "string": + case "number": + case "boolean": { + // Special-case NaN, Infinity, -Infinity, -0 + if (Number.isNaN(obj)) { + return { + type: "nan", + }; + } else if (obj === Infinity) { + return { + type: "infinity", + }; + } else if (obj === -Infinity) { + return { + type: "negative-infinity", + }; + } else if (obj === 0 && 1 / obj === -Infinity) { + return { + type: "negative-zero", + }; + } + + return { + type: "simple", + value: obj, + }; + } + case "bigint": { + return { + type: "bigint", + valueString: obj.toString(), + }; + } + case "symbol": { + const key = Symbol.keyFor(obj); + if (key === undefined) { + return { + type: "unregistered-symbol", + reference: getReference(obj), + description: obj.description, + }; + } else { + return { + type: "registered-symbol", + key, + }; + } + } + case "undefined": { + return { + type: "undefined", + }; + } + case "object": + case "function": { + if (obj === null) { + return { + type: "simple", + value: null, + }; + } + const ref = getReference(obj); + if (ref in heap) { + // perfect! we already have a reference to this object in the heap + } else { + // we need to add a new reference to the heap + const recurse = (pathDelta: null | ((old: string) => string), value: any) => { + const newHostObjectPath = pathDelta === null || !hostObjectPath ? undefined : pathDelta(hostObjectPath); + if (options?.stopIfUnreachableFromHost && newHostObjectPath === undefined) { + return { + type: "manual-stop", + }; + } + return getSerialized(value, newHostObjectPath); + }; + heap[ref] = "[circular reference]"; + heap[ref] = createHeapEntryForObjectLike(recurse, obj); + } + return { + type: "object-like", + reference: ref, + }; + } + default: { + throw new Error(`Unknown type: ${typeof obj}`); + } + } + }; + + // assert there are no more [circular reference]s in the heap + for (const key in heap) { + if (typeof heap[key] === "string") { + throw new Error(`Circular reference found in heap: ${key}`); + } + } + + const res = { heap: heap as Record, serialized: getSerialized(obj, options?.hostObjectPath) }; + JSON.stringify(res); // assert the value is JSON-serializable + return res; + } + + function deserialize({ heap, serialized }: { heap: Record, serialized: any }) { + let varCount = 0; + const preProcessingStatements: string[] = []; + const postProcessingStatements: string[] = []; + + const varNamesByReference: Map = new Map(); + + const getExpression = (serialized: any): string => { + switch (serialized?.type) { + case "simple": { + return JSON.stringify(serialized.value); + } + case "nan": { + return "NaN"; + } + case "infinity": { + return "Infinity"; + } + case "negative-infinity": { + return "-Infinity"; + } + case "negative-zero": { + return "-0"; + } + case "bigint": { + return serialized.valueString + "n"; + } + case "unregistered-symbol": { + return `getOrCreateValuesByReference(${JSON.stringify(serialized.reference)}, () => Symbol(${JSON.stringify(serialized.description)}))`; + } + case "registered-symbol": { + return `Symbol.for(${JSON.stringify(serialized.key)})`; + } + case "undefined": { + return "undefined"; + } + case "host-object": { + return serialized.path; + } + case "object-like": { + if (!(serialized.reference in heap)) { + throw new Error(`Heap entry not found for object-like: ${serialized.reference}. The serialized object may be invalid.`); + } + const heapEntry = heap[serialized.reference]; + + if (varNamesByReference.has(serialized.reference)) { + return varNamesByReference.get(serialized.reference)!; + } + + const recurse = (data: any) => { + return getExpression(data); + }; + const varName = `var${varCount++}`; + varNamesByReference.set(serialized.reference, varName); + const { initializationFunc, postProcessingFunc } = createExpressionFromHeapEntry(recurse, heapEntry); + const initialization = initializationFunc(); + preProcessingStatements.push(`const ${varName} /* ${serialized.reference} */ = ${initialization.trim()};`); + const postProcessing = postProcessingFunc(); + if (postProcessing) postProcessingStatements.push(`(${postProcessing.trim()})(${varName});`); + return varName; + } + case undefined: { + throw new Error(`Serialized object does not have an objectType. This serialized object may be invalid. Object: ${JSON.stringify(serialized)}`); + } + default: { + throw new Error(`Unknown serialized type: ${serialized.type}. This serialized object may be invalid.`); + } + } + }; + + const expr = getExpression(serialized); + const code = ` + ${preProcessingStatements.join("\n")} + + const res = ${expr}; + + ${postProcessingStatements.join("\n")} + + return res; + `; + console.log("CODE", code); + + return (new Function( + "getOrCreateValuesByReference", + "throwErr", + "scopeMap", + "definePropertyIfNotAlreadyDefined", + code, + ))( + getOrCreateValuesByReference, + throwErr, + scopeMap, + definePropertyIfNotAlreadyDefined, + ); + } + + function registerFunction(func: Function, { scope, source }: { scope: () => Record, source: string }) { + if (!source) { + throw new Error("Source is required in registerFunction (function: " + func.toString() + ")"); + } + if (typeof scope !== "function") { + throw new Error("Scope must be a function in registerFunction (function: " + func.toString() + ")"); + } + + const ref = getReference(func); + functionDataByReference.set(ref, { + functionType: "syntactic", + scope, + source, + }); + return func; + } + + /** + * Class method syntax is different from normal function syntax and we cannot simply wrap it in a `registerFunction` + * call without affecting the behavior, so we need to handle static and instance methods specially. + */ + function registerClass(classConstructor: Function, { scope, source }: { scope: () => Record, source: string }) { + // The class itself is a function + registerFunction(classConstructor, { + scope, + source, + }); + + // Register static methods + for (const key of Object.getOwnPropertyNames(classConstructor)) { + const method = classConstructor[key as keyof typeof classConstructor]; + if (typeof method === "function" && method !== classConstructor) { + const ref = getReference(method); + functionDataByReference.set(ref, { + functionType: "class", + class: classConstructor, + expression: `C[${JSON.stringify(key)}]`, + }); + } + } + + // Register prototype + classPrototypes.set(classConstructor.prototype, { + class: classConstructor, + }); + + // Register instance methods + for (const key of Object.getOwnPropertyNames(classConstructor.prototype)) { + if (key === "constructor") continue; // skip the constructor, it's already registered + if (typeof classConstructor.prototype[key] === "function" && classConstructor.prototype[key] !== classConstructor) { + const ref = getReference(classConstructor.prototype[key]); + functionDataByReference.set(ref, { + functionType: "class", + class: classConstructor, + expression: `C.prototype[${JSON.stringify(key)}]`, // TODO: this is wrong, we need to use the correct path + }); + } + } + + return classConstructor; + } + + function registerHostObject(obj: any, path: string) { + serialize(obj, { hostObjectPath: path, stopIfUnreachableFromHost: true }); + } + + function ensureSerializable(obj: T): T { + const serialized = serialize(obj); + const deserialized = deserialize(serialized); + if (obj !== deserialized && !(Number.isNaN(obj) && Number.isNaN(deserialized))) { + throw new Error("Error while deserializing the object: Output does not match the input"); + } + return deserialized; + } + + (globalThis as any).__STACK_SerializableJs = { + serialize, + deserialize, + registerFunction, + registerClass, + ensureSerializable, + }; + + registerHostObject(globalThis, "globalThis"); + }; + + return `(${func.toString()})()`; +} diff --git a/packages/stack-shared/src/helpers/js-serializer/serialization-tests.test.ts b/packages/stack-shared/src/helpers/js-serializer/serialization-tests.test.ts new file mode 100644 index 0000000000..5a68f2cc27 --- /dev/null +++ b/packages/stack-shared/src/helpers/js-serializer/serialization-tests.test.ts @@ -0,0 +1,1713 @@ +import crypto from 'crypto'; +import { describe, expect, it } from 'vitest'; +import * as vm from 'vm'; +import { wait } from '../../utils/promises'; +import { nicify } from '../../utils/strings'; +import { transpileJsToSerializableJs } from './transpiler'; + +function runTranspiledVmScript(code: string) { + const transpiled = transpileJsToSerializableJs(code); + const context = vm.createContext({ + crypto: crypto.webcrypto, + console, + }); + const script = new vm.Script(transpiled).runInContext(context); + return script; +} + +async function testCode(objCode: string) { + await wait(10); // spawning too many vms synchronously freezes vitest; let's give it a bit of breathing room + console.log("RUNNING"); + const serializeCode = ` + const obj = (${objCode.trim()}); + const serialized = __STACK_SerializableJs.serialize(obj); + serialized; + `; + + const serializedResult = runTranspiledVmScript(serializeCode); + + console.log("SERIALIZED RESULT", { objCode }, nicify(serializedResult, { maxDepth: 10 })); + + const deserializeCode = ` + const deserialized = __STACK_SerializableJs.deserialize(${JSON.stringify(serializedResult)}); + deserialized; + `; + + const deserializedResult = runTranspiledVmScript(deserializeCode); + + return deserializedResult; +} + +describe('Serialization and Deserialization with VM', async () => { + // Primitive types + it('should serialize strings', async () => { + expect(await testCode(`"abc"`)).toBe("abc"); + expect(await testCode(`"123"`)).toBe("123"); + expect(await testCode(`"12" + 3`)).toBe("123"); + }); + + it('should serialize numbers', async () => { + expect(await testCode(`42`)).toBe(42); + expect(await testCode(`3.14`)).toBe(3.14); + expect(await testCode(`-0`)).toBe(-0); + expect(1 / await testCode(`-0`)).toBe(-Infinity); + expect(await testCode(`0`)).toBe(0); + }); + + it('should serialize special number values', async () => { + expect(await testCode(`Infinity`)).toBe(Infinity); + expect(await testCode(`-Infinity`)).toBe(-Infinity); + expect(Number.isNaN(await testCode(`NaN`))).toBe(true); + }); + + it('should serialize booleans', async () => { + expect(await testCode(`true`)).toBe(true); + expect(await testCode(`false`)).toBe(false); + }); + + it('should serialize null and undefined', async () => { + expect(await testCode(`null`)).toBe(null); + expect(await testCode(`undefined`)).toBe(undefined); + }); + + it('should serialize bigints', async () => { + expect(await testCode(`123n`)).toBe(123n); + expect(await testCode(`-456789012345678901234567890n`)).toBe(-456789012345678901234567890n); + }); + + it('should serialize symbols', async () => { + const result = await testCode(`Symbol("test")`); + expect(typeof result).toBe('symbol'); + expect(result.description).toBe('test'); + }); + + it('should serialize registered symbols', async () => { + const result = await testCode(`Symbol.for("global")`); + expect(typeof result).toBe('symbol'); + expect(Symbol.keyFor(result)).toBe('global'); + }); + + // Objects + it('should serialize basic objects', async () => { + expect(await testCode(` + { exampleKey: "exampleValue" } + `)).toMatchInlineSnapshot(` + { + "exampleKey": "exampleValue", + } + `); + }); + + it('should serialize arrays', async () => { + expect(await testCode(`[1, 2, 3]`)).toEqual([1, 2, 3]); + expect(await testCode(`[1, "two", true, null, undefined]`)).toEqual([1, "two", true, null, undefined]); + }); + + it('should serialize nested objects', async () => { + expect(await testCode(`{ + a: 1, + b: { + c: 2, + d: { + e: 3, + f: [4, 5, 6] + } + } + }`)).toEqual({ + a: 1, + b: { + c: 2, + d: { + e: 3, + f: [4, 5, 6] + } + } + }); + }); + + it('should serialize objects with circular references', async () => { + const result = await testCode(` + (() => { + const obj = { a: 1 }; + obj.self = obj; + return obj; + })() + `); + expect(result.a).toBe(1); + expect(result.self).toBe(result); + }); + + it('should serialize sparse arrays', async () => { + const result = await testCode(`(() => { + const arr = [1, , , 4]; + arr.length = 10; + return arr; + })()`); + expect(result.length).toBe(10); + expect(result[0]).toBe(1); + expect(result[3]).toBe(4); + expect(1 in result).toBe(false); + }); + + it('should serialize arrays with custom properties', async () => { + const result = await testCode(`(() => { + const arr = [1, 2, 3]; + arr.customProp = "test"; + return arr; + })()`); + expect(result).toEqual(Object.assign([1, 2, 3], { customProp: "test" })); + expect(result.customProp).toBe("test"); + }); + + // Built-in objects + it('should serialize Dates', async () => { + const result = await testCode(`new Date("2024-01-01T00:00:00.000Z")`); + expect(result.constructor.name).toBe("Date"); + expect(result.toISOString()).toBe("2024-01-01T00:00:00.000Z"); + }); + + it('should serialize RegExp', async () => { + const result = await testCode(`/test\\d+/gi`); + expect(result.constructor.name).toBe("RegExp"); + expect(result.source).toBe("test\\d+"); + expect(result.flags).toBe("gi"); + }); + + it('should serialize Maps', async () => { + const result = await testCode(`new Map([ + ["key1", "value1"], + ["key2", 42], + [3, "three"] + ])`); + expect(result.constructor.name).toBe("Map"); + expect(result.size).toBe(3); + expect(result.get("key1")).toBe("value1"); + expect(result.get("key2")).toBe(42); + expect(result.get(3)).toBe("three"); + }); + + it('should serialize Sets', async () => { + const result = await testCode(`new Set([1, "two", 3, "four", 1])`); + expect(result.constructor.name).toBe("Set"); + expect(result.size).toBe(4); + expect(result.has(1)).toBe(true); + expect(result.has("two")).toBe(true); + expect(result.has(3)).toBe(true); + expect(result.has("four")).toBe(true); + }); + + it('should serialize Errors', async () => { + const result = await testCode(`(() => { + const err = new Error("Test error"); + err.name = "CustomError"; + return err; + })()`); + expect(result.constructor.name).toBe("Error"); + expect(result.message).toBe("Test error"); + expect(result.name).toBe("CustomError"); + }); + + it('should serialize typed arrays', async () => { + const result = await testCode(`new Uint8Array([1, 2, 3, 4, 5])`); + expect(result.constructor.name).toBe("Uint8Array"); + expect(result).toMatchInlineSnapshot(` + Uint8Array { + "0": 1, + "1": 2, + "2": 3, + "3": 4, + "4": 5, + } + `); + }); + + it('should serialize ArrayBuffers', async () => { + const result = await testCode(`(() => { + const buffer = new ArrayBuffer(8); + const view = new Uint8Array(buffer); + view[0] = 255; + view[7] = 128; + return buffer; + })()`); + expect(result.constructor.name).toBe("ArrayBuffer"); + expect(result.byteLength).toBe(8); + const view = new Uint8Array(result); + expect(view[0]).toBe(255); + expect(view[7]).toBe(128); + }); + + // Functions + it('should serialize simple functions', async () => { + const result = await testCode(`function add(a, b) { return a + b; }`); + expect(typeof result).toBe('function'); + expect(result(2, 3)).toBe(5); + }); + + it('should serialize recursive functions', async () => { + const result = await testCode(` + (() => { + function a(x) { + if (x > 10) return x; + return a(x + 3); + } + return a; + })() + `); + expect(typeof result).toBe('function'); + expect(result(0)).toBe(12); + }); + + + it('should serialize functions with circular scope dependencies', async () => { + const result = await testCode(` + (() => { + function a(x) { + if (x % 5 === 0) return x; + return b(x); + } + function b(x) { + return a(x + 1); + } + return [a, b]; + })() + `); + expect(typeof result[0]).toBe('function'); + expect(typeof result[1]).toBe('function'); + expect(result[0](0)).toBe(0); + expect(result[0](1)).toBe(5); + expect(result[1](0)).toBe(5); + expect(result[1](1)).toBe(5); + }); + + it('should serialize recursive functions that use a different name for the recursive call', async () => { + const result = await testCode(` + (() => { + function a(x) { + if (x > 10) return x; + return b(x + 3); + } + const b = a; + return a; + })() + `); + expect(typeof result).toBe('function'); + expect(result(0)).toBe(12); + }); + + it('should serialize arrow functions', async () => { + const result = await testCode(`(x, y) => x * y`); + expect(typeof result).toBe('function'); + expect(result(3, 4)).toBe(12); + }); + + it('should serialize async functions', async () => { + const result = await testCode(`async function fetchData() { + return "data"; + }`); + expect(typeof result).toBe('function'); + return await expect(result()).resolves.toBe("data"); + }); + + it('should serialize generator functions', async () => { + const result = await testCode(`function* gen() { + yield 1; + yield 2; + yield 3; + }`); + expect(typeof result).toBe('function'); + const generator = result(); + expect(generator.next().value).toBe(1); + expect(generator.next().value).toBe(2); + expect(generator.next().value).toBe(3); + expect(generator.next().done).toBe(true); + }); + + it('should serialize functions with closures', async () => { + const result = await testCode(`(() => { + const multiplier = 10; + return function(x) { + return x * multiplier; + }; + })()`); + expect(typeof result).toBe('function'); + expect(result(5)).toBe(50); + }); + + it('should serialize nested closures', async () => { + const result = await testCode(`(() => { + const a = 1; + return function(b) { + return function(c) { + return a + b + c; + }; + }; + })()`); + expect(typeof result).toBe('function'); + const inner = result(2); + expect(typeof inner).toBe('function'); + expect(inner(3)).toBe(6); + }); + + // Classes + it('should serialize basic classes', async () => { + const result = await testCode(`class MyClass { + constructor(value) { + this.value = value; + } + getValue() { + return this.value; + } + static staticMethod() { + return "static"; + } + }`); + expect(typeof result).toBe('function'); + expect(result.name).toBe("MyClass"); + const instance = new result(42); + expect(instance.value).toBe(42); + expect(instance.getValue()).toBe(42); + expect(result.staticMethod()).toBe("static"); + expect(() => result()).toThrow(); + }); + + it('should serialize classes with circular scope dependencies', async () => { + const result = await testCode(` + (() => { + class A { + constructor(x) { + this.x = x; + this.b = x > 10 ? null : new B(x + 1); + } + } + class B { + constructor(x) { + this.x = x; + this.a = new A(2 * x); + } + } + return [A, B]; + })() + `); + expect(typeof result[0]).toBe('function'); + expect(typeof result[1]).toBe('function'); + expect(new result[0](1).x).toBe(1); + expect(new result[0](1).b.x).toBe(2); + expect(new result[0](1).b.a.x).toBe(4); + expect(new result[0](1).b.a.b.x).toBe(5); + expect(new result[0](1).b.a.b.a.x).toBe(10); + expect(new result[0](1).b.a.b.a.b.x).toBe(11); + expect(new result[0](1).b.a.b.a.b.a.x).toBe(22); + expect(new result[0](1).b.a.b.a.b.a.b).toBe(null); + expect(new result[1](2).x).toBe(2); + expect(new result[1](2).a.x).toBe(4); + expect(new result[1](2).a.b.x).toBe(5); + expect(new result[1](2).a.b.a.x).toBe(10); + expect(new result[1](2).a.b.a.b.x).toBe(11); + expect(new result[1](2).a.b.a.b.a.x).toBe(22); + expect(new result[1](2).a.b.a.b.a.b).toBe(null); + }); + + it('should serialize classes that have a field equal to the class itself', async () => { + const result = await testCode(` + class A { + static self = A; + constructor() { + this.x = 42; + } + } + `); + expect(typeof result).toBe('function'); + expect(result.self).toBe(result); + expect(new result().x).toBe(42); + }); + + it('should serialize class inheritance', async () => { + const result = await testCode(`(() => { + class Animal { + constructor(name) { + this.name = name; + } + speak() { + return this.name + " makes a sound"; + } + } + + class Dog extends Animal { + constructor(name, breed) { + super(name); + this.breed = breed; + } + speak() { + return this.name + " barks"; + } + } + + return Dog; + })()`); + expect(typeof result).toBe('function'); + const dog = new result("Max", "Golden Retriever"); + expect(dog.name).toBe("Max"); + expect(dog.breed).toBe("Golden Retriever"); + expect(dog.speak()).toBe("Max barks"); + }); + + it('should serialize class inheritance when the superclass references the subclass', async () => { + const result = await testCode(`(() => { + class Animal { + constructor(name) { + this.name = name; + this.dogConstructor = Dog; + } + speak() { + return this.name + " makes a sound"; + } + } + + class Dog extends Animal { + constructor(name, breed) { + super(name); + this.breed = breed; + } + speak() { + return this.name + " barks"; + } + } + + return Dog; + })()`); + expect(typeof result).toBe('function'); + const dog = new result("Max", "Golden Retriever"); + console.log(dog); + expect(dog.name).toBe("Max"); + expect(dog.breed).toBe("Golden Retriever"); + expect(dog.speak()).toBe("Max barks"); + }); + + it('should serialize class inheritance when the superclass references the subclass and the superclass is the one being serialized', async () => { + const result = await testCode(`(() => { + class Animal { + constructor(name) { + this.name = name; + this.dogConstructor = Dog; + } + speak() { + return this.name + " makes a sound"; + } + } + + class Dog extends Animal { + constructor(name, breed) { + super(name); + this.breed = breed; + } + speak() { + return this.name + " barks"; + } + } + + return Animal; + })()`); + expect(typeof result).toBe('function'); + const animal = new result("Sam the Squirrel"); + expect(animal.name).toBe("Sam the Squirrel"); + expect(animal.speak()).toBe("Sam the Squirrel makes a sound"); + expect(typeof animal.dogConstructor).toBe('function'); + expect(animal.dogConstructor.name).toBe("Dog"); + expect(new animal.dogConstructor("Max", "Golden Retriever").speak()).toBe("Max barks"); + }); + + it('should serialize class instances', async () => { + const result = await testCode(`(() => { + class Person { + constructor(name, age) { + this.name = name; + this.age = age; + } + greet() { + return "Hello, I'm " + this.name; + } + } + return new Person("Alice", 30); + })()`); + expect(result.name).toBe("Alice"); + expect(result.age).toBe(30); + expect(result.greet()).toBe("Hello, I'm Alice"); + }); + + // Object descriptors + it('should serialize getters and setters', async () => { + const result = await testCode(`{ + _value: 10, + get value() { + return this._value; + }, + set value(v) { + this._value = v; + } + }`); + expect(result.value).toBe(10); + result.value = 20; + expect(result.value).toBe(20); + expect(result._value).toBe(20); + }); + + it('should serialize non-enumerable properties', async () => { + const result = await testCode(`(() => { + const obj = {}; + Object.defineProperty(obj, 'hidden', { + value: 'secret', + enumerable: false, + writable: true, + configurable: true + }); + Object.defineProperty(obj, 'visible', { + value: 'public', + enumerable: true + }); + return obj; + })()`); + expect(result.hidden).toBe('secret'); + expect(result.visible).toBe('public'); + expect(Object.keys(result)).toEqual(['visible']); + }); + + it('should serialize non-writable properties', async () => { + const result = await testCode(`(() => { + const obj = {}; + Object.defineProperty(obj, 'constant', { + value: 42, + writable: false + }); + return obj; + })()`); + expect(result.constant).toBe(42); + expect(() => { + result.constant = 100; + }).toThrow(); + }); + + it('should serialize non-configurable properties', async () => { + const result = await testCode(`(() => { + const obj = {}; + Object.defineProperty(obj, 'fixed', { + value: 'immutable', + configurable: false + }); + return obj; + })()`); + expect(result.fixed).toBe('immutable'); + expect(() => { + delete result.fixed; + }).toThrow(); + }); + + it('should serialize frozen objects', async () => { + const result = await testCode(`Object.freeze({ a: 1, b: 2 })`); + expect(result).toEqual({ a: 1, b: 2 }); + expect(Object.isFrozen(result)).toBe(true); + }); + + it('should serialize sealed objects', async () => { + const result = await testCode(`Object.seal({ x: 10, y: 20 })`); + expect(result).toEqual({ x: 10, y: 20 }); + expect(Object.isSealed(result)).toBe(true); + }); + + it('should serialize non-extensible objects', async () => { + const result = await testCode(`(() => { + const obj = { prop: 'value' }; + Object.preventExtensions(obj); + return obj; + })()`); + expect(result.prop).toBe('value'); + expect(Object.isExtensible(result)).toBe(false); + }); + + // Edge cases + it('should handle circular references', async () => { + const result = await testCode(`(() => { + const obj = { a: 1 }; + obj.self = obj; + return obj; + })()`); + expect(result.a).toBe(1); + expect(result.self).toBe(result); + }); + + it('should handle mutual references', async () => { + const result = await testCode(`(() => { + const obj1 = { name: 'obj1' }; + const obj2 = { name: 'obj2' }; + obj1.ref = obj2; + obj2.ref = obj1; + return { obj1, obj2 }; + })()`); + expect(result.obj1.name).toBe('obj1'); + expect(result.obj2.name).toBe('obj2'); + expect(result.obj1.ref).toBe(result.obj2); + expect(result.obj2.ref).toBe(result.obj1); + }); + + it('should serialize objects with symbol keys', async () => { + const result = await testCode(`(() => { + const sym1 = Symbol('key1'); + const sym2 = Symbol.for('key2'); + const obj = { + [sym1]: 'value1', + [sym2]: 'value2', + normal: 'value3' + }; + return obj; + })()`); + expect(result.normal).toBe('value3'); + const symbols = Object.getOwnPropertySymbols(result); + expect(symbols.length).toBe(2); + }); + + it('should serialize complex nested structures', async () => { + const result = await testCode(`{ + data: { + users: [ + { id: 1, name: "Alice", tags: new Set(["admin", "user"]) }, + { id: 2, name: "Bob", tags: new Set(["user"]) } + ], + index: new Map([ + [1, "Alice"], + [2, "Bob"] + ]), + metadata: { + created: new Date("2024-01-01"), + pattern: /user-\\d+/, + transform: (x) => x.toUpperCase() + } + } + }`); + expect(result.data.users[0].name).toBe("Alice"); + expect(result.data.users[0].tags.has("admin")).toBe(true); + expect(result.data.index.get(1)).toBe("Alice"); + expect(result.data.metadata.created.getFullYear()).toBe(2024); + expect(result.data.metadata.pattern.test("user-123")).toBe(true); + expect(result.data.metadata.transform("hello")).toBe("HELLO"); + }); + + it('should serialize objects with mixed property types', async () => { + const result = await testCode(`(() => { + const obj = { + normal: 'prop', + 123: 'numeric', + [Symbol('sym')]: 'symbol', + get computed() { return 'getter'; }, + method() { return 'method'; } + }; + Object.defineProperty(obj, 'hidden', { + value: 'non-enumerable', + enumerable: false + }); + return obj; + })()`); + expect(result.normal).toBe('prop'); + expect(result[123]).toBe('numeric'); + expect(result.computed).toBe('getter'); + expect(result.method()).toBe('method'); + expect(result.hidden).toBe('non-enumerable'); + }); + + it('should serialize async generator functions', async () => { + const result = await testCode(`async function* asyncGen() { + yield 1; + yield 2; + yield 3; + }`); + expect(typeof result).toBe('function'); + const generator = result(); + return await (async () => { + expect((await generator.next()).value).toBe(1); + expect((await generator.next()).value).toBe(2); + expect((await generator.next()).value).toBe(3); + expect((await generator.next()).done).toBe(true); + })(); + }); + + it('should serialize promises (with rejection)', async () => { + const result = await testCode(`Promise.resolve(42)`); + expect(result.constructor.name).toBe("Promise"); + // Note: Promises can't be fully serialized, they should reject + return await expect(result).rejects.toThrow(); + }); + + it('should serialize proxy objects', async () => { + const result = await testCode(`(() => { + const target = { value: 42 }; + const handler = { + get(target, prop) { + if (prop === 'doubled') { + return target.value * 2; + } + return target[prop]; + } + }; + return new Proxy(target, handler); + })()`); + // Note: Proxies may not serialize perfectly, but basic functionality should work + expect(result.value).toBe(42); + }); + + it('should handle objects with null prototype', async () => { + const result = await testCode(`Object.create(null)`); + expect(Object.getPrototypeOf(result)).toBe(null); + }); + + it('should serialize WeakMap and WeakSet', async () => { + const result = await testCode(`{ + weakMap: new WeakMap(), + weakSet: new WeakSet() + }`); + expect(result.weakMap.constructor.name).toBe("WeakMap"); + expect(result.weakSet.constructor.name).toBe("WeakSet"); + }); + + it('should serialize primitive wrapper objects', async () => { + const result = await testCode(`{ + num: new Number(42), + str: new String("hello"), + bool: new Boolean(true) + }`); + expect(typeof result.num).toBe("object"); + expect(typeof result.str).toBe("object"); + expect(typeof result.bool).toBe("object"); + expect(result.num.valueOf()).toBe(42); + expect(result.str.valueOf()).toBe("hello"); + expect(result.bool.valueOf()).toBe(true); + }); + + it('should handle very deeply nested objects', async () => { + const result = await testCode(`(() => { + let obj = { value: 0 }; + let current = obj; + for (let i = 1; i <= 100; i++) { + current.next = { value: i }; + current = current.next; + } + return obj; + })()`); + let current = result; + for (let i = 0; i <= 100; i++) { + expect(current.value).toBe(i); + current = current.next; + if (i === 100) { + expect(current).toBeUndefined(); + } + } + }); + + it('should serialize functions that reference each other', async () => { + const result = await testCode(`(() => { + function isEven(n) { + if (n === 0) return true; + return isOdd(n - 1); + } + + function isOdd(n) { + if (n === 0) return false; + return isEven(n - 1); + } + + return { isEven, isOdd }; + })()`); + expect(result.isEven(4)).toBe(true); + expect(result.isEven(5)).toBe(false); + expect(result.isOdd(3)).toBe(true); + expect(result.isOdd(6)).toBe(false); + }); + + it('should handle computed property names', async () => { + const result = await testCode(`(() => { + const key1 = 'dynamic'; + const key2 = 'Key'; + return { + [key1 + key2]: 'value', + ['computed' + 123]: 'another' + }; + })()`); + expect(result.dynamicKey).toBe('value'); + expect(result.computed123).toBe('another'); + }); + + it('should serialize template literal functions', async () => { + const result = await testCode(`(strings, ...values) => { + return strings.join('') + values.join(''); + }`); + expect(typeof result).toBe('function'); + expect(result(['Hello ', ' world'], 'beautiful')).toBe('Hello worldbeautiful'); + }); + + it('should handle object spread and rest', async () => { + const result = await testCode(`(() => { + const base = { a: 1, b: 2 }; + const extended = { ...base, c: 3, a: 10 }; + return extended; + })()`); + expect(result).toEqual({ a: 10, b: 2, c: 3 }); + }); + + it('should serialize objects with toJSON method', async () => { + const result = await testCode(`{ + value: 42, + toJSON() { + return { custom: this.value * 2 }; + } + }`); + expect(result.value).toBe(42); + expect(result.toJSON()).toEqual({ custom: 84 }); + }); + + // Bound functions and context + it('should serialize bound functions', async () => { + const result = await testCode(`(() => { + const obj = { + value: 42, + getValue: function() { + return this.value; + } + }; + return obj.getValue.bind(obj); + })()`); + expect(typeof result).toBe('function'); + expect(result()).toBe(42); + }); + + it('should serialize functions with call/apply', async () => { + const result = await testCode(`(() => { + function greet(greeting, punctuation) { + return greeting + ", " + this.name + punctuation; + } + return greet; + })()`); + expect(typeof result).toBe('function'); + expect(result.call({ name: "Alice" }, "Hello", "!")).toBe("Hello, Alice!"); + expect(result.apply({ name: "Bob" }, ["Hi", "?"])).toBe("Hi, Bob?"); + }); + + it('should serialize partially applied functions', async () => { + const result = await testCode(`(() => { + function add(a, b, c) { + return a + b + c; + } + return add.bind(null, 1, 2); + })()`); + expect(typeof result).toBe('function'); + expect(result(3)).toBe(6); + }); + + // Iterators and iterables + it('should serialize custom iterators', async () => { + const result = await testCode(`(() => { + const obj = { + values: [1, 2, 3], + [Symbol.iterator]: function() { + let index = 0; + const values = this.values; + return { + next() { + if (index < values.length) { + return { value: values[index++], done: false }; + } + return { done: true }; + } + }; + } + }; + return obj; + })()`); + const values = []; + for (const value of result) { + values.push(value); + } + expect(values).toEqual([1, 2, 3]); + }); + + it('should serialize async iterators', async () => { + const result = await testCode(`(() => { + const obj = { + values: [1, 2, 3], + [Symbol.asyncIterator]: async function*() { + for (const value of this.values) { + yield value; + } + } + }; + return obj; + })()`); + const values = []; + for await (const value of result) { + values.push(value); + } + expect(values).toEqual([1, 2, 3]); + }); + + it('should serialize Map iterators', async () => { + const result = await testCode(`(() => { + const map = new Map([['a', 1], ['b', 2]]); + return { + keys: [...map.keys()], + values: [...map.values()], + entries: [...map.entries()] + }; + })()`); + expect(result.keys).toEqual(['a', 'b']); + expect(result.values).toEqual([1, 2]); + expect(result.entries).toEqual([['a', 1], ['b', 2]]); + }); + + // String edge cases + it('should serialize strings with special characters', async () => { + const result = await testCode(`"Hello\\nWorld\\t\\r\\0\\x00\\u0000"`); + expect(result).toBe("Hello\nWorld\t\r\0\x00\u0000"); + }); + + it('should serialize template literals', async () => { + const result = await testCode(`(() => { + const name = "World"; + const num = 42; + return \`Hello \${name}, the answer is \${num}\`; + })()`); + expect(result).toBe("Hello World, the answer is 42"); + }); + + // Number edge cases + it('should serialize MAX_SAFE_INTEGER and beyond', async () => { + expect(await testCode(`Number.MAX_SAFE_INTEGER`)).toBe(Number.MAX_SAFE_INTEGER); + expect(await testCode(`Number.MIN_SAFE_INTEGER`)).toBe(Number.MIN_SAFE_INTEGER); + expect(await testCode(`Number.MAX_VALUE`)).toBe(Number.MAX_VALUE); + expect(await testCode(`Number.MIN_VALUE`)).toBe(Number.MIN_VALUE); + expect(await testCode(`Number.EPSILON`)).toBe(Number.EPSILON); + }); + + // Object property edge cases + it('should serialize property accessors with side effects', async () => { + const result = await testCode(`(() => { + let count = 0; + const obj = { + get counter() { + return ++count; + } + }; + return obj; + })()`); + const val1 = result.counter; + const val2 = result.counter; + expect(val2).toBeGreaterThan(val1); + }); + + it('should serialize objects with numeric string keys', async () => { + const result = await testCode(`{ + "0": "zero", + "1": "one", + "10": "ten", + "2": "two" + }`); + expect(result["0"]).toBe("zero"); + expect(result["1"]).toBe("one"); + expect(result["10"]).toBe("ten"); + expect(result["2"]).toBe("two"); + }); + + // Function edge cases + it('should serialize functions with default parameters', async () => { + const result = await testCode(`(function(a = 1, b = 2) { + return a + b; + })`); + expect(result()).toBe(3); + expect(result(5)).toBe(7); + expect(result(5, 5)).toBe(10); + }); + + it('should serialize functions with rest parameters', async () => { + const result = await testCode(`(function(...args) { + return args.reduce((a, b) => a + b, 0); + })`); + expect(result(1, 2, 3, 4, 5)).toBe(15); + }); + + it('should serialize functions with destructuring parameters', async () => { + const result = await testCode(`(function({ x, y = 10 }) { + return x + y; + })`); + expect(result({ x: 5 })).toBe(15); + expect(result({ x: 5, y: 3 })).toBe(8); + }); + + // Class edge cases + it('should serialize private class fields', async () => { + const result = await testCode(`(() => { + class Counter { + #count = 0; + + increment() { + return ++this.#count; + } + + getCount() { + return this.#count; + } + } + return new Counter(); + })()`); + expect(result.increment()).toBe(1); + expect(result.increment()).toBe(2); + expect(result.getCount()).toBe(2); + }); + + it('should serialize static class fields', async () => { + const result = await testCode(`class MyClass { + static staticField = "static value"; + static #privateStatic = "private"; + + static getPrivate() { + return this.#privateStatic; + } + }`); + expect(result.staticField).toBe("static value"); + expect(result.getPrivate()).toBe("private"); + }); + + it('should serialize class with getters/setters', async () => { + const result = await testCode(`(() => { + class Temperature { + constructor(celsius) { + this._celsius = celsius; + } + + get fahrenheit() { + return this._celsius * 9/5 + 32; + } + + set fahrenheit(value) { + this._celsius = (value - 32) * 5/9; + } + + get celsius() { + return this._celsius; + } + } + return new Temperature(0); + })()`); + expect(result.celsius).toBe(0); + expect(result.fahrenheit).toBe(32); + result.fahrenheit = 212; + expect(result.celsius).toBe(100); + }); + + it('should serialize class with getters accessing private fields', async () => { + const result = await testCode(`(() => { + class Temperature { + #celsius; + constructor(celsius) { + this.#celsius = celsius; + } + + get fahrenheit() { + return this.#celsius * 9/5 + 32; + } + + set fahrenheit(value) { + this.#celsius = (value - 32) * 5/9; + } + + get celsius() { + return this.#celsius; + } + } + return new Temperature(0); + })()`); + expect(result.fahrenheit).toBe(32); + result.fahrenheit = 212; + expect(result.celsius).toBe(100); + }); + + it('should serialize class with static getters/setters accessing static private fields', async () => { + const result = await testCode(`(() => { + class Temperature { + static #celsius = 0; + + static get fahrenheit() { + return this.#celsius * 9/5 + 32; + } + + static set fahrenheit(value) { + this.#celsius = (value - 32) * 5/9; + } + + static get celsius() { + return this.#celsius; + } + } + return Temperature; + })()`); + expect(result.fahrenheit).toBe(32); + result.fahrenheit = 212; + expect(result.celsius).toBe(100); + expect(result.fahrenheit).toBe(212); + }); + + it('should serialize class methods correctly even after they are no longer on the class prototype', async () => { + const result = await testCode(`(() => { + class MyClass { + method() { + return "Hello"; + } + } + const method = MyClass.prototype.method; + MyClass.prototype.method = () => "Goodbye"; + return [method, MyClass.prototype.method]; + })()`); + expect(result[0]()).toBe("Hello"); + expect(result[1]()).toBe("Goodbye"); + }); + + // Promise edge cases + it('should serialize rejected promises', async () => { + const result = await testCode(`Promise.reject(new Error("Test error"))`); + expect(result.constructor.name).toBe("Promise"); + return await expect(result).rejects.toThrow(); + }); + + it('should serialize promise chains', async () => { + const result = await testCode(`(() => { + return Promise.resolve(1) + .then(x => x * 2) + .then(x => x + 3); + })()`); + expect(result.constructor.name).toBe("Promise"); + // Can't fully serialize promise chains + return await expect(result).rejects.toThrow(); + }); + + // RegExp edge cases + it('should serialize complex regex patterns', async () => { + const result = await testCode(`/^(?:(?:https?|ftp):\\/\\/)(?:\\S+(?::\\S*)?@)?(?:(?!10(?:\\.\\d{1,3}){3}))/gim`); + expect(result.constructor.name).toBe("RegExp"); + expect(result.global).toBe(true); + expect(result.ignoreCase).toBe(true); + expect(result.multiline).toBe(true); + }); + + it('should serialize regex with unicode flag', async () => { + const result = await testCode(`/\\p{Emoji}/gu`); + expect(result.constructor.name).toBe("RegExp"); + expect(result.unicode).toBe(true); + expect(result.global).toBe(true); + }); + + // Error edge cases + it('should serialize custom error types', async () => { + const result = await testCode(`(() => { + class CustomError extends Error { + constructor(message, code) { + super(message); + this.name = "CustomError"; + this.code = code; + } + } + const err = new CustomError("Something went wrong", 404); + return err; + })()`); + expect(result.message).toBe("Something went wrong"); + expect(result.name).toBe("CustomError"); + expect(result.code).toBe(404); + }); + + it('should serialize error with cause', async () => { + const result = await testCode(`(() => { + const cause = new Error("Root cause"); + const error = new Error("High level error", { cause }); + return error; + })()`); + expect(result.message).toBe("High level error"); + expect(result.cause.message).toBe("Root cause"); + }); + + // Typed arrays edge cases + it('should serialize different typed arrays', async () => { + expect(Array.from(await testCode(`new Int8Array([127, -128, 0])`))).toEqual([127, -128, 0]); + expect(Array.from(await testCode(`new Uint16Array([0, 65535, 32768])`))).toEqual([0, 65535, 32768]); + expect(Array.from(await testCode(`new Float32Array([3.14, -0, Infinity])`))).toEqual([3.140000104904175, -0, Infinity]); + expect(Array.from(await testCode(`new BigInt64Array([123n, -456n])`))).toEqual([123n, -456n]); + }); + + it('should serialize DataView', async () => { + const result = await testCode(`(() => { + const buffer = new ArrayBuffer(8); + const view = new DataView(buffer); + view.setInt32(0, 42); + view.setFloat32(4, 3.14); + return view; + })()`); + expect(result.constructor.name).toBe("DataView"); + expect(result.getInt32(0)).toBe(42); + expect(result.getFloat32(4)).toBeCloseTo(3.14); + }); + + // Collection edge cases + it('should serialize Maps with object keys', async () => { + const result = await testCode(`(() => { + const key1 = { id: 1 }; + const key2 = { id: 2 }; + const map = new Map([ + [key1, "value1"], + [key2, "value2"] + ]); + return { map, key1, key2 }; + })()`); + expect(result.map.get(result.key1)).toBe("value1"); + expect(result.map.get(result.key2)).toBe("value2"); + }); + + it('should serialize recursive Maps', async () => { + const result = await testCode(`(() => { + const map = new Map(); + map.set("key1", "value1"); + map.set("key2", 42); + map.set(3, "three"); + map.set(map, map); + return map; + })()`); + expect(result.constructor.name).toBe("Map"); + expect(result.size).toBe(3); + expect(result.get("key1")).toBe("value1"); + expect(result.get("key2")).toBe(42); + expect(result.get(3)).toBe("three"); + expect(result.get(result)).toBe(result); + }); + + it('should serialize Sets with object values', async () => { + const result = await testCode(`(() => { + const obj1 = { id: 1 }; + const obj2 = { id: 2 }; + const set = new Set([obj1, obj2, obj1]); + return { set, obj1, obj2 }; + })()`); + expect(result.set.size).toBe(2); + expect(result.set.has(result.obj1)).toBe(true); + expect(result.set.has(result.obj2)).toBe(true); + }); + + // Object.create edge cases + it('should serialize objects with custom prototypes', async () => { + const result = await testCode(`(() => { + const proto = { + greet() { + return "Hello, " + this.name; + } + }; + const obj = Object.create(proto); + obj.name = "World"; + return obj; + })()`); + expect(result.name).toBe("World"); + expect(result.greet()).toBe("Hello, World"); + }); + + it('should serialize prototype chains', async () => { + const result = await testCode(`(() => { + const grandparent = { level: "grandparent" }; + const parent = Object.create(grandparent); + parent.level = "parent"; + const child = Object.create(parent); + child.level = "child"; + return child; + })()`); + expect(result.level).toBe("child"); + }); + + // Destructuring edge cases + it('should handle destructured function returns', async () => { + const result = await testCode(`(() => { + function getCoords() { + return { x: 10, y: 20, z: 30 }; + } + const { x, y } = getCoords(); + return { x, y }; + })()`); + expect(result).toEqual({ x: 10, y: 20 }); + }); + + // Multiple inheritance patterns + it('should serialize mixins', async () => { + const result = await testCode(`(() => { + const canFly = { + fly() { return this.name + " flies"; } + }; + + const canSwim = { + swim() { return this.name + " swims"; } + }; + + function createDuck(name) { + return Object.assign( + { name }, + canFly, + canSwim + ); + } + + return createDuck("Donald"); + })()`); + expect(result.name).toBe("Donald"); + expect(result.fly()).toBe("Donald flies"); + expect(result.swim()).toBe("Donald swims"); + }); + + // Object.assign and spread edge cases + it('should handle property override order', async () => { + const result = await testCode(`(() => { + const obj1 = { a: 1, b: 2 }; + const obj2 = { b: 3, c: 4 }; + const obj3 = { c: 5, d: 6 }; + return Object.assign({}, obj1, obj2, obj3); + })()`); + expect(result).toEqual({ a: 1, b: 3, c: 5, d: 6 }); + }); + + // Global objects + it('should handle references to global objects', async () => { + const result = await testCode(`{ + objProto: Object.prototype, + arrProto: Array.prototype, + funcProto: Function.prototype + }`); + expect(result.objProto.constructor.name).toBe("Object"); + expect(result.arrProto.constructor.name).toBe("Array"); + expect(result.funcProto.constructor.name).toBe("Function"); + }); + + // Arguments object + it('should serialize arguments object', async () => { + const result = await testCode(`(function() { + return arguments; + })(1, 2, 3)`); + expect(result[0]).toBe(1); + expect(result[1]).toBe(2); + expect(result[2]).toBe(3); + expect(result.length).toBe(3); + }); + + // Tagged template literals + it('should serialize tagged template functions', async () => { + const result = await testCode(`(() => { + function tag(strings, ...values) { + return { + strings: [...strings], + values: [...values] + }; + } + return tag; + })()`); + const tagged = result`Hello ${"World"} and ${42}!`; + expect(tagged.strings).toEqual(["Hello ", " and ", "!"]); + expect(tagged.values).toEqual(["World", 42]); + }); + + // Object.is edge cases + it('should preserve Object.is semantics', async () => { + const result = await testCode(`{ + zero: 0, + negZero: -0, + nan1: NaN, + nan2: 0/0 + }`); + expect(Object.is(result.zero, 0)).toBe(true); + expect(Object.is(result.negZero, -0)).toBe(true); + expect(Object.is(result.zero, result.negZero)).toBe(false); + expect(Object.is(result.nan1, NaN)).toBe(true); + expect(Object.is(result.nan1, result.nan2)).toBe(true); + }); + + // WeakMap operations + it('should serialize WeakMap with operations', async () => { + const result = await testCode(`(() => { + const wm = new WeakMap(); + const key1 = { id: 1 }; + const key2 = { id: 2 }; + wm.set(key1, "value1"); + wm.set(key2, "value2"); + return { wm, key1, key2 }; + })()`); + expect(result.wm.constructor.name).toBe("WeakMap"); + expect(result.wm.has(result.key1)).toBe(true); + expect(result.wm.has(result.key2)).toBe(true); + expect(result.wm.get(result.key1)).toBe("value1"); + expect(result.wm.get(result.key2)).toBe("value2"); + }); + + // WeakSet operations + it('should serialize WeakSet with operations', async () => { + const result = await testCode(`(() => { + const ws = new WeakSet(); + const obj1 = { id: 1 }; + const obj2 = { id: 2 }; + ws.add(obj1); + ws.add(obj2); + ws.add(obj1); // Adding twice, should still only be there once + return { ws, obj1, obj2 }; + })()`); + expect(result.ws.constructor.name).toBe("WeakSet"); + expect(result.ws.has(result.obj1)).toBe(true); + expect(result.ws.has(result.obj2)).toBe(true); + }); + + // WeakRef + it('should serialize WeakRef', async () => { + const result = await testCode(`(() => { + const target = { value: 42 }; + const ref = new WeakRef(target); + return { ref, target }; + })()`); + expect(result.ref.constructor.name).toBe("WeakRef"); + expect(result.ref.deref()).toBe(result.target); + expect(result.ref.deref().value).toBe(42); + }); + + // FinalizationRegistry + it('should serialize FinalizationRegistry', async () => { + const result = await testCode(`(() => { + let cleanupCalled = false; + const registry = new FinalizationRegistry((heldValue) => { + cleanupCalled = true; + }); + const target = { data: "test" }; + registry.register(target, "cleanup value"); + return { registry, target, getCleanupStatus: () => cleanupCalled }; + })()`); + expect(result.registry.constructor.name).toBe("FinalizationRegistry"); + expect(result.target.data).toBe("test"); + expect(result.getCleanupStatus()).toBe(false); // Won't be called immediately + }); + + // WeakMap with complex values + it('should serialize WeakMap with complex values', async () => { + const result = await testCode(`(() => { + const wm = new WeakMap(); + const key = { name: "key" }; + const value = { + data: [1, 2, 3], + nested: { prop: "value" }, + fn: function() { return this.data.length; } + }; + wm.set(key, value); + return { wm, key }; + })()`); + const value = result.wm.get(result.key); + expect(value.data).toEqual([1, 2, 3]); + expect(value.nested.prop).toBe("value"); + expect(value.fn()).toBe(3); + }); + + // Multiple WeakRefs to same object + it('should serialize multiple WeakRefs to same target', async () => { + const result = await testCode(`(() => { + const target = { shared: "data" }; + const ref1 = new WeakRef(target); + const ref2 = new WeakRef(target); + return { ref1, ref2, target }; + })()`); + expect(result.ref1.deref()).toBe(result.target); + expect(result.ref2.deref()).toBe(result.target); + expect(result.ref1.deref()).toBe(result.ref2.deref()); + }); + + // WeakMap/WeakSet edge cases + it('should handle WeakMap/WeakSet with deleted keys', async () => { + const result = await testCode(`(() => { + const wm = new WeakMap(); + const ws = new WeakSet(); + const key1 = { id: 1 }; + const key2 = { id: 2 }; + + wm.set(key1, "value1"); + wm.set(key2, "value2"); + ws.add(key1); + ws.add(key2); + + wm.delete(key1); + ws.delete(key1); + + return { wm, ws, key1, key2 }; + })()`); + expect(result.wm.has(result.key1)).toBe(false); + expect(result.wm.has(result.key2)).toBe(true); + expect(result.ws.has(result.key1)).toBe(false); + expect(result.ws.has(result.key2)).toBe(true); + }); + + // WeakRef deref returning undefined + it('should handle WeakRef with nullified target', async () => { + const result = await testCode(`(() => { + let target = { value: 42 }; + const ref = new WeakRef(target); + // Note: In real scenarios, target might be garbage collected + // For testing, we just simulate the scenario + return { ref, hasTarget: () => ref.deref() !== undefined }; + })()`); + expect(result.ref.constructor.name).toBe("WeakRef"); + expect(result.hasTarget()).toBe(true); // In test, target is kept alive + }); + + // FinalizationRegistry with unregister + it('should serialize FinalizationRegistry with unregister', async () => { + const result = await testCode(`(() => { + const registry = new FinalizationRegistry((heldValue) => { + console.log("Cleanup:", heldValue); + }); + const target1 = { id: 1 }; + const target2 = { id: 2 }; + const token1 = {}; + const token2 = {}; + + registry.register(target1, "value1", token1); + registry.register(target2, "value2", token2); + registry.unregister(token1); + + return { registry, target1, target2, token1, token2 }; + })()`); + expect(result.registry.constructor.name).toBe("FinalizationRegistry"); + expect(result.target1.id).toBe(1); + expect(result.target2.id).toBe(2); + }); + + // Combinations of weak references + it('should serialize combinations of weak collections', async () => { + const result = await testCode(`(() => { + const shared = { shared: true }; + const wm = new WeakMap(); + const ws = new WeakSet(); + const wr = new WeakRef(shared); + + wm.set(shared, "in WeakMap"); + ws.add(shared); + + return { + shared, + wm, + ws, + wr, + check: () => ({ + inMap: wm.has(shared), + inSet: ws.has(shared), + deref: wr.deref() === shared + }) + }; + })()`); + const check = result.check(); + expect(check.inMap).toBe(true); + expect(check.inSet).toBe(true); + expect(check.deref).toBe(true); + }); + + // WeakMap as value in another WeakMap + it('should serialize nested weak collections', async () => { + const result = await testCode(`(() => { + const outer = new WeakMap(); + const inner = new WeakMap(); + const key1 = { level: "outer" }; + const key2 = { level: "inner" }; + + inner.set(key2, "inner value"); + outer.set(key1, inner); + + return { outer, key1, key2 }; + })()`); + const inner = result.outer.get(result.key1); + expect(inner.constructor.name).toBe("WeakMap"); + expect(inner.get(result.key2)).toBe("inner value"); + }); + + // Symbol as WeakMap value (not key) + it('should serialize symbols in weak collections', async () => { + const result = await testCode(`(() => { + const wm = new WeakMap(); + const key = { id: "key" }; + const sym = Symbol("mySymbol"); + wm.set(key, sym); + return { wm, key, sym }; + })()`); + expect(result.wm.get(result.key)).toBe(result.sym); + expect(typeof result.wm.get(result.key)).toBe("symbol"); + }); + + // WeakRef in arrays and objects + it('should serialize WeakRefs in collections', async () => { + const result = await testCode(`(() => { + const targets = [ + { id: 1 }, + { id: 2 }, + { id: 3 } + ]; + const refs = targets.map(t => new WeakRef(t)); + const refMap = new Map(targets.map((t, i) => [i, new WeakRef(t)])); + + return { targets, refs, refMap }; + })()`); + expect(result.refs[0].deref().id).toBe(1); + expect(result.refs[1].deref().id).toBe(2); + expect(result.refs[2].deref().id).toBe(3); + expect(result.refMap.get(0).deref().id).toBe(1); + }); + + // Circular references with WeakMap + it('should handle circular references in WeakMaps', async () => { + const result = await testCode(`(() => { + const wm = new WeakMap(); + const obj = { name: "circular" }; + obj.weakMap = wm; + wm.set(obj, obj); + return { wm, obj }; + })()`); + expect(result.wm.get(result.obj)).toBe(result.obj); + expect(result.obj.weakMap).toBe(result.wm); + }); + + // WeakSet with functions + it('should serialize functions in WeakSet', async () => { + const result = await testCode(`(() => { + const ws = new WeakSet(); + const fn1 = function() { return 1; }; + const fn2 = () => 2; + const fn3 = async function() { return 3; }; + + ws.add(fn1); + ws.add(fn2); + ws.add(fn3); + + return { ws, fn1, fn2, fn3 }; + })()`); + expect(result.ws.has(result.fn1)).toBe(true); + expect(result.ws.has(result.fn2)).toBe(true); + expect(result.ws.has(result.fn3)).toBe(true); + }); + + // FinalizationRegistry with multiple registrations + it('should handle FinalizationRegistry with multiple targets', async () => { + const result = await testCode(`(() => { + const cleanups = []; + const registry = new FinalizationRegistry((heldValue) => { + cleanups.push(heldValue); + }); + + const targets = [ + { id: 1 }, + { id: 2 }, + { id: 3 } + ]; + + targets.forEach((target, i) => { + registry.register(target, "cleanup-" + i); + }); + + return { registry, targets, getCleanups: () => [...cleanups] }; + })()`); + expect(result.registry.constructor.name).toBe("FinalizationRegistry"); + expect(result.targets.length).toBe(3); + expect(result.getCleanups()).toEqual([]); // No cleanups yet + }); +}); diff --git a/packages/stack-shared/src/helpers/js-serializer/transpiler.test.ts b/packages/stack-shared/src/helpers/js-serializer/transpiler.test.ts new file mode 100644 index 0000000000..38744df57b --- /dev/null +++ b/packages/stack-shared/src/helpers/js-serializer/transpiler.test.ts @@ -0,0 +1,1966 @@ +import { describe, expect, it } from 'vitest'; +import { deindent } from '../../utils/strings'; +import { transpileJsToSerializableJs } from './transpiler'; + +function getUserCode(str: string): string { + const userCodeStartSentinel = "// USER_CODE_START\n"; + const userCodeEndSentinel = "// USER_CODE_END\n"; + return str.slice(str.indexOf(userCodeStartSentinel) + userCodeStartSentinel.length, str.indexOf(userCodeEndSentinel)); +} + +describe('transpileJsToSerializableJs', () => { + describe('Arrow Functions', () => { + it('should transform arrow functions without scope', () => { + const input = deindent` + () => { + console.log('hello'); + }; + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + __STACK_SerializableJs.registerFunction(() => { + console.log('hello'); + }, { + scope: () => ({}), + source: "()=>{console.log('hello');}" + })" + `); + }); + + it('should transform arrow functions in object fields called `scope`', () => { + const input = deindent` + const obj = { + scope: () => ({}), + fn: () => { + console.log('hello'); + } + }; + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const obj = { + scope: __STACK_SerializableJs.registerFunction(() => ({}), { + scope: () => ({}), + source: "()=>({})" + }), + fn: __STACK_SerializableJs.registerFunction(() => { + console.log('hello'); + }, { + scope: () => ({}), + source: "()=>{console.log('hello');}" + }) + }" + `); + }); + + it('should transform arrow functions with scope', () => { + const input = deindent` + const x = 123; + () => { + console.log(x); + }; + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const x = 123; + __STACK_SerializableJs.registerFunction(() => { + console.log(x); + }, { + scope: () => ({ + x + }), + source: "()=>{console.log(__$getFromScope(\\"x\\"));}" + })" + `); + }); + + it('should transform recursive arrow functions', () => { + const input = deindent` + const fn = () => fn(); + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const fn = __STACK_SerializableJs.registerFunction(() => fn(), { + scope: () => ({ + fn + }), + source: "()=>__$getFromScope(\\"fn\\")()" + })" + `); + }); + + it('should transform recursive arrow functions that use a different name for the recursive call', () => { + const input = deindent` + const fn = () => b(); + const b = fn; + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const fn = __STACK_SerializableJs.registerFunction(() => b(), { + scope: () => ({ + b + }), + source: "()=>__$getFromScope(\\"b\\")()" + }); + const b = fn" + `); + }); + + it('should transform arrow functions with single expression', () => { + const input = deindent` + const x = 5; + const fn = () => x * 2; + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const x = 5; + const fn = __STACK_SerializableJs.registerFunction(() => x * 2, { + scope: () => ({ + x + }), + source: "()=>__$getFromScope(\\"x\\")*2" + })" + `); + }); + + it('should transform arrow functions with single parameter without parens', () => { + const input = deindent` + const multiplier = 2; + const fn = x => x * multiplier; + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const multiplier = 2; + const fn = __STACK_SerializableJs.registerFunction(x => x * multiplier, { + scope: () => ({ + multiplier + }), + source: "x=>x*__$getFromScope(\\"multiplier\\")" + })" + `); + }); + + it('should transform arrow functions with multiple parameters', () => { + const input = deindent` + const offset = 10; + const fn = (a, b) => a + b + offset; + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const offset = 10; + const fn = __STACK_SerializableJs.registerFunction((a, b) => a + b + offset, { + scope: () => ({ + offset + }), + source: "(a,b)=>a+b+__$getFromScope(\\"offset\\")" + })" + `); + }); + + it('should transform arrow functions with more complicated dependencies', () => { + const input = deindent` + const store = { + offset: 10 + }; + const fn = (a, b) => a + b + store.offset; + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const store = { + offset: 10 + }; + const fn = __STACK_SerializableJs.registerFunction((a, b) => a + b + store.offset, { + scope: () => ({ + store + }), + source: "(a,b)=>a+b+__$getFromScope(\\"store\\").offset" + })" + `); + }); + + it('should transform async arrow functions', () => { + const input = deindent` + const url = 'api/data'; + const fn = async () => { + return await fetch(url); + }; + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const url = 'api/data'; + const fn = /*#__PURE__*/__STACK_SerializableJs.registerFunction(function () { + var _ref = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(__STACK_SerializableJs.registerFunction(function _callee() { + return _regeneratorRuntime.wrap(__STACK_SerializableJs.registerFunction(function (_context) { + while (1) switch (_context.prev = _context.next) { + case 0: + _context.next = 1; + return fetch(url); + case 1: + return _context.abrupt("return", _context.sent); + case 2: + case "end": + return _context.stop(); + } + }, { + scope: () => ({ + url + }), + source: "(function(_context){while(1)switch(_context.prev=_context.next){case 0:_context.next=1;return fetch(__$getFromScope(\\"url\\"));case 1:return _context.abrupt(\\"return\\",_context.sent);case 2:case\\"end\\":return _context.stop();}})" + }), _callee); + }, { + scope: () => ({ + _regeneratorRuntime, + url + }), + source: "function _callee(){return __$getFromScope(\\"_regeneratorRuntime\\").wrap(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:_context.next=1;return fetch(__$getFromScope(\\"url\\"));case 1:return _context.abrupt(\\"return\\",_context.sent);case 2:case\\"end\\":return _context.stop();}},{scope:()=>({url:__$getFromScope(\\"url\\")}),source:\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:_context.next=1;return fetch(__$getFromScope(\\\\\\"url\\\\\\"));case 1:return _context.abrupt(\\\\\\"return\\\\\\",_context.sent);case 2:case\\\\\\"end\\\\\\":return _context.stop();}})\\"}),_callee);}" + }))); + return __STACK_SerializableJs.registerFunction(function fn() { + return _ref.apply(this, arguments); + }, { + scope: () => ({ + _ref + }), + source: "function fn(){return __$getFromScope(\\"_ref\\").apply(this,arguments);}" + }); + }, { + scope: () => ({ + _asyncToGenerator, + _regeneratorRuntime, + url + }), + source: "(function(){var _ref=__$getFromScope(\\"_asyncToGenerator\\")(__$getFromScope(\\"_regeneratorRuntime\\").mark(__STACK_SerializableJs.registerFunction(function _callee(){return __$getFromScope(\\"_regeneratorRuntime\\").wrap(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:_context.next=1;return fetch(__$getFromScope(\\"url\\"));case 1:return _context.abrupt(\\"return\\",_context.sent);case 2:case\\"end\\":return _context.stop();}},{scope:()=>({url:__$getFromScope(\\"url\\")}),source:\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:_context.next=1;return fetch(__$getFromScope(\\\\\\"url\\\\\\"));case 1:return _context.abrupt(\\\\\\"return\\\\\\",_context.sent);case 2:case\\\\\\"end\\\\\\":return _context.stop();}})\\"}),_callee);},{scope:()=>({_regeneratorRuntime:__$getFromScope(\\"_regeneratorRuntime\\"),url:__$getFromScope(\\"url\\")}),source:\\"function _callee(){return __$getFromScope(\\\\\\"_regeneratorRuntime\\\\\\").wrap(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:_context.next=1;return fetch(__$getFromScope(\\\\\\"url\\\\\\"));case 1:return _context.abrupt(\\\\\\"return\\\\\\",_context.sent);case 2:case\\\\\\"end\\\\\\":return _context.stop();}},{scope:()=>({url:__$getFromScope(\\\\\\"url\\\\\\")}),source:\\\\\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:_context.next=1;return fetch(__$getFromScope(\\\\\\\\\\\\\\"url\\\\\\\\\\\\\\"));case 1:return _context.abrupt(\\\\\\\\\\\\\\"return\\\\\\\\\\\\\\",_context.sent);case 2:case\\\\\\\\\\\\\\"end\\\\\\\\\\\\\\":return _context.stop();}})\\\\\\"}),_callee);}\\"})));return __STACK_SerializableJs.registerFunction(function fn(){return _ref.apply(this,arguments);},{scope:()=>({_ref}),source:\\"function fn(){return __$getFromScope(\\\\\\"_ref\\\\\\").apply(this,arguments);}\\"});})" + })()" + `); + }); + }); + + describe('Function Expressions', () => { + it('should transform anonymous function expressions', () => { + const input = deindent` + const y = 456; + const fn = function() { + return y * 2; + }; + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const y = 456; + const fn = __STACK_SerializableJs.registerFunction(function () { + return y * 2; + }, { + scope: () => ({ + y + }), + source: "(function(){return __$getFromScope(\\"y\\")*2;})" + })" + `); + }); + + it('should transform named function expressions', () => { + const input = deindent` + const x = 10; + const fn = function named() { + return x; + }; + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const x = 10; + const fn = __STACK_SerializableJs.registerFunction(function named() { + return x; + }, { + scope: () => ({ + x + }), + source: "function named(){return __$getFromScope(\\"x\\");}" + })" + `); + }); + + it('should transform async function expressions', () => { + const input = deindent` + const delay = 1000; + const fn = async function() { + await new Promise(r => setTimeout(r, delay)); + }; + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const delay = 1000; + const fn = /*#__PURE__*/__STACK_SerializableJs.registerFunction(function () { + var _ref = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(__STACK_SerializableJs.registerFunction(function _callee() { + return _regeneratorRuntime.wrap(__STACK_SerializableJs.registerFunction(function (_context) { + while (1) switch (_context.prev = _context.next) { + case 0: + _context.next = 1; + return new Promise(__STACK_SerializableJs.registerFunction(r => setTimeout(r, delay), { + scope: () => ({ + delay + }), + source: "r=>setTimeout(r,__$getFromScope(\\"delay\\"))" + })); + case 1: + case "end": + return _context.stop(); + } + }, { + scope: () => ({ + delay + }), + source: "(function(_context){while(1)switch(_context.prev=_context.next){case 0:_context.next=1;return new Promise(__STACK_SerializableJs.registerFunction(r=>setTimeout(r,__$getFromScope(\\"delay\\")),{scope:()=>({delay:__$getFromScope(\\"delay\\")}),source:\\"r=>setTimeout(r,__$getFromScope(\\\\\\"delay\\\\\\"))\\"}));case 1:case\\"end\\":return _context.stop();}})" + }), _callee); + }, { + scope: () => ({ + _regeneratorRuntime, + delay + }), + source: "function _callee(){return __$getFromScope(\\"_regeneratorRuntime\\").wrap(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:_context.next=1;return new Promise(__STACK_SerializableJs.registerFunction(r=>setTimeout(r,__$getFromScope(\\"delay\\")),{scope:()=>({delay:__$getFromScope(\\"delay\\")}),source:\\"r=>setTimeout(r,__$getFromScope(\\\\\\"delay\\\\\\"))\\"}));case 1:case\\"end\\":return _context.stop();}},{scope:()=>({delay:__$getFromScope(\\"delay\\")}),source:\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:_context.next=1;return new Promise(__STACK_SerializableJs.registerFunction(r=>setTimeout(r,__$getFromScope(\\\\\\"delay\\\\\\")),{scope:()=>({delay:__$getFromScope(\\\\\\"delay\\\\\\")}),source:\\\\\\"r=>setTimeout(r,__$getFromScope(\\\\\\\\\\\\\\"delay\\\\\\\\\\\\\\"))\\\\\\"}));case 1:case\\\\\\"end\\\\\\":return _context.stop();}})\\"}),_callee);}" + }))); + return __STACK_SerializableJs.registerFunction(function fn() { + return _ref.apply(this, arguments); + }, { + scope: () => ({ + _ref + }), + source: "function fn(){return __$getFromScope(\\"_ref\\").apply(this,arguments);}" + }); + }, { + scope: () => ({ + _asyncToGenerator, + _regeneratorRuntime, + delay + }), + source: "(function(){var _ref=__$getFromScope(\\"_asyncToGenerator\\")(__$getFromScope(\\"_regeneratorRuntime\\").mark(__STACK_SerializableJs.registerFunction(function _callee(){return __$getFromScope(\\"_regeneratorRuntime\\").wrap(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:_context.next=1;return new Promise(__STACK_SerializableJs.registerFunction(r=>setTimeout(r,__$getFromScope(\\"delay\\")),{scope:()=>({delay:__$getFromScope(\\"delay\\")}),source:\\"r=>setTimeout(r,__$getFromScope(\\\\\\"delay\\\\\\"))\\"}));case 1:case\\"end\\":return _context.stop();}},{scope:()=>({delay:__$getFromScope(\\"delay\\")}),source:\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:_context.next=1;return new Promise(__STACK_SerializableJs.registerFunction(r=>setTimeout(r,__$getFromScope(\\\\\\"delay\\\\\\")),{scope:()=>({delay:__$getFromScope(\\\\\\"delay\\\\\\")}),source:\\\\\\"r=>setTimeout(r,__$getFromScope(\\\\\\\\\\\\\\"delay\\\\\\\\\\\\\\"))\\\\\\"}));case 1:case\\\\\\"end\\\\\\":return _context.stop();}})\\"}),_callee);},{scope:()=>({_regeneratorRuntime:__$getFromScope(\\"_regeneratorRuntime\\"),delay:__$getFromScope(\\"delay\\")}),source:\\"function _callee(){return __$getFromScope(\\\\\\"_regeneratorRuntime\\\\\\").wrap(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:_context.next=1;return new Promise(__STACK_SerializableJs.registerFunction(r=>setTimeout(r,__$getFromScope(\\\\\\"delay\\\\\\")),{scope:()=>({delay:__$getFromScope(\\\\\\"delay\\\\\\")}),source:\\\\\\"r=>setTimeout(r,__$getFromScope(\\\\\\\\\\\\\\"delay\\\\\\\\\\\\\\"))\\\\\\"}));case 1:case\\\\\\"end\\\\\\":return _context.stop();}},{scope:()=>({delay:__$getFromScope(\\\\\\"delay\\\\\\")}),source:\\\\\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:_context.next=1;return new Promise(__STACK_SerializableJs.registerFunction(r=>setTimeout(r,__$getFromScope(\\\\\\\\\\\\\\"delay\\\\\\\\\\\\\\")),{scope:()=>({delay:__$getFromScope(\\\\\\\\\\\\\\"delay\\\\\\\\\\\\\\")}),source:\\\\\\\\\\\\\\"r=>setTimeout(r,__$getFromScope(\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\"delay\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\"))\\\\\\\\\\\\\\"}));case 1:case\\\\\\\\\\\\\\"end\\\\\\\\\\\\\\":return _context.stop();}})\\\\\\"}),_callee);}\\"})));return __STACK_SerializableJs.registerFunction(function fn(){return _ref.apply(this,arguments);},{scope:()=>({_ref}),source:\\"function fn(){return __$getFromScope(\\\\\\"_ref\\\\\\").apply(this,arguments);}\\"});})" + })()" + `); + }); + + it('should transform generator function expressions', () => { + const input = deindent` + const max = 5; + const gen = function*() { + for (let i = 0; i < max; i++) yield i; + }; + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const max = 5; + const gen = /*#__PURE__*/_regeneratorRuntime.mark(__STACK_SerializableJs.registerFunction(function _callee() { + var i; + return _regeneratorRuntime.wrap(__STACK_SerializableJs.registerFunction(function (_context) { + while (1) switch (_context.prev = _context.next) { + case 0: + i = 0; + case 1: + if (!(i < max)) { + _context.next = 3; + break; + } + _context.next = 2; + return i; + case 2: + i++; + _context.next = 1; + break; + case 3: + case "end": + return _context.stop(); + } + }, { + scope: () => ({ + i, + max + }), + source: "(function(_context){while(1)switch(_context.prev=_context.next){case 0:i=0;case 1:if(!(__$getFromScope(\\"i\\")<__$getFromScope(\\"max\\"))){_context.next=3;break;}_context.next=2;return __$getFromScope(\\"i\\");case 2:__$getFromScope(\\"i\\")++;_context.next=1;break;case 3:case\\"end\\":return _context.stop();}})" + }), _callee); + }, { + scope: () => ({ + _regeneratorRuntime, + max + }), + source: "function _callee(){var i;return __$getFromScope(\\"_regeneratorRuntime\\").wrap(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:i=0;case 1:if(!(i<__$getFromScope(\\"max\\"))){_context.next=3;break;}_context.next=2;return i;case 2:i++;_context.next=1;break;case 3:case\\"end\\":return _context.stop();}},{scope:()=>({i,max:__$getFromScope(\\"max\\")}),source:\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:i=0;case 1:if(!(__$getFromScope(\\\\\\"i\\\\\\")<__$getFromScope(\\\\\\"max\\\\\\"))){_context.next=3;break;}_context.next=2;return __$getFromScope(\\\\\\"i\\\\\\");case 2:__$getFromScope(\\\\\\"i\\\\\\")++;_context.next=1;break;case 3:case\\\\\\"end\\\\\\":return _context.stop();}})\\"}),_callee);}" + }))" + `); + }); + + it('should transform async generator function expressions', () => { + const input = deindent` + const data = [1, 2, 3]; + const gen = async function*() { + for (const item of data) yield item; + }; + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const data = [1, 2, 3]; + const gen = /*#__PURE__*/_regeneratorRuntime.mark(__STACK_SerializableJs.registerFunction(function _callee() { + var _i, _data, item; + return _regeneratorRuntime.async(__STACK_SerializableJs.registerFunction(function (_context) { + while (1) switch (_context.prev = _context.next) { + case 0: + _i = 0, _data = data; + case 1: + if (!(_i < _data.length)) { + _context.next = 3; + break; + } + item = _data[_i]; + _context.next = 2; + return item; + case 2: + _i++; + _context.next = 1; + break; + case 3: + case "end": + return _context.stop(); + } + }, { + scope: () => ({ + data, + _i, + _data, + item + }), + source: "(function(_context){while(1)switch(_context.prev=_context.next){case 0:_i=0,_data=__$getFromScope(\\"data\\");case 1:if(!(__$getFromScope(\\"_i\\")<__$getFromScope(\\"_data\\").length)){_context.next=3;break;}item=__$getFromScope(\\"_data\\")[__$getFromScope(\\"_i\\")];_context.next=2;return __$getFromScope(\\"item\\");case 2:__$getFromScope(\\"_i\\")++;_context.next=1;break;case 3:case\\"end\\":return _context.stop();}})" + }), _callee, null, null, Promise); + }, { + scope: () => ({ + _regeneratorRuntime, + data + }), + source: "function _callee(){var _i,_data,item;return __$getFromScope(\\"_regeneratorRuntime\\").async(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:_i=0,_data=__$getFromScope(\\"data\\");case 1:if(!(_i<_data.length)){_context.next=3;break;}item=_data[_i];_context.next=2;return item;case 2:_i++;_context.next=1;break;case 3:case\\"end\\":return _context.stop();}},{scope:()=>({data:__$getFromScope(\\"data\\"),_i,_data,item}),source:\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:_i=0,_data=__$getFromScope(\\\\\\"data\\\\\\");case 1:if(!(__$getFromScope(\\\\\\"_i\\\\\\")<__$getFromScope(\\\\\\"_data\\\\\\").length)){_context.next=3;break;}item=__$getFromScope(\\\\\\"_data\\\\\\")[__$getFromScope(\\\\\\"_i\\\\\\")];_context.next=2;return __$getFromScope(\\\\\\"item\\\\\\");case 2:__$getFromScope(\\\\\\"_i\\\\\\")++;_context.next=1;break;case 3:case\\\\\\"end\\\\\\":return _context.stop();}})\\"}),_callee,null,null,Promise);}" + }))" + `); + }); + }); + + describe('Function Declarations', () => { + it('should transform function declarations', () => { + const input = deindent` + const data = 'test'; + function fetchData() { + return data; + } + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const data = 'test'; + function fetchData() { + return data; + } + __STACK_SerializableJs.registerFunction(fetchData, { + scope: () => ({ + data + }), + source: "function fetchData(){return __$getFromScope(\\"data\\");}" + })" + `); + }); + + it('should transform function declarations that call each other in a circular dependency', () => { + const input = deindent` + function a() { + return b(); + } + function b() { + return a(); + } + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + function a() { + return b(); + } + __STACK_SerializableJs.registerFunction(a, { + scope: () => ({ + b + }), + source: "function a(){return __$getFromScope(\\"b\\")();}" + }); + function b() { + return a(); + } + __STACK_SerializableJs.registerFunction(b, { + scope: () => ({ + a + }), + source: "function b(){return __$getFromScope(\\"a\\")();}" + })" + `); + }); + + it('should transform recursive function declarations', () => { + const input = deindent` + function a() { + return a(); + } + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + function a() { + return a(); + } + __STACK_SerializableJs.registerFunction(a, { + scope: () => ({ + a + }), + source: "function a(){return __$getFromScope(\\"a\\")();}" + })" + `); + }); + + it('should transform async function declarations', () => { + const input = deindent` + const endpoint = '/api'; + async function fetchData() { + return await fetch(endpoint); + } + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const endpoint = '/api'; + function fetchData() { + return _fetchData.apply(this, arguments); + } + __STACK_SerializableJs.registerFunction(fetchData, { + scope: () => ({ + _fetchData + }), + source: "function fetchData(){return __$getFromScope(\\"_fetchData\\").apply(this,arguments);}" + }); + function _fetchData() { + _fetchData = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(__STACK_SerializableJs.registerFunction(function _callee() { + return _regeneratorRuntime.wrap(__STACK_SerializableJs.registerFunction(function (_context) { + while (1) switch (_context.prev = _context.next) { + case 0: + _context.next = 1; + return fetch(endpoint); + case 1: + return _context.abrupt("return", _context.sent); + case 2: + case "end": + return _context.stop(); + } + }, { + scope: () => ({ + endpoint + }), + source: "(function(_context){while(1)switch(_context.prev=_context.next){case 0:_context.next=1;return fetch(__$getFromScope(\\"endpoint\\"));case 1:return _context.abrupt(\\"return\\",_context.sent);case 2:case\\"end\\":return _context.stop();}})" + }), _callee); + }, { + scope: () => ({ + _regeneratorRuntime, + endpoint + }), + source: "function _callee(){return __$getFromScope(\\"_regeneratorRuntime\\").wrap(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:_context.next=1;return fetch(__$getFromScope(\\"endpoint\\"));case 1:return _context.abrupt(\\"return\\",_context.sent);case 2:case\\"end\\":return _context.stop();}},{scope:()=>({endpoint:__$getFromScope(\\"endpoint\\")}),source:\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:_context.next=1;return fetch(__$getFromScope(\\\\\\"endpoint\\\\\\"));case 1:return _context.abrupt(\\\\\\"return\\\\\\",_context.sent);case 2:case\\\\\\"end\\\\\\":return _context.stop();}})\\"}),_callee);}" + }))); + return _fetchData.apply(this, arguments); + } + __STACK_SerializableJs.registerFunction(_fetchData, { + scope: () => ({ + _asyncToGenerator, + _regeneratorRuntime, + endpoint, + _fetchData + }), + source: "function _fetchData(){_fetchData=__$getFromScope(\\"_asyncToGenerator\\")(__$getFromScope(\\"_regeneratorRuntime\\").mark(function _callee(){return __$getFromScope(\\"_regeneratorRuntime\\").wrap(function(_context){while(1)switch(_context.prev=_context.next){case 0:_context.next=1;return fetch(__$getFromScope(\\"endpoint\\"));case 1:return _context.abrupt(\\"return\\",_context.sent);case 2:case\\"end\\":return _context.stop();}},_callee);}));return __$getFromScope(\\"_fetchData\\").apply(this,arguments);}" + })" + `); + }); + + it('should transform generator function declarations', () => { + const input = deindent` + const items = [1, 2, 3]; + function* generator() { + yield* items; + } + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + var _marked = /*#__PURE__*/_regeneratorRuntime.mark(generator); + const items = [1, 2, 3]; + function generator() { + return _regeneratorRuntime.wrap(__STACK_SerializableJs.registerFunction(function (_context) { + while (1) switch (_context.prev = _context.next) { + case 0: + return _context.delegateYield(items, "t0", 1); + case 1: + case "end": + return _context.stop(); + } + }, { + scope: () => ({ + items + }), + source: "(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.delegateYield(__$getFromScope(\\"items\\"),\\"t0\\",1);case 1:case\\"end\\":return _context.stop();}})" + }), _marked); + } + __STACK_SerializableJs.registerFunction(generator, { + scope: () => ({ + _regeneratorRuntime, + items, + _marked + }), + source: "function generator(){return __$getFromScope(\\"_regeneratorRuntime\\").wrap(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.delegateYield(__$getFromScope(\\"items\\"),\\"t0\\",1);case 1:case\\"end\\":return _context.stop();}},__$getFromScope(\\"_marked\\"));}" + })" + `); + }); + + it('should transform async generator function declarations', () => { + const input = deindent` + const source = [1, 2, 3]; + async function* asyncGen() { + for (const item of source) { + yield await Promise.resolve(item); + } + } + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + var _marked = /*#__PURE__*/_regeneratorRuntime.mark(asyncGen); + const source = [1, 2, 3]; + function asyncGen() { + var _i, _source, item; + return _regeneratorRuntime.async(__STACK_SerializableJs.registerFunction(function (_context) { + while (1) switch (_context.prev = _context.next) { + case 0: + _i = 0, _source = source; + case 1: + if (!(_i < _source.length)) { + _context.next = 4; + break; + } + item = _source[_i]; + _context.next = 2; + return _regeneratorRuntime.awrap(Promise.resolve(item)); + case 2: + _context.next = 3; + return _context.sent; + case 3: + _i++; + _context.next = 1; + break; + case 4: + case "end": + return _context.stop(); + } + }, { + scope: () => ({ + source, + _i, + _source, + _regeneratorRuntime, + item + }), + source: "(function(_context){while(1)switch(_context.prev=_context.next){case 0:_i=0,_source=__$getFromScope(\\"source\\");case 1:if(!(__$getFromScope(\\"_i\\")<__$getFromScope(\\"_source\\").length)){_context.next=4;break;}item=__$getFromScope(\\"_source\\")[__$getFromScope(\\"_i\\")];_context.next=2;return __$getFromScope(\\"_regeneratorRuntime\\").awrap(Promise.resolve(__$getFromScope(\\"item\\")));case 2:_context.next=3;return _context.sent;case 3:__$getFromScope(\\"_i\\")++;_context.next=1;break;case 4:case\\"end\\":return _context.stop();}})" + }), _marked, null, null, Promise); + } + __STACK_SerializableJs.registerFunction(asyncGen, { + scope: () => ({ + _regeneratorRuntime, + source, + _marked + }), + source: "function asyncGen(){var _i,_source,item;return __$getFromScope(\\"_regeneratorRuntime\\").async(function(_context){while(1)switch(_context.prev=_context.next){case 0:_i=0,_source=__$getFromScope(\\"source\\");case 1:if(!(_i<_source.length)){_context.next=4;break;}item=_source[_i];_context.next=2;return __$getFromScope(\\"_regeneratorRuntime\\").awrap(Promise.resolve(item));case 2:_context.next=3;return _context.sent;case 3:_i++;_context.next=1;break;case 4:case\\"end\\":return _context.stop();}},__$getFromScope(\\"_marked\\"),null,null,Promise);}" + })" + `); + }); + }); + + describe('Object Methods', () => { + it('should transform object method shorthand', () => { + const input = deindent` + const prefix = 'Hello'; + const obj = { + greet() { + return prefix + ' World'; + } + }; + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const prefix = 'Hello'; + const obj = { + greet: __STACK_SerializableJs.registerFunction(function () { + return prefix + ' World'; + }, { + scope: () => ({ + prefix + }), + source: "(function(){return __$getFromScope(\\"prefix\\")+' World';})" + }) + }" + `); + }); + + it('should transform object method with key-value syntax', () => { + const input = deindent` + const x = 1; + const obj = { + method: function() { return x; } + }; + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const x = 1; + const obj = { + method: __STACK_SerializableJs.registerFunction(function () { + return x; + }, { + scope: () => ({ + x + }), + source: "(function(){return __$getFromScope(\\"x\\");})" + }) + }" + `); + }); + + it('should transform object method with arrow function', () => { + const input = deindent` + const x = 1; + const obj = { + method: () => x + }; + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const x = 1; + const obj = { + method: __STACK_SerializableJs.registerFunction(() => x, { + scope: () => ({ + x + }), + source: "()=>__$getFromScope(\\"x\\")" + }) + }" + `); + }); + + it('should transform async object methods', () => { + const input = deindent` + const data = 'test'; + const obj = { + async fetch() { + return data; + } + }; + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const data = 'test'; + const obj = { + fetch: __STACK_SerializableJs.registerFunction(function () { + return _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee() { + return _regeneratorRuntime.wrap(function (_context) { + while (1) switch (_context.prev = _context.next) { + case 0: + return _context.abrupt("return", data); + case 1: + case "end": + return _context.stop(); + } + }, _callee); + }))(); + }, { + scope: () => ({ + _asyncToGenerator, + _regeneratorRuntime, + data + }), + source: "(function(){return __$getFromScope(\\"_asyncToGenerator\\")(__$getFromScope(\\"_regeneratorRuntime\\").mark(function _callee(){return __$getFromScope(\\"_regeneratorRuntime\\").wrap(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.abrupt(\\"return\\",__$getFromScope(\\"data\\"));case 1:case\\"end\\":return _context.stop();}},_callee);}))();})" + }) + }" + `); + }); + + it('should transform generator object methods', () => { + const input = deindent` + const max = 3; + const obj = { + *gen() { + for (let i = 0; i < max; i++) yield i; + } + }; + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const max = 3; + const obj = { + gen: __STACK_SerializableJs.registerFunction(function () { + return /*#__PURE__*/_regeneratorRuntime.mark(function _callee() { + var i; + return _regeneratorRuntime.wrap(function (_context) { + while (1) switch (_context.prev = _context.next) { + case 0: + i = 0; + case 1: + if (!(i < max)) { + _context.next = 3; + break; + } + _context.next = 2; + return i; + case 2: + i++; + _context.next = 1; + break; + case 3: + case "end": + return _context.stop(); + } + }, _callee); + })(); + }, { + scope: () => ({ + _regeneratorRuntime, + max + }), + source: "(function(){return __$getFromScope(\\"_regeneratorRuntime\\").mark(function _callee(){var i;return __$getFromScope(\\"_regeneratorRuntime\\").wrap(function(_context){while(1)switch(_context.prev=_context.next){case 0:i=0;case 1:if(!(i<__$getFromScope(\\"max\\"))){_context.next=3;break;}_context.next=2;return i;case 2:i++;_context.next=1;break;case 3:case\\"end\\":return _context.stop();}},_callee);})();})" + }) + }" + `); + }); + + it('should transform computed property methods', () => { + const input = deindent` + const key = 'method'; + const value = 42; + const obj = { + [key]() { + return value; + } + }; + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const key = 'method'; + const value = 42; + const obj = { + [key]: __STACK_SerializableJs.registerFunction(function () { + return value; + }, { + scope: () => ({ + key, + value + }), + source: "(function(){return __$getFromScope(\\"value\\");})" + }) + }" + `); + }); + + it('should transform getters', () => { + const input = deindent` + let value = 10; + const obj = { + get prop() { + return value; + } + }; + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + let value = 10; + const obj = __STACK_SerializableJs.registerFunction(() => { + const _obj = {}; + Object.defineProperty(_obj, "prop", { + get: __STACK_SerializableJs.registerFunction(function () { + return value; + }, { + scope: () => ({ + value + }), + source: "(function(){return __$getFromScope(\\"value\\");})" + }), + enumerable: true, + configurable: true + }); + return _obj; + }, { + scope: () => ({ + value + }), + source: "()=>{const _obj={};Object.defineProperty(_obj,\\"prop\\",{get:__STACK_SerializableJs.registerFunction(function(){return __$getFromScope(\\"value\\");},{scope:()=>({value:__$getFromScope(\\"value\\")}),source:\\"(function(){return __$getFromScope(\\\\\\"value\\\\\\");})\\"}),enumerable:true,configurable:true});return _obj;}" + })()" + `); + }); + + it('should transform setters', () => { + const input = deindent` + let value = 10; + const obj = { + set prop(v) { + value = v; + } + }; + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + let value = 10; + const obj = __STACK_SerializableJs.registerFunction(() => { + const _obj = {}; + Object.defineProperty(_obj, "prop", { + set: __STACK_SerializableJs.registerFunction(function (v) { + value = v; + }, { + scope: () => ({}), + source: "(function(v){value=v;})" + }), + enumerable: true, + configurable: true + }); + return _obj; + }, { + scope: () => ({}), + source: "()=>{const _obj={};Object.defineProperty(_obj,\\"prop\\",{set:__STACK_SerializableJs.registerFunction(function(v){value=v;},{scope:()=>({}),source:\\"(function(v){value=v;})\\"}),enumerable:true,configurable:true});return _obj;}" + })()" + `); + }); + + it('should transform both getters and setters', () => { + const input = deindent` + let value = 10; + const obj = { + get prop() { + return value; + }, + set prop(v) { + value = v; + } + }; + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + let value = 10; + const obj = __STACK_SerializableJs.registerFunction(() => { + const _obj = {}; + Object.defineProperty(_obj, "prop", { + get: __STACK_SerializableJs.registerFunction(function () { + return value; + }, { + scope: () => ({ + value + }), + source: "(function(){return __$getFromScope(\\"value\\");})" + }), + set: __STACK_SerializableJs.registerFunction(function (v) { + value = v; + }, { + scope: () => ({}), + source: "(function(v){value=v;})" + }), + enumerable: true, + configurable: true + }); + return _obj; + }, { + scope: () => ({ + value + }), + source: "()=>{const _obj={};Object.defineProperty(_obj,\\"prop\\",{get:__STACK_SerializableJs.registerFunction(function(){return __$getFromScope(\\"value\\");},{scope:()=>({value:__$getFromScope(\\"value\\")}),source:\\"(function(){return __$getFromScope(\\\\\\"value\\\\\\");})\\"}),set:__STACK_SerializableJs.registerFunction(function(v){value=v;},{scope:()=>({}),source:\\"(function(v){value=v;})\\"}),enumerable:true,configurable:true});return _obj;}" + })()" + `); + }); + }); + + describe('Class Methods', () => { + it('should transform class methods', () => { + const input = deindent` + const classVar = 'test'; + class MyClass { + method() { + return classVar; + } + } + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const classVar = 'test'; + class MyClass { + method() { + return classVar; + } + } + __STACK_SerializableJs.registerClass(MyClass, { + scope: () => ({ + classVar + }), + source: "class MyClass{method(){return __$getFromScope(\\"classVar\\");}}" + })" + `); + }); + + it('should transform class arrow function properties', () => { + const input = deindent` + const x = 1; + class MyClass { + method = () => x; + } + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const x = 1; + class MyClass { + method = __STACK_SerializableJs.registerFunction(() => x, { + scope: () => ({ + x + }), + source: "()=>__$getFromScope(\\"x\\")" + }); + } + __STACK_SerializableJs.registerClass(MyClass, { + scope: () => ({ + x + }), + source: "class MyClass{method=__STACK_SerializableJs.registerFunction(()=>__$getFromScope(\\"x\\"),{scope:()=>({x:__$getFromScope(\\"x\\")}),source:\\"()=>__$getFromScope(\\\\\\"x\\\\\\")\\"});}" + })" + `); + }); + + it('should transform static class methods', () => { + const input = deindent` + const staticVar = 'static'; + class MyClass { + static staticMethod() { + return staticVar; + } + } + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const staticVar = 'static'; + class MyClass { + static staticMethod() { + return staticVar; + } + } + __STACK_SerializableJs.registerClass(MyClass, { + scope: () => ({ + staticVar + }), + source: "class MyClass{static staticMethod(){return __$getFromScope(\\"staticVar\\");}}" + })" + `); + }); + + it('should transform async class methods', () => { + const input = deindent` + const data = 'async data'; + class MyClass { + async fetchData() { + return data; + } + } + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const data = 'async data'; + class MyClass { + fetchData() { + return _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(__STACK_SerializableJs.registerFunction(function _callee() { + return _regeneratorRuntime.wrap(__STACK_SerializableJs.registerFunction(function (_context) { + while (1) switch (_context.prev = _context.next) { + case 0: + return _context.abrupt("return", data); + case 1: + case "end": + return _context.stop(); + } + }, { + scope: () => ({ + data + }), + source: "(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.abrupt(\\"return\\",__$getFromScope(\\"data\\"));case 1:case\\"end\\":return _context.stop();}})" + }), _callee); + }, { + scope: () => ({ + _regeneratorRuntime, + data + }), + source: "function _callee(){return __$getFromScope(\\"_regeneratorRuntime\\").wrap(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.abrupt(\\"return\\",__$getFromScope(\\"data\\"));case 1:case\\"end\\":return _context.stop();}},{scope:()=>({data:__$getFromScope(\\"data\\")}),source:\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.abrupt(\\\\\\"return\\\\\\",__$getFromScope(\\\\\\"data\\\\\\"));case 1:case\\\\\\"end\\\\\\":return _context.stop();}})\\"}),_callee);}" + })))(); + } + } + __STACK_SerializableJs.registerClass(MyClass, { + scope: () => ({ + _asyncToGenerator, + _regeneratorRuntime, + data + }), + source: "class MyClass{fetchData(){return __$getFromScope(\\"_asyncToGenerator\\")(__$getFromScope(\\"_regeneratorRuntime\\").mark(__STACK_SerializableJs.registerFunction(function _callee(){return __$getFromScope(\\"_regeneratorRuntime\\").wrap(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.abrupt(\\"return\\",__$getFromScope(\\"data\\"));case 1:case\\"end\\":return _context.stop();}},{scope:()=>({data:__$getFromScope(\\"data\\")}),source:\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.abrupt(\\\\\\"return\\\\\\",__$getFromScope(\\\\\\"data\\\\\\"));case 1:case\\\\\\"end\\\\\\":return _context.stop();}})\\"}),_callee);},{scope:()=>({_regeneratorRuntime:__$getFromScope(\\"_regeneratorRuntime\\"),data:__$getFromScope(\\"data\\")}),source:\\"function _callee(){return __$getFromScope(\\\\\\"_regeneratorRuntime\\\\\\").wrap(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.abrupt(\\\\\\"return\\\\\\",__$getFromScope(\\\\\\"data\\\\\\"));case 1:case\\\\\\"end\\\\\\":return _context.stop();}},{scope:()=>({data:__$getFromScope(\\\\\\"data\\\\\\")}),source:\\\\\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.abrupt(\\\\\\\\\\\\\\"return\\\\\\\\\\\\\\",__$getFromScope(\\\\\\\\\\\\\\"data\\\\\\\\\\\\\\"));case 1:case\\\\\\\\\\\\\\"end\\\\\\\\\\\\\\":return _context.stop();}})\\\\\\"}),_callee);}\\"})))();}}" + })" + `); + }); + + it('should transform generator class methods', () => { + const input = deindent` + const items = [1, 2, 3]; + class MyClass { + *generator() { + yield* items; + } + } + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const items = [1, 2, 3]; + class MyClass { + generator() { + return /*#__PURE__*/_regeneratorRuntime.mark(__STACK_SerializableJs.registerFunction(function _callee() { + return _regeneratorRuntime.wrap(__STACK_SerializableJs.registerFunction(function (_context) { + while (1) switch (_context.prev = _context.next) { + case 0: + return _context.delegateYield(items, "t0", 1); + case 1: + case "end": + return _context.stop(); + } + }, { + scope: () => ({ + items + }), + source: "(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.delegateYield(__$getFromScope(\\"items\\"),\\"t0\\",1);case 1:case\\"end\\":return _context.stop();}})" + }), _callee); + }, { + scope: () => ({ + _regeneratorRuntime, + items + }), + source: "function _callee(){return __$getFromScope(\\"_regeneratorRuntime\\").wrap(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.delegateYield(__$getFromScope(\\"items\\"),\\"t0\\",1);case 1:case\\"end\\":return _context.stop();}},{scope:()=>({items:__$getFromScope(\\"items\\")}),source:\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.delegateYield(__$getFromScope(\\\\\\"items\\\\\\"),\\\\\\"t0\\\\\\",1);case 1:case\\\\\\"end\\\\\\":return _context.stop();}})\\"}),_callee);}" + }))(); + } + } + __STACK_SerializableJs.registerClass(MyClass, { + scope: () => ({ + _regeneratorRuntime, + items + }), + source: "class MyClass{generator(){return __$getFromScope(\\"_regeneratorRuntime\\").mark(__STACK_SerializableJs.registerFunction(function _callee(){return __$getFromScope(\\"_regeneratorRuntime\\").wrap(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.delegateYield(__$getFromScope(\\"items\\"),\\"t0\\",1);case 1:case\\"end\\":return _context.stop();}},{scope:()=>({items:__$getFromScope(\\"items\\")}),source:\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.delegateYield(__$getFromScope(\\\\\\"items\\\\\\"),\\\\\\"t0\\\\\\",1);case 1:case\\\\\\"end\\\\\\":return _context.stop();}})\\"}),_callee);},{scope:()=>({_regeneratorRuntime:__$getFromScope(\\"_regeneratorRuntime\\"),items:__$getFromScope(\\"items\\")}),source:\\"function _callee(){return __$getFromScope(\\\\\\"_regeneratorRuntime\\\\\\").wrap(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.delegateYield(__$getFromScope(\\\\\\"items\\\\\\"),\\\\\\"t0\\\\\\",1);case 1:case\\\\\\"end\\\\\\":return _context.stop();}},{scope:()=>({items:__$getFromScope(\\\\\\"items\\\\\\")}),source:\\\\\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.delegateYield(__$getFromScope(\\\\\\\\\\\\\\"items\\\\\\\\\\\\\\"),\\\\\\\\\\\\\\"t0\\\\\\\\\\\\\\",1);case 1:case\\\\\\\\\\\\\\"end\\\\\\\\\\\\\\":return _context.stop();}})\\\\\\"}),_callee);}\\"}))();}}" + })" + `); + }); + + it('should transform class getters', () => { + const input = deindent` + const store = { value: 42 }; + class MyClass { + get prop() { + return store.value; + } + } + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const store = { + value: 42 + }; + class MyClass { + get prop() { + return store.value; + } + } + __STACK_SerializableJs.registerClass(MyClass, { + scope: () => ({ + store + }), + source: "class MyClass{get prop(){return __$getFromScope(\\"store\\").value;}}" + })" + `); + }); + + it('should transform class setters', () => { + const input = deindent` + let stored = 0; + class MyClass { + set prop(v) { + stored = v; + } + } + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + let stored = 0; + class MyClass { + set prop(v) { + stored = v; + } + } + __STACK_SerializableJs.registerClass(MyClass, { + scope: () => ({}), + source: "class MyClass{set prop(v){stored=v;}}" + })" + `); + }); + + it('should transform private class methods', () => { + const input = deindent` + const secret = 'private'; + class MyClass { + #privateMethod() { + return secret; + } + } + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const secret = 'private'; + class MyClass { + #privateMethod() { + return secret; + } + } + __STACK_SerializableJs.registerClass(MyClass, { + scope: () => ({ + secret + }), + source: "class MyClass{#privateMethod(){return __$getFromScope(\\"secret\\");}}" + })" + `); + }); + + it('should handle constructors specially', () => { + const input = deindent` + const defaultValue = 100; + class MyClass { + constructor() { + this.value = defaultValue; + } + } + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const defaultValue = 100; + class MyClass { + constructor() { + this.value = defaultValue; + } + } + __STACK_SerializableJs.registerClass(MyClass, { + scope: () => ({ + defaultValue + }), + source: "class MyClass{constructor(){this.value=__$getFromScope(\\"defaultValue\\");}}" + })" + `); + }); + }); + + describe('Functions as Arguments', () => { + it('should transform functions passed as arguments', () => { + const input = deindent` + const x = 5; + arr.map(item => item * x); + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const x = 5; + arr.map(__STACK_SerializableJs.registerFunction(item => item * x, { + scope: () => ({ + x + }), + source: "item=>item*__$getFromScope(\\"x\\")" + }))" + `); + }); + + it('should transform functions in Promise chains', () => { + const input = deindent` + const multiplier = 2; + promise.then(value => value * multiplier); + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const multiplier = 2; + promise.then(__STACK_SerializableJs.registerFunction(value => value * multiplier, { + scope: () => ({ + multiplier + }), + source: "value=>value*__$getFromScope(\\"multiplier\\")" + }))" + `); + }); + + it('should transform IIFEs (Immediately Invoked Function Expressions)', () => { + const input = deindent` + const x = 10; + (function() { + console.log(x); + })(); + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const x = 10; + __STACK_SerializableJs.registerFunction(function () { + console.log(x); + }, { + scope: () => ({ + x + }), + source: "(function(){console.log(__$getFromScope(\\"x\\"));})" + })()" + `); + }); + + it('should transform IIFEs with arrow functions', () => { + const input = deindent` + const x = 10; + (() => { + console.log(x); + })(); + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const x = 10; + __STACK_SerializableJs.registerFunction(() => { + console.log(x); + }, { + scope: () => ({ + x + }), + source: "()=>{console.log(__$getFromScope(\\"x\\"));}" + })()" + `); + }); + }); + + describe('Nested Functions', () => { + it('should handle nested functions', () => { + const input = deindent` + const outer = 1; + const fn = () => { + const inner = 2; + return () => { + return outer + inner; + }; + }; + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const outer = 1; + const fn = __STACK_SerializableJs.registerFunction(() => { + const inner = 2; + return __STACK_SerializableJs.registerFunction(() => { + return outer + inner; + }, { + scope: () => ({ + outer, + inner + }), + source: "()=>{return __$getFromScope(\\"outer\\")+__$getFromScope(\\"inner\\");}" + }); + }, { + scope: () => ({ + outer + }), + source: "()=>{const inner=2;return __STACK_SerializableJs.registerFunction(()=>{return __$getFromScope(\\"outer\\")+inner;},{scope:()=>({outer:__$getFromScope(\\"outer\\"),inner}),source:\\"()=>{return __$getFromScope(\\\\\\"outer\\\\\\")+__$getFromScope(\\\\\\"inner\\\\\\");}\\"});}" + })" + `); + }); + + it('should handle deeply nested functions', () => { + const input = deindent` + const a = 1; + const fn1 = () => { + const b = 2; + return () => { + const c = 3; + return () => a + b + c; + }; + }; + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const a = 1; + const fn1 = __STACK_SerializableJs.registerFunction(() => { + const b = 2; + return __STACK_SerializableJs.registerFunction(() => { + const c = 3; + return __STACK_SerializableJs.registerFunction(() => a + b + c, { + scope: () => ({ + a, + b, + c + }), + source: "()=>__$getFromScope(\\"a\\")+__$getFromScope(\\"b\\")+__$getFromScope(\\"c\\")" + }); + }, { + scope: () => ({ + a, + b + }), + source: "()=>{const c=3;return __STACK_SerializableJs.registerFunction(()=>__$getFromScope(\\"a\\")+__$getFromScope(\\"b\\")+c,{scope:()=>({a:__$getFromScope(\\"a\\"),b:__$getFromScope(\\"b\\"),c}),source:\\"()=>__$getFromScope(\\\\\\"a\\\\\\")+__$getFromScope(\\\\\\"b\\\\\\")+__$getFromScope(\\\\\\"c\\\\\\")\\"});}" + }); + }, { + scope: () => ({ + a + }), + source: "()=>{const b=2;return __STACK_SerializableJs.registerFunction(()=>{const c=3;return __STACK_SerializableJs.registerFunction(()=>__$getFromScope(\\"a\\")+b+c,{scope:()=>({a:__$getFromScope(\\"a\\"),b,c}),source:\\"()=>__$getFromScope(\\\\\\"a\\\\\\")+__$getFromScope(\\\\\\"b\\\\\\")+__$getFromScope(\\\\\\"c\\\\\\")\\"});},{scope:()=>({a:__$getFromScope(\\"a\\"),b}),source:\\"()=>{const c=3;return __STACK_SerializableJs.registerFunction(()=>__$getFromScope(\\\\\\"a\\\\\\")+__$getFromScope(\\\\\\"b\\\\\\")+c,{scope:()=>({a:__$getFromScope(\\\\\\"a\\\\\\"),b:__$getFromScope(\\\\\\"b\\\\\\"),c}),source:\\\\\\"()=>__$getFromScope(\\\\\\\\\\\\\\"a\\\\\\\\\\\\\\")+__$getFromScope(\\\\\\\\\\\\\\"b\\\\\\\\\\\\\\")+__$getFromScope(\\\\\\\\\\\\\\"c\\\\\\\\\\\\\\")\\\\\\"});}\\"});}" + })" + `); + }); + }); + + describe('Edge Cases', () => { + it('should handle empty scope', () => { + const input = deindent` + () => { + console.log('no external vars'); + }; + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + __STACK_SerializableJs.registerFunction(() => { + console.log('no external vars'); + }, { + scope: () => ({}), + source: "()=>{console.log('no external vars');}" + })" + `); + }); + + it('should not capture function parameters in scope', () => { + const input = deindent` + const external = 5; + const fn = (param) => { + return param + external; + }; + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const external = 5; + const fn = __STACK_SerializableJs.registerFunction(param => { + return param + external; + }, { + scope: () => ({ + external + }), + source: "param=>{return param+__$getFromScope(\\"external\\");}" + })" + `); + }); + + it('should handle destructured parameters', () => { + const input = deindent` + const multiplier = 2; + const fn = ({x, y}) => (x + y) * multiplier; + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const multiplier = 2; + const fn = __STACK_SerializableJs.registerFunction(({ + x, + y + }) => (x + y) * multiplier, { + scope: () => ({ + multiplier + }), + source: "({x,y})=>(x+y)*__$getFromScope(\\"multiplier\\")" + })" + `); + }); + + it('should handle rest parameters', () => { + const input = deindent` + const base = 10; + const fn = (...args) => args.reduce((a, b) => a + b, base); + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const base = 10; + const fn = __STACK_SerializableJs.registerFunction((...args) => args.reduce(__STACK_SerializableJs.registerFunction((a, b) => a + b, { + scope: () => ({}), + source: "(a,b)=>a+b" + }), base), { + scope: () => ({ + base + }), + source: "(...args)=>args.reduce(__STACK_SerializableJs.registerFunction((a,b)=>a+b,{scope:()=>({}),source:\\"(a,b)=>a+b\\"}),__$getFromScope(\\"base\\"))" + })" + `); + }); + + it('should handle default parameters', () => { + const input = deindent` + const defaultValue = 5; + const fn = (x = defaultValue) => x * 2; + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const defaultValue = 5; + const fn = __STACK_SerializableJs.registerFunction((x = defaultValue) => x * 2, { + scope: () => ({ + defaultValue + }), + source: "(x=__$getFromScope(\\"defaultValue\\"))=>x*2" + })" + `); + }); + + it('should handle functions returning functions', () => { + const input = deindent` + const x = 1; + const factory = () => () => x; + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const x = 1; + const factory = __STACK_SerializableJs.registerFunction(() => __STACK_SerializableJs.registerFunction(() => x, { + scope: () => ({ + x + }), + source: "()=>__$getFromScope(\\"x\\")" + }), { + scope: () => ({ + x + }), + source: "()=>__STACK_SerializableJs.registerFunction(()=>__$getFromScope(\\"x\\"),{scope:()=>({x:__$getFromScope(\\"x\\")}),source:\\"()=>__$getFromScope(\\\\\\"x\\\\\\")\\"})" + })" + `); + }); + + it('should preserve function body content', () => { + const input = deindent` + const x = 1; + () => { + const y = 2; + return x + y; + }; + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const x = 1; + __STACK_SerializableJs.registerFunction(() => { + const y = 2; + return x + y; + }, { + scope: () => ({ + x + }), + source: "()=>{const y=2;return __$getFromScope(\\"x\\")+y;}" + })" + `); + }); + }); + + describe('Expression Wrapping with ensureSerializable', () => { + it('should wrap literal expressions when enabled', () => { + const input = deindent` + const x = 10; + const y = "hello"; + const z = true; + const arr = [1, 2, 3]; + const obj = { a: 1, b: 2 }; + `; + + const output = transpileJsToSerializableJs(input, { wrapExpressions: true }); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const x = __STACK_SerializableJs.ensureSerializable(10); + const y = __STACK_SerializableJs.ensureSerializable("hello"); + const z = __STACK_SerializableJs.ensureSerializable(true); + const arr = __STACK_SerializableJs.ensureSerializable([__STACK_SerializableJs.ensureSerializable(1), __STACK_SerializableJs.ensureSerializable(2), __STACK_SerializableJs.ensureSerializable(3)]); + const obj = __STACK_SerializableJs.ensureSerializable({ + a: __STACK_SerializableJs.ensureSerializable(1), + b: __STACK_SerializableJs.ensureSerializable(2) + })" + `); + }); + + it('should wrap binary expressions', () => { + const input = deindent` + const a = 5; + const b = 10; + const sum = a + b; + const product = a * b; + const comparison = a > b; + `; + + const output = transpileJsToSerializableJs(input, { wrapExpressions: true }); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const a = __STACK_SerializableJs.ensureSerializable(5); + const b = __STACK_SerializableJs.ensureSerializable(10); + const sum = __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(a) + __STACK_SerializableJs.ensureSerializable(b)); + const product = __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(a) * __STACK_SerializableJs.ensureSerializable(b)); + const comparison = __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(a) > __STACK_SerializableJs.ensureSerializable(b))" + `); + }); + + it('should wrap member expressions', () => { + const input = deindent` + const obj = { a: { b: { c: 1 } } }; + const value = obj.a.b.c; + const arr = [1, 2, 3]; + const elem = arr[1]; + `; + + const output = transpileJsToSerializableJs(input, { wrapExpressions: true }); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const obj = __STACK_SerializableJs.ensureSerializable({ + a: __STACK_SerializableJs.ensureSerializable({ + b: __STACK_SerializableJs.ensureSerializable({ + c: __STACK_SerializableJs.ensureSerializable(1) + }) + }) + }); + const value = __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(obj).a).b).c); + const arr = __STACK_SerializableJs.ensureSerializable([__STACK_SerializableJs.ensureSerializable(1), __STACK_SerializableJs.ensureSerializable(2), __STACK_SerializableJs.ensureSerializable(3)]); + const elem = __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(arr)[__STACK_SerializableJs.ensureSerializable(1)])" + `); + }); + + it('should wrap call expressions', () => { + const input = deindent` + function add(a, b) { + return a + b; + } + const result = add(5, 10); + const chained = add(add(1, 2), 3); + `; + + const output = transpileJsToSerializableJs(input, { wrapExpressions: true }); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + function add(a, b) { + return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(a) + __STACK_SerializableJs.ensureSerializable(b)); + } + __STACK_SerializableJs.registerFunction(add, { + scope: () => ({}), + source: "function add(a,b){return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(a)+__STACK_SerializableJs.ensureSerializable(b));}" + }); + const result = __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(add)(__STACK_SerializableJs.ensureSerializable(5), __STACK_SerializableJs.ensureSerializable(10))); + const chained = __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(add)(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(add)(__STACK_SerializableJs.ensureSerializable(1), __STACK_SerializableJs.ensureSerializable(2))), __STACK_SerializableJs.ensureSerializable(3)))" + `); + }); + + it('should not wrap update expressions', () => { + const input = deindent` + let count = 0; + count++; + count--; + ++count; + --count; + `; + + const output = transpileJsToSerializableJs(input, { wrapExpressions: true }); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + let count = __STACK_SerializableJs.ensureSerializable(0); + count++; + count--; + ++count; + --count" + `); + }); + + it('should not wrap assignment left-hand sides', () => { + const input = deindent` + let x; + x = 10; + let obj = {}; + obj.prop = 20; + obj['key'] = 30; + `; + + const output = transpileJsToSerializableJs(input, { wrapExpressions: true }); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + let x; + x = __STACK_SerializableJs.ensureSerializable(10); + let obj = __STACK_SerializableJs.ensureSerializable({}); + __STACK_SerializableJs.ensureSerializable(obj).prop = __STACK_SerializableJs.ensureSerializable(20); + __STACK_SerializableJs.ensureSerializable(obj)[__STACK_SerializableJs.ensureSerializable('key')] = __STACK_SerializableJs.ensureSerializable(30)" + `); + }); + + it('should wrap conditional expressions', () => { + const input = deindent` + const a = 5; + const b = 10; + const max = a > b ? a : b; + const nested = a > 0 ? (b > 0 ? a + b : a) : 0; + `; + + const output = transpileJsToSerializableJs(input, { wrapExpressions: true }); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const a = __STACK_SerializableJs.ensureSerializable(5); + const b = __STACK_SerializableJs.ensureSerializable(10); + const max = __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(a) > __STACK_SerializableJs.ensureSerializable(b)) ? __STACK_SerializableJs.ensureSerializable(a) : __STACK_SerializableJs.ensureSerializable(b)); + const nested = __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(a) > __STACK_SerializableJs.ensureSerializable(0)) ? __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(b) > __STACK_SerializableJs.ensureSerializable(0)) ? __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(a) + __STACK_SerializableJs.ensureSerializable(b)) : __STACK_SerializableJs.ensureSerializable(a)) : __STACK_SerializableJs.ensureSerializable(0))" + `); + }); + + it('should wrap function and class expressions with both ensureSerializable and register functions', () => { + const input = deindent` + const fn = function() { return 1; }; + const arrow = () => 2; + const asyncFn = async () => 3; + class MyClass { + method() { return 4; } + } + const ClassExpr = class CE { }; + `; + + const output = transpileJsToSerializableJs(input, { wrapExpressions: true }); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const fn = __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.registerFunction(function () { + return __STACK_SerializableJs.ensureSerializable(1); + }, { + scope: () => ({}), + source: "(function(){return __STACK_SerializableJs.ensureSerializable(1);})" + })); + const arrow = __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.registerFunction(() => __STACK_SerializableJs.ensureSerializable(2), { + scope: () => ({}), + source: "()=>__STACK_SerializableJs.ensureSerializable(2)" + })); + const asyncFn = /*#__PURE__*/__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.registerFunction(function () { + var _ref = __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_asyncToGenerator)(/*#__PURE__*/__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_regeneratorRuntime).mark)(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.registerFunction(function _callee() { + return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_regeneratorRuntime).wrap)(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.registerFunction(function (_context) { + while (__STACK_SerializableJs.ensureSerializable(1)) switch (__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).prev = __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).next))) { + case __STACK_SerializableJs.ensureSerializable(0): + return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).abrupt)(__STACK_SerializableJs.ensureSerializable("return"), __STACK_SerializableJs.ensureSerializable(3))); + case __STACK_SerializableJs.ensureSerializable(1): + case __STACK_SerializableJs.ensureSerializable("end"): + return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).stop)()); + } + }, { + scope: () => ({}), + source: "(function(_context){while(__STACK_SerializableJs.ensureSerializable(1))switch(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).prev=__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).next))){case __STACK_SerializableJs.ensureSerializable(0):return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).abrupt)(__STACK_SerializableJs.ensureSerializable(\\"return\\"),__STACK_SerializableJs.ensureSerializable(3)));case __STACK_SerializableJs.ensureSerializable(1):case __STACK_SerializableJs.ensureSerializable(\\"end\\"):return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).stop)());}})" + })), __STACK_SerializableJs.ensureSerializable(_callee))); + }, { + scope: () => ({ + _regeneratorRuntime + }), + source: "function _callee(){return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__$getFromScope(\\"_regeneratorRuntime\\")).wrap)(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.registerFunction(function(_context){while(__STACK_SerializableJs.ensureSerializable(1))switch(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).prev=__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).next))){case __STACK_SerializableJs.ensureSerializable(0):return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).abrupt)(__STACK_SerializableJs.ensureSerializable(\\"return\\"),__STACK_SerializableJs.ensureSerializable(3)));case __STACK_SerializableJs.ensureSerializable(1):case __STACK_SerializableJs.ensureSerializable(\\"end\\"):return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).stop)());}},{scope:()=>({}),source:\\"(function(_context){while(__STACK_SerializableJs.ensureSerializable(1))switch(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).prev=__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).next))){case __STACK_SerializableJs.ensureSerializable(0):return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).abrupt)(__STACK_SerializableJs.ensureSerializable(\\\\\\"return\\\\\\"),__STACK_SerializableJs.ensureSerializable(3)));case __STACK_SerializableJs.ensureSerializable(1):case __STACK_SerializableJs.ensureSerializable(\\\\\\"end\\\\\\"):return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).stop)());}})\\"})),__STACK_SerializableJs.ensureSerializable(_callee)));}" + })))))); + return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.registerFunction(function asyncFn() { + return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_ref).apply)(__STACK_SerializableJs.ensureSerializable(this), __STACK_SerializableJs.ensureSerializable(arguments))); + }, { + scope: () => ({ + _ref + }), + source: "function asyncFn(){return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__$getFromScope(\\"_ref\\")).apply)(__STACK_SerializableJs.ensureSerializable(this),__STACK_SerializableJs.ensureSerializable(arguments)));}" + })); + }, { + scope: () => ({ + _asyncToGenerator, + _regeneratorRuntime + }), + source: "(function(){var _ref=__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__$getFromScope(\\"_asyncToGenerator\\"))(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__$getFromScope(\\"_regeneratorRuntime\\")).mark)(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.registerFunction(function _callee(){return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__$getFromScope(\\"_regeneratorRuntime\\")).wrap)(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.registerFunction(function(_context){while(__STACK_SerializableJs.ensureSerializable(1))switch(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).prev=__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).next))){case __STACK_SerializableJs.ensureSerializable(0):return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).abrupt)(__STACK_SerializableJs.ensureSerializable(\\"return\\"),__STACK_SerializableJs.ensureSerializable(3)));case __STACK_SerializableJs.ensureSerializable(1):case __STACK_SerializableJs.ensureSerializable(\\"end\\"):return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).stop)());}},{scope:()=>({}),source:\\"(function(_context){while(__STACK_SerializableJs.ensureSerializable(1))switch(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).prev=__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).next))){case __STACK_SerializableJs.ensureSerializable(0):return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).abrupt)(__STACK_SerializableJs.ensureSerializable(\\\\\\"return\\\\\\"),__STACK_SerializableJs.ensureSerializable(3)));case __STACK_SerializableJs.ensureSerializable(1):case __STACK_SerializableJs.ensureSerializable(\\\\\\"end\\\\\\"):return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).stop)());}})\\"})),__STACK_SerializableJs.ensureSerializable(_callee)));},{scope:()=>({_regeneratorRuntime:__$getFromScope(\\"_regeneratorRuntime\\")}),source:\\"function _callee(){return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__$getFromScope(\\\\\\"_regeneratorRuntime\\\\\\")).wrap)(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.registerFunction(function(_context){while(__STACK_SerializableJs.ensureSerializable(1))switch(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).prev=__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).next))){case __STACK_SerializableJs.ensureSerializable(0):return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).abrupt)(__STACK_SerializableJs.ensureSerializable(\\\\\\"return\\\\\\"),__STACK_SerializableJs.ensureSerializable(3)));case __STACK_SerializableJs.ensureSerializable(1):case __STACK_SerializableJs.ensureSerializable(\\\\\\"end\\\\\\"):return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).stop)());}},{scope:()=>({}),source:\\\\\\"(function(_context){while(__STACK_SerializableJs.ensureSerializable(1))switch(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).prev=__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).next))){case __STACK_SerializableJs.ensureSerializable(0):return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).abrupt)(__STACK_SerializableJs.ensureSerializable(\\\\\\\\\\\\\\"return\\\\\\\\\\\\\\"),__STACK_SerializableJs.ensureSerializable(3)));case __STACK_SerializableJs.ensureSerializable(1):case __STACK_SerializableJs.ensureSerializable(\\\\\\\\\\\\\\"end\\\\\\\\\\\\\\"):return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).stop)());}})\\\\\\"})),__STACK_SerializableJs.ensureSerializable(_callee)));}\\"}))))));return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.registerFunction(function asyncFn(){return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_ref).apply)(__STACK_SerializableJs.ensureSerializable(this),__STACK_SerializableJs.ensureSerializable(arguments)));},{scope:()=>({_ref}),source:\\"function asyncFn(){return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__$getFromScope(\\\\\\"_ref\\\\\\")).apply)(__STACK_SerializableJs.ensureSerializable(this),__STACK_SerializableJs.ensureSerializable(arguments)));}\\"}));})" + }))()); + class MyClass { + method() { + return __STACK_SerializableJs.ensureSerializable(4); + } + } + __STACK_SerializableJs.registerClass(MyClass, { + scope: () => ({}), + source: "class MyClass{method(){return __STACK_SerializableJs.ensureSerializable(4);}}" + }); + const ClassExpr = __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.registerClass(class CE {}, { + scope: () => ({}), + source: "class CE{}" + }))" + `); + }); + + it('should not wrap function and class declarations (statements)', () => { + const input = deindent` + function myFunction() { return 1; } + class MyClass { } + const x = 10; + `; + + const output = transpileJsToSerializableJs(input, { wrapExpressions: true }); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + function myFunction() { + return __STACK_SerializableJs.ensureSerializable(1); + } + __STACK_SerializableJs.registerFunction(myFunction, { + scope: () => ({}), + source: "function myFunction(){return __STACK_SerializableJs.ensureSerializable(1);}" + }); + class MyClass {} + __STACK_SerializableJs.registerClass(MyClass, { + scope: () => ({}), + source: "class MyClass{}" + }); + const x = __STACK_SerializableJs.ensureSerializable(10)" + `); + }); + + it('should wrap expressions inside functions', () => { + const input = deindent` + const x = 10; + function calculate(a, b) { + const sum = a + b; + const product = a * b; + return sum > product ? sum : product; + } + `; + + const output = transpileJsToSerializableJs(input, { wrapExpressions: true }); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const x = __STACK_SerializableJs.ensureSerializable(10); + function calculate(a, b) { + const sum = __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(a) + __STACK_SerializableJs.ensureSerializable(b)); + const product = __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(a) * __STACK_SerializableJs.ensureSerializable(b)); + return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(sum) > __STACK_SerializableJs.ensureSerializable(product)) ? __STACK_SerializableJs.ensureSerializable(sum) : __STACK_SerializableJs.ensureSerializable(product)); + } + __STACK_SerializableJs.registerFunction(calculate, { + scope: () => ({}), + source: "function calculate(a,b){const sum=__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(a)+__STACK_SerializableJs.ensureSerializable(b));const product=__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(a)*__STACK_SerializableJs.ensureSerializable(b));return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(sum)>__STACK_SerializableJs.ensureSerializable(product))?__STACK_SerializableJs.ensureSerializable(sum):__STACK_SerializableJs.ensureSerializable(product));}" + })" + `); + }); + + it('should not wrap expressions when option is disabled', () => { + const input = deindent` + const x = 10; + const y = x + 5; + const obj = { a: 1 }; + const fn = () => x; + `; + + const output = transpileJsToSerializableJs(input, { wrapExpressions: false }); + expect(getUserCode(output)).toMatchInlineSnapshot(` + " + const x = 10; + const y = x + 5; + const obj = { + a: 1 + }; + const fn = __STACK_SerializableJs.registerFunction(() => x, { + scope: () => ({ + x + }), + source: "()=>__$getFromScope(\\"x\\")" + })" + `); + }); + + it('should not wrap expressions by default', () => { + const input = deindent` + const x = 10; + const y = x + 5; + `; + + const outputDefault = transpileJsToSerializableJs(input); + const outputExplicitFalse = transpileJsToSerializableJs(input, { wrapExpressions: false }); + + expect(getUserCode(outputDefault)).toBe(getUserCode(outputExplicitFalse)); + expect(getUserCode(outputDefault)).not.toContain('ensureSerializable'); + }); + }); +}); diff --git a/packages/stack-shared/src/helpers/js-serializer/transpiler.ts b/packages/stack-shared/src/helpers/js-serializer/transpiler.ts new file mode 100644 index 0000000000..f0f4852114 --- /dev/null +++ b/packages/stack-shared/src/helpers/js-serializer/transpiler.ts @@ -0,0 +1,897 @@ +import * as babel from '@babel/core'; +import generate from '@babel/generator'; +import * as t from '@babel/types'; +import { ASYNC_TO_GENERATOR_RUNTIME, REGENERATOR_RUNTIME } from './runtime-strings'; +import { getSerializationHelpers } from './serialization-helpers'; + +/** + * Bundle runtime helpers and replace imports with local references + */ +function bundleRuntimeHelpers(code: string): string { + // Start with just the code, we'll add runtime and markers + let modifiedCode = code; + let runtimeCode = ''; + + // Check if we need async helpers + const needsAsyncHelpers = code.includes('import _asyncToGenerator'); + + if (needsAsyncHelpers) { + // Remove the import statement + modifiedCode = modifiedCode.replace( + /import\s+_asyncToGenerator\s+from\s+["']@babel\/runtime\/helpers\/asyncToGenerator["'];?\s*/g, + '' + ); + + // Add helper to runtime section + runtimeCode += ASYNC_TO_GENERATOR_RUNTIME + '\n'; + } + + // Handle regenerator runtime import - replace with inline runtime + const needsRegenerator = modifiedCode.includes('import _regeneratorRuntime'); + if (needsRegenerator) { + modifiedCode = modifiedCode.replace( + /import\s+_regeneratorRuntime\s+from\s+["']@babel\/runtime\/regenerator["'];?\s*/g, + '' + ); + // Add regenerator runtime to runtime section + runtimeCode += `(() => { ${REGENERATOR_RUNTIME} })();\n\nconst _regeneratorRuntime = regeneratorRuntime;\n`; + } + + // Combine runtime and user code with clear marker + if (runtimeCode) { + modifiedCode = runtimeCode + "\n// USER_CODE_START\n\n" + modifiedCode; + } else { + modifiedCode = "// USER_CODE_START\n\n" + modifiedCode; + } + + return modifiedCode; +} + +/** + * Transform async functions to generators and generators to use regenerator runtime + * This combines both transformations for better optimization + */ +function transformAsyncAndGenerators(code: string): string { + const result = babel.transformSync(code, { + plugins: [ + ['@babel/plugin-transform-for-of'], + ['@babel/plugin-transform-async-to-generator'], + ['@babel/plugin-transform-regenerator', { + asyncGenerators: true, + generators: true, + async: true + }], + ['@babel/plugin-transform-runtime', { + regenerator: true, + helpers: true, + useESModules: false + }] + ], + parserOpts: { + sourceType: 'module', + plugins: ['jsx', 'typescript'] + }, + generatorOpts: { + retainLines: false, + compact: false + } + }); + + return result?.code || code; +} + +/** + * Transform objects with getters/setters to use Object.defineProperty + */ +function transformGettersSetters(code: string): string { + const plugin: babel.PluginObj = { + name: 'transform-getters-setters', + visitor: { + ObjectExpression: { + exit(path) { + // Check if this object has getters or setters + const hasGettersOrSetters = path.node.properties.some(prop => + t.isObjectMethod(prop) && (prop.kind === 'get' || prop.kind === 'set') + ); + + if (hasGettersOrSetters) { + transformObjectWithGettersSetters(path); + } + } + } + } + }; + + function transformObjectWithGettersSetters(path: babel.NodePath) { + const objectNode = path.node; + + // Group getters and setters by property name + const propertyDescriptors = new Map(); + const regularProperties: any[] = []; + + // First pass: categorize properties + for (const prop of objectNode.properties) { + if (t.isObjectMethod(prop) && (prop.kind === 'get' || prop.kind === 'set')) { + const keyName = getKeyName(prop.key); + if (!propertyDescriptors.has(keyName)) { + propertyDescriptors.set(keyName, { key: prop.key }); + } + const descriptor = propertyDescriptors.get(keyName)!; + if (prop.kind === 'get') { + descriptor.get = prop; + } else { + descriptor.set = prop; + } + } else if (t.isObjectMethod(prop)) { + // Transform regular methods to function expressions + const funcExpr = t.functionExpression( + null, + prop.params, + prop.body as t.BlockStatement, + prop.generator, + prop.async + ); + regularProperties.push( + t.objectProperty( + prop.key, + funcExpr, + prop.computed, + false + ) + ); + } else { + regularProperties.push(prop); + } + } + + // If no getters/setters, just process regular properties + if (propertyDescriptors.size === 0) { + return; + } + + // Create IIFE that builds the object with Object.defineProperty calls + const objIdentifier = t.identifier('_obj'); + const statements: t.Statement[] = []; + + // Create object with regular properties + statements.push( + t.variableDeclaration('const', [ + t.variableDeclarator( + objIdentifier, + t.objectExpression(regularProperties) + ) + ]) + ); + + // Add Object.defineProperty calls for each getter/setter + for (const [keyName, descriptor] of propertyDescriptors) { + const descriptorObj: t.ObjectProperty[] = []; + + if (descriptor.get) { + const funcExpr = t.functionExpression( + null, + descriptor.get.params, + descriptor.get.body as t.BlockStatement, + descriptor.get.generator, + descriptor.get.async + ); + descriptorObj.push( + t.objectProperty( + t.identifier('get'), + funcExpr + ) + ); + } + + if (descriptor.set) { + const funcExpr = t.functionExpression( + null, + descriptor.set.params, + descriptor.set.body as t.BlockStatement, + descriptor.set.generator, + descriptor.set.async + ); + descriptorObj.push( + t.objectProperty( + t.identifier('set'), + funcExpr + ) + ); + } + + // Add enumerable and configurable + descriptorObj.push( + t.objectProperty(t.identifier('enumerable'), t.booleanLiteral(true)), + t.objectProperty(t.identifier('configurable'), t.booleanLiteral(true)) + ); + + const keyArg = t.isIdentifier(descriptor.key) || t.isStringLiteral(descriptor.key) + ? t.stringLiteral(keyName) + : t.isPrivateName(descriptor.key) + ? t.stringLiteral(descriptor.key.id.name) + : descriptor.key as t.Expression; + + statements.push( + t.expressionStatement( + t.callExpression( + t.memberExpression( + t.identifier('Object'), + t.identifier('defineProperty') + ), + [ + objIdentifier, + keyArg, + t.objectExpression(descriptorObj) + ] + ) + ) + ); + } + + // Return the object + statements.push(t.returnStatement(objIdentifier)); + + // Create IIFE + const iife = t.callExpression( + t.arrowFunctionExpression( + [], + t.blockStatement(statements) + ), + [] + ); + + path.replaceWith(iife); + path.skip(); + } + + function getKeyName(key: t.Expression | t.Identifier | t.StringLiteral | t.NumericLiteral | t.BigIntLiteral | t.DecimalLiteral | t.PrivateName): string { + if (t.isIdentifier(key)) { + return key.name; + } else if (t.isStringLiteral(key)) { + return key.value; + } else if (t.isNumericLiteral(key)) { + return String(key.value); + } + // For computed or other key types, generate a unique name + return '__computed_' + Math.random().toString(36).substr(2, 9); + } + + const result = babel.transformSync(code, { + plugins: [plugin], + parserOpts: { + sourceType: 'module', + plugins: ['jsx', 'typescript'] + }, + generatorOpts: { + retainLines: false, + compact: false + } + }); + + return result?.code || code; +} + +/** + * Wrap functions with __registerFunction + */ +function wrapFunctions(code: string): string { + const plugin: babel.PluginObj = { + name: 'wrap-functions-with-register', + visitor: { + FunctionExpression: { + exit(path) { + // Skip if this is a scope lambda function in registerFunction/registerClass options + if (isScopeLambdaInRegisterCall(path)) { + return; + } + transformFunction(path); + } + }, + ArrowFunctionExpression: { + exit(path) { + // Skip if this is a scope lambda function in registerFunction/registerClass options + if (isScopeLambdaInRegisterCall(path)) { + return; + } + transformFunction(path); + } + }, + FunctionDeclaration(path) { + // Keep function declaration as-is and add registerFunction call after + const { node } = path; + if (node.id) { + // Collect scope variables for the function + const scopeVars = collectScopeVariables(path); + + // Generate the source string with scope variables replaced + const sourceString = generateSourceWithScopeReplaced(node, scopeVars); + + // Create the scope object + const scopeProperties = Array.from(scopeVars).map(varName => + t.objectProperty( + t.identifier(varName), + t.identifier(varName), + false, + true // shorthand + ) + ); + + const scopeObj = t.objectExpression(scopeProperties); + + // Wrap scope object in a lambda function + const scopeLambda = t.arrowFunctionExpression( + [], + scopeObj + ); + + // Create the options object with scope and source + const optionsProperties = [ + t.objectProperty( + t.identifier('scope'), + scopeLambda + ) + ]; + + // Add source property + optionsProperties.push( + t.objectProperty( + t.identifier('source'), + t.stringLiteral(sourceString) + ) + ); + + const optionsObj = t.objectExpression(optionsProperties); + + // Create a registerFunction call (no reassignment needed) + const registerCall = t.expressionStatement( + t.callExpression( + t.memberExpression( + t.identifier('__STACK_SerializableJs'), + t.identifier('registerFunction') + ), + [t.identifier(node.id.name), optionsObj] + ) + ); + + // Insert the registerFunction call after the function declaration + path.insertAfter(registerCall); + } + }, + ObjectMethod(path) { + // Transform regular object methods (not getters/setters) + const { node } = path; + if (node.kind === 'get' || node.kind === 'set') { + // Skip getters/setters - they're handled in separate step + return; + } + + const funcExpr = t.functionExpression( + null, + node.params, + node.body as t.BlockStatement, + node.generator, + node.async + ); + + const wrappedFunc = wrapWithRegisterFunction(funcExpr, path); + + path.replaceWith( + t.objectProperty( + node.key, + wrappedFunc, + node.computed, + false + ) + ); + path.skip(); + }, + ClassMethod(path) { + // Skip all class methods - they'll be handled as part of the class + // Getters, setters, constructors, and regular methods all stay as-is + return; + }, + ClassDeclaration: { + exit(path) { + transformClass(path); + } + }, + ClassExpression: { + exit(path) { + transformClass(path); + } + } + } + }; + + function isScopeLambdaInRegisterCall(path: babel.NodePath): boolean { + // Check if this is a scope property + const parent = path.parent; + if (!t.isObjectProperty(parent) || + !t.isIdentifier(parent.key) || + parent.key.name !== 'scope') { + return false; + } + + // Check if the parent object is the options argument to registerFunction/registerClass + const grandParent = path.parentPath?.parent; + if (!t.isObjectExpression(grandParent)) { + return false; + } + + // Check if this object is the second argument to a registerFunction/registerClass call + const greatGrandParent = path.parentPath?.parentPath?.parent; + if (!t.isCallExpression(greatGrandParent) || greatGrandParent.arguments[1] !== grandParent) { + return false; + } + + // Check if the call is to __STACK_SerializableJs.registerFunction or registerClass + if (!t.isMemberExpression(greatGrandParent.callee) || + !t.isIdentifier(greatGrandParent.callee.object) || + greatGrandParent.callee.object.name !== '__STACK_SerializableJs' || + !t.isIdentifier(greatGrandParent.callee.property) || + (greatGrandParent.callee.property.name !== 'registerFunction' && + greatGrandParent.callee.property.name !== 'registerClass')) { + return false; + } + + return true; + } + + function transformFunction(path: babel.NodePath) { + // Don't transform if already wrapped + if (t.isCallExpression(path.parent) && + t.isMemberExpression(path.parent.callee) && + t.isIdentifier(path.parent.callee.object) && + path.parent.callee.object.name === '__STACK_SerializableJs' && + t.isIdentifier(path.parent.callee.property) && + path.parent.callee.property.name === 'registerFunction') { + return; + } + + const wrapped = wrapWithRegisterFunction(path.node, path); + path.replaceWith(wrapped); + } + + // Remove the old transformObjectWithGettersSetters and getKeyName functions - they're now in transformGettersSetters + + function transformClass(path: babel.NodePath) { + const { node } = path; + + // Collect all variables in scope for the class + const scopeVars = collectScopeVariables(path); + + // Generate the source string with scope variables replaced + const sourceString = generateSourceWithScopeReplaced(node, scopeVars); + + // Create the scope object + const scopeProperties = Array.from(scopeVars).map(varName => + t.objectProperty( + t.identifier(varName), + t.identifier(varName), + false, + true // shorthand + ) + ); + + const scopeObj = t.objectExpression(scopeProperties); + + // Wrap scope object in a lambda function + const scopeLambda = t.arrowFunctionExpression( + [], + scopeObj + ); + + // Create the options object with scope and source + const optionsProperties = [ + t.objectProperty( + t.identifier('scope'), + scopeLambda + ) + ]; + + // Add source property + optionsProperties.push( + t.objectProperty( + t.identifier('source'), + t.stringLiteral(sourceString) + ) + ); + + const optionsObj = t.objectExpression(optionsProperties); + + if (t.isClassDeclaration(node)) { + // For class declarations, add a separate registerClass call after the class + const className = node.id; + if (!className) { + // Anonymous class declaration (shouldn't happen, but handle it) + return; + } + + // Create the registerClass call (no reassignment needed) + const registerCall = t.expressionStatement( + t.callExpression( + t.memberExpression( + t.identifier('__STACK_SerializableJs'), + t.identifier('registerClass') + ), + [t.identifier(className.name), optionsObj] + ) + ); + + // Insert the registerClass call after the class declaration + path.insertAfter(registerCall); + } else { + // For class expressions, wrap the class with registerClass + const wrappedClass = t.callExpression( + t.memberExpression( + t.identifier('__STACK_SerializableJs'), + t.identifier('registerClass') + ), + [node, optionsObj] + ); + + path.replaceWith(wrappedClass); + path.skip(); + } + } + + // Remove the old transformClassGettersSetters function content + // This function is no longer needed as we're not transforming getters/setters + function OLD_transformClassGettersSetters_REMOVED(path: babel.NodePath) { + // This function has been removed - classes are now handled by transformClass + + // Function content removed - handled in new transformClass above + } + + function wrapWithRegisterFunction( + func: t.FunctionExpression | t.ArrowFunctionExpression, + path: babel.NodePath + ): t.CallExpression { + // Collect all variables in scope + const scopeVars = collectScopeVariables(path); + + // Generate the source string with scope variables replaced + const sourceString = generateSourceWithScopeReplaced(func, scopeVars); + + // Create the scope object + const scopeProperties = Array.from(scopeVars).map(varName => + t.objectProperty( + t.identifier(varName), + t.identifier(varName), + false, + true // shorthand + ) + ); + + const scopeObj = t.objectExpression(scopeProperties); + + // Wrap scope object in a lambda function + const scopeLambda = t.arrowFunctionExpression( + [], + scopeObj + ); + + // Create the options object with scope and source + const optionsProperties = [ + t.objectProperty( + t.identifier('scope'), + scopeLambda + ) + ]; + + // Add source property + optionsProperties.push( + t.objectProperty( + t.identifier('source'), + t.stringLiteral(sourceString) + ) + ); + + const optionsObj = t.objectExpression(optionsProperties); + + // Create the __STACK_SerializableJs.registerFunction call + return t.callExpression( + t.memberExpression( + t.identifier('__STACK_SerializableJs'), + t.identifier('registerFunction') + ), + [func, optionsObj] + ); + } + + function collectScopeVariables(path: babel.NodePath): Set { + const scopeVars = new Set(); + const functionScope = path.scope; + + // Traverse the function to find all referenced identifiers + path.traverse({ + Identifier(idPath: any) { + const name = idPath.node.name; + + // Skip if it's not a referenced identifier + if (!idPath.isReferencedIdentifier()) { + return; + } + + // Get the binding for this identifier + const binding = idPath.scope.getBinding(name); + + if (!binding) { + // No binding found - might be a global + return; + } + + // Check if the binding is from outside this function's scope + // We want to capture variables that are: + // 1. Defined outside the function + // 2. Not parameters of this function + // 3. Not defined within this function + + // The binding.scope tells us where the variable was defined + // If it's not the same as or a child of our function scope, it's external + if (!isInScope(binding.scope, functionScope)) { + scopeVars.add(name); + } + } + }); + + return scopeVars; + } + + function isInScope(testScope: any, targetScope: any): boolean { + let current = testScope; + while (current) { + if (current === targetScope) { + return true; + } + current = current.parent; + } + return false; + } + + function generateSourceWithScopeReplaced( + func: t.FunctionExpression | t.ArrowFunctionExpression | t.FunctionDeclaration | t.ClassDeclaration | t.ClassExpression, + scopeVars: Set + ): string { + // Clone the function node to avoid mutating the original + const clonedFunc = t.cloneNode(func, true); + + // For anonymous function expressions, we need to wrap them in parentheses to make valid syntax + let codeToTransform: string; + const generateOptions = { + comments: false, // Don't include comments + compact: true, // Minimize whitespace + }; + + if (t.isFunctionExpression(clonedFunc) && !clonedFunc.id) { + // Wrap anonymous function expression in parentheses + codeToTransform = '(' + generate(clonedFunc, generateOptions).code + ')'; + } else { + codeToTransform = generate(clonedFunc, generateOptions).code; + } + + // Create a visitor to replace scope variable references + const replaceVisitor: babel.Visitor = { + Identifier(path) { + const name = path.node.name; + + // Only replace if it's a referenced identifier and in our scope vars + if (path.isReferencedIdentifier() && scopeVars.has(name)) { + // Check if the binding is local to this function + const binding = path.scope.getBinding(name); + + // If there's no binding or the binding is from outside, replace it + if (!binding || !isInScope(binding.scope, path.getFunctionParent()?.scope)) { + // Replace with __$getFromScope("varName") + path.replaceWith( + t.callExpression( + t.identifier('__$getFromScope'), + [t.stringLiteral(name)] + ) + ); + path.skip(); + } + } + } + }; + + // Transform the code + const result = babel.transformSync(codeToTransform, { + plugins: [ + () => ({ + visitor: replaceVisitor + }) + ], + parserOpts: { + sourceType: 'module', + plugins: ['jsx', 'typescript'] + }, + generatorOpts: { + retainLines: false, + compact: true, + } + }); + + // Remove trailing semicolon if present (since we're generating expressions, not statements) + let sourceCode = result?.code || ''; + if (sourceCode.endsWith(';')) { + sourceCode = sourceCode.slice(0, -1); + } + + return sourceCode; + } + + const result = babel.transformSync(code, { + plugins: [plugin], + parserOpts: { + sourceType: 'module', + plugins: ['jsx', 'typescript'] + }, + generatorOpts: { + retainLines: false, + compact: false + } + }); + + return result?.code || code; +} + + +/** + * Options for the transpiler + */ +export type TranspilerOptions = { + /** + * Whether to wrap all expressions with ensureSerializable + * This adds runtime checks but can impact performance + */ + wrapExpressions?: boolean, +} + +/** + * Wrap all expressions with __STACK_SerializableJs.ensureSerializable + */ +function wrapExpressionsWithEnsureSerializable(code: string): string { + const plugin: babel.PluginObj = { + name: 'wrap-expressions-with-ensure-serializable', + visitor: { + Expression: { + exit(path) { + const { node, parent } = path; + + // Skip if already wrapped + if (t.isCallExpression(node) && + t.isMemberExpression(node.callee) && + t.isIdentifier(node.callee.object) && + node.callee.object.name === '__STACK_SerializableJs' && + t.isIdentifier(node.callee.property) && + (node.callee.property.name === 'ensureSerializable' || + node.callee.property.name === 'registerFunction' || + node.callee.property.name === 'registerClass')) { + return; + } + + // Skip if parent is already a wrapper call + if (t.isCallExpression(parent) && + t.isMemberExpression(parent.callee) && + t.isIdentifier(parent.callee.object) && + parent.callee.object.name === '__STACK_SerializableJs') { + return; + } + + // Skip property keys (they can't be wrapped) + if (t.isObjectProperty(parent) && parent.key === node && !parent.computed) { + return; + } + if (t.isObjectMethod(parent) && parent.key === node && !parent.computed) { + return; + } + if (t.isMemberExpression(parent) && parent.property === node && !parent.computed) { + return; + } + if (t.isClassProperty(parent) && parent.key === node && !parent.computed) { + return; + } + if (t.isClassMethod(parent) && parent.key === node && !parent.computed) { + return; + } + + // Skip function and class names + if (t.isFunctionDeclaration(parent) && parent.id === node) { + return; + } + if (t.isFunctionExpression(parent) && parent.id === node) { + return; + } + if (t.isClassDeclaration(parent) && parent.id === node) { + return; + } + if (t.isClassExpression(parent) && parent.id === node) { + return; + } + + // Skip if this is a statement expression (like in ExpressionStatement) + // We only want to wrap actual expression values, not statement-level expressions + if (t.isExpressionStatement(parent) && parent.expression === node) { + return; + } + + // Skip if this is the argument of an update expression (++, --) + if (t.isUpdateExpression(parent) && parent.argument === node) { + return; + } + + // Skip if this is the left side of an assignment + if (t.isAssignmentExpression(parent) && parent.left === node) { + return; + } + + // Skip if this is a pattern (destructuring) + if (t.isPattern(node)) { + return; + } + + // Don't skip function expressions, arrow functions, or class expressions + // They should be wrapped with ensureSerializable AND registerFunction/registerClass + + // Wrap the expression + const wrapped = t.callExpression( + t.memberExpression( + t.identifier('__STACK_SerializableJs'), + t.identifier('ensureSerializable') + ), + [node] + ); + + path.replaceWith(wrapped); + path.skip(); + } + } + } + }; + + const result = babel.transformSync(code, { + plugins: [plugin], + parserOpts: { + sourceType: 'module', + plugins: ['jsx', 'typescript'] + }, + generatorOpts: { + retainLines: false, + compact: false + } + }); + + return result?.code || code; +} + +export function transpileJsToSerializableJs(js: string, options: TranspilerOptions = {}): string { + // Step 1: Transform async functions and generators to use regenerator runtime + let transformedCode = transformAsyncAndGenerators(js); + + // Step 2: Bundle runtime helpers and replace imports + transformedCode = bundleRuntimeHelpers(transformedCode); + + // Step 3: Transform getters/setters to use Object.defineProperty + transformedCode = transformGettersSetters(transformedCode); + + // Step 4 (Optional): Wrap all expressions with ensureSerializable + // This happens before wrapping functions so that the wrapped expressions + // are also captured in the function registrations + if (options.wrapExpressions) { + transformedCode = wrapExpressionsWithEnsureSerializable(transformedCode); + } + + // Step 5: Wrap functions with register functions + // This happens after bundling so the bundled helpers also get wrapped + transformedCode = wrapFunctions(transformedCode); + + // Step 6: Prepend custom serialization helpers + transformedCode = getSerializationHelpers() + "\n\n" + transformedCode; + + return transformedCode; +} diff --git a/packages/stack-shared/tsconfig.json b/packages/stack-shared/tsconfig.json index df0bb61e92..db000fa80d 100644 --- a/packages/stack-shared/tsconfig.json +++ b/packages/stack-shared/tsconfig.json @@ -17,6 +17,7 @@ "vitest/importMeta" ] }, + "allowJs": true, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b75bd06d65..2466999246 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -170,7 +170,7 @@ importers: version: 6.12.0(@opentelemetry/api@1.9.0) '@sentry/nextjs': specifier: ^8.40.0 - version: 8.40.0(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)(webpack@5.92.0(@swc/core@1.3.101)(esbuild@0.24.2)) + version: 8.40.0(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.4.1(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)(webpack@5.92.0(@swc/core@1.3.101)(esbuild@0.24.2)) '@simplewebauthn/server': specifier: ^11.0.0 version: 11.0.0(encoding@0.1.13) @@ -200,7 +200,7 @@ importers: version: 7.4.1 freestyle-sandboxes: specifier: ^0.0.92 - version: 0.0.92(encoding@0.1.13)(expo-constants@17.1.7(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(expo-linking@7.0.5(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react-native-safe-area-context@5.5.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native-screens@4.11.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(ws@8.18.3) + version: 0.0.92(encoding@0.1.13)(expo-constants@17.1.7(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(expo-linking@7.0.5(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react-native-safe-area-context@5.5.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native-screens@4.11.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(ws@8.18.3) jose: specifier: ^5.2.2 version: 5.4.0 @@ -209,7 +209,7 @@ importers: version: 1.0.6 next: specifier: 15.4.1 - version: 15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 15.4.1(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) nodemailer: specifier: ^6.9.10 version: 6.9.13 @@ -345,7 +345,7 @@ importers: version: 2.0.2(react@19.0.0) '@sentry/nextjs': specifier: ^8.40.0 - version: 8.40.0(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)(webpack@5.92.0(@swc/core@1.3.101)(esbuild@0.24.2)) + version: 8.40.0(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.4.1(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)(webpack@5.92.0(@swc/core@1.3.101)(esbuild@0.24.2)) '@stackframe/stack': specifier: workspace:* version: link:../../packages/stack @@ -372,10 +372,10 @@ importers: version: 8.20.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@vercel/analytics': specifier: ^1.2.2 - version: 1.3.1(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0) + version: 1.3.1(next@15.4.1(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0) '@vercel/speed-insights': specifier: ^1.0.12 - version: 1.0.12(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0) + version: 1.0.12(next@15.4.1(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0) canvas-confetti: specifier: ^1.9.2 version: 1.9.3 @@ -387,7 +387,7 @@ importers: version: 7.4.1 geist: specifier: ^1 - version: 1.3.0(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)) + version: 1.3.0(next@15.4.1(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)) jose: specifier: ^5.2.2 version: 5.6.3 @@ -399,10 +399,10 @@ importers: version: 0.508.0(react@19.0.0) next: specifier: 15.4.1 - version: 15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 15.4.1(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) next-themes: specifier: ^0.2.1 - version: 0.2.1(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 0.2.1(next@15.4.1(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0) posthog-js: specifier: ^1.235.0 version: 1.235.4 @@ -435,7 +435,7 @@ importers: version: 1.32.0(encoding@0.1.13) svix-react: specifier: ^1.13.0 - version: 1.13.0(@babel/core@7.26.0)(prop-types@15.8.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(svix@1.32.0(encoding@0.1.13)) + version: 1.13.0(@babel/core@7.28.3)(prop-types@15.8.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(svix@1.32.0(encoding@0.1.13)) tailwind-merge: specifier: ^2.3.0 version: 2.3.0 @@ -582,7 +582,7 @@ importers: version: link:../packages/stack-shared '@vercel/mcp-adapter': specifier: ^1.0.0 - version: 1.0.0(@modelcontextprotocol/sdk@1.17.2)(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) + version: 1.0.0(@modelcontextprotocol/sdk@1.17.2)(next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) '@xyflow/react': specifier: ^12.6.4 version: 12.7.0(@types/react@18.3.12)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -594,19 +594,19 @@ importers: version: 0.7.1 fumadocs-core: specifier: 15.3.3 - version: 15.3.3(@types/react@18.3.12)(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 15.3.3(@types/react@18.3.12)(next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) fumadocs-mdx: specifier: 11.6.4 - version: 11.6.4(@fumadocs/mdx-remote@1.4.0(@types/react@18.3.12)(acorn@8.14.0)(fumadocs-core@15.3.3(@types/react@18.3.12)(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1))(acorn@8.14.0)(fumadocs-core@15.3.3(@types/react@18.3.12)(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) + version: 11.6.4(@fumadocs/mdx-remote@1.4.0(@types/react@18.3.12)(acorn@8.14.0)(fumadocs-core@15.3.3(@types/react@18.3.12)(next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1))(acorn@8.14.0)(fumadocs-core@15.3.3(@types/react@18.3.12)(next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) fumadocs-openapi: specifier: ^8.1.12 - version: 8.1.12(@types/react-dom@18.3.1)(@types/react@18.3.12)(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@4.1.9) + version: 8.1.12(@types/react-dom@18.3.1)(@types/react@18.3.12)(next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@4.1.9) fumadocs-typescript: specifier: ^4.0.5 version: 4.0.5(typescript@5.8.3) fumadocs-ui: specifier: 15.3.3 - version: 15.3.3(@types/react-dom@18.3.1)(@types/react@18.3.12)(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@4.1.9) + version: 15.3.3(@types/react-dom@18.3.1)(@types/react@18.3.12)(next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@4.1.9) js-yaml: specifier: ^4.1.0 version: 4.1.0 @@ -621,7 +621,7 @@ importers: version: 10.0.1 next: specifier: 15.4.1 - version: 15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -700,7 +700,7 @@ importers: version: link:../../packages/stack next: specifier: ^14.1 - version: 14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 14.2.15(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: specifier: ^18 version: 18.3.1 @@ -752,7 +752,7 @@ importers: version: link:../../packages/stack-ui next: specifier: 15.4.1 - version: 15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) next-themes: specifier: ^0.4.3 version: 0.4.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -807,10 +807,10 @@ importers: version: link:../../packages/stack-ui next: specifier: ^14.1 - version: 14.2.3(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 14.2.3(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-themes: specifier: ^0.2.1 - version: 0.2.1(next@14.2.3(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 0.2.1(next@14.2.3(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: specifier: ^18.2 version: 18.3.1 @@ -847,7 +847,7 @@ importers: version: link:../../packages/stack next: specifier: 14.2.5 - version: 14.2.5(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 14.2.5(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: specifier: ^18 version: 18.3.1 @@ -899,7 +899,7 @@ importers: version: link:../../packages/stack next: specifier: ^14.2 - version: 14.2.3(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 14.2.3(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: specifier: ^18 version: 18.3.1 @@ -933,7 +933,7 @@ importers: version: link:../../packages/stack next: specifier: 14.3.0-canary.26 - version: 14.3.0-canary.26(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 14.3.0-canary.26(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: specifier: ^18 version: 18.3.1 @@ -1025,7 +1025,7 @@ importers: version: 5.6.3 next: specifier: ^14.2.5 - version: 14.2.5(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 14.2.5(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: specifier: ^18.3.0 version: 18.3.1 @@ -1127,7 +1127,7 @@ importers: devDependencies: '@quetzallabs/i18n': specifier: ^0.1.19 - version: 0.1.19(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@18.3.1))(react@18.3.1)) + version: 0.1.19(next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@18.3.1))(react@18.3.1)) '@types/color': specifier: ^3.0.6 version: 3.0.6 @@ -1242,7 +1242,7 @@ importers: devDependencies: '@quetzallabs/i18n': specifier: ^0.1.19 - version: 0.1.19(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) + version: 0.1.19(next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) '@types/color': specifier: ^3.0.6 version: 3.0.6 @@ -1366,7 +1366,7 @@ importers: devDependencies: '@quetzallabs/i18n': specifier: ^0.1.19 - version: 0.1.19(next@14.2.5(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.2.0))(react@18.2.0)) + version: 0.1.19(next@14.2.5(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.2.0))(react@18.2.0)) '@types/color': specifier: ^3.0.6 version: 3.0.6 @@ -1399,7 +1399,7 @@ importers: version: 9.0.2 next: specifier: ^14.1.0 - version: 14.2.5(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.2.0))(react@18.2.0) + version: 14.2.5(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.2.0))(react@18.2.0) postcss: specifier: ^8.4.38 version: 8.4.47 @@ -1430,7 +1430,7 @@ importers: devDependencies: next: specifier: ^14.1.0 - version: 14.2.5(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 14.2.5(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) react: specifier: ^18.2 version: 18.2.0 @@ -1443,6 +1443,33 @@ importers: packages/stack-shared: dependencies: + '@babel/core': + specifier: ^7.28.3 + version: 7.28.3 + '@babel/generator': + specifier: ^7.28.3 + version: 7.28.3 + '@babel/plugin-transform-async-to-generator': + specifier: ^7.27.1 + version: 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-for-of': + specifier: ^7.27.1 + version: 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-regenerator': + specifier: ^7.28.3 + version: 7.28.3(@babel/core@7.28.3) + '@babel/plugin-transform-runtime': + specifier: ^7.28.3 + version: 7.28.3(@babel/core@7.28.3) + '@babel/preset-env': + specifier: ^7.28.3 + version: 7.28.3(@babel/core@7.28.3) + '@babel/runtime': + specifier: ^7.28.3 + version: 7.28.3 + '@babel/types': + specifier: ^7.28.2 + version: 7.28.2 '@opentelemetry/api': specifier: ^1.9.0 version: 1.9.0 @@ -1479,6 +1506,9 @@ importers: oauth4webapi: specifier: ^2.10.3 version: 2.10.4 + regenerator-runtime: + specifier: ^0.14.1 + version: 0.14.1 semver: specifier: ^7.6.3 version: 7.6.3 @@ -1491,10 +1521,19 @@ importers: devDependencies: '@sentry/nextjs': specifier: ^8.40.0 - version: 8.40.0(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0)(webpack@5.92.0(@swc/core@1.3.101)(esbuild@0.24.2)) + version: 8.40.0(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.4.1(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0)(webpack@5.92.0(@swc/core@1.3.101)(esbuild@0.24.2)) '@simplewebauthn/types': specifier: ^11.0.0 version: 11.0.0 + '@types/babel-generator': + specifier: ^6.25.8 + version: 6.25.8 + '@types/babel__core': + specifier: ^7.20.5 + version: 7.20.5 + '@types/babel__generator': + specifier: ^7.27.0 + version: 7.27.0 '@types/bcryptjs': specifier: ^3.0.0 version: 3.0.0 @@ -1739,7 +1778,7 @@ importers: devDependencies: '@quetzallabs/i18n': specifier: ^0.1.19 - version: 0.1.19(next@14.2.5(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) + version: 0.1.19(next@14.2.5(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) '@types/color': specifier: ^3.0.6 version: 3.0.6 @@ -1772,7 +1811,7 @@ importers: version: 9.0.2 next: specifier: ^14.1.0 - version: 14.2.5(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 14.2.5(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) postcss: specifier: ^8.4.38 version: 8.4.47 @@ -2071,36 +2110,20 @@ packages: resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.26.2': - resolution: {integrity: sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==} - engines: {node: '>=6.9.0'} - '@babel/compat-data@7.28.0': resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==} engines: {node: '>=6.9.0'} - '@babel/core@7.26.0': - resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} - engines: {node: '>=6.9.0'} - - '@babel/core@7.26.9': - resolution: {integrity: sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==} - engines: {node: '>=6.9.0'} - '@babel/core@7.28.0': resolution: {integrity: sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==} engines: {node: '>=6.9.0'} - '@babel/generator@7.26.2': - resolution: {integrity: sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==} + '@babel/core@7.28.3': + resolution: {integrity: sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==} engines: {node: '>=6.9.0'} - '@babel/generator@7.26.9': - resolution: {integrity: sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==} - engines: {node: '>=6.9.0'} - - '@babel/generator@7.28.0': - resolution: {integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==} + '@babel/generator@7.28.3': + resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} engines: {node: '>=6.9.0'} '@babel/helper-annotate-as-pure@7.24.7': @@ -2111,10 +2134,6 @@ packages: resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.25.9': - resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} - engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.27.2': resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} engines: {node: '>=6.9.0'} @@ -2131,6 +2150,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-create-class-features-plugin@7.28.3': + resolution: {integrity: sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/helper-create-regexp-features-plugin@7.27.1': resolution: {integrity: sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==} engines: {node: '>=6.9.0'} @@ -2154,26 +2179,18 @@ packages: resolution: {integrity: sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==} engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.24.7': - resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-imports@7.25.9': - resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} - engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.27.1': resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.26.0': - resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} + '@babel/helper-module-transforms@7.27.3': + resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-module-transforms@7.27.3': - resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==} + '@babel/helper-module-transforms@7.28.3': + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -2186,14 +2203,6 @@ packages: resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} engines: {node: '>=6.9.0'} - '@babel/helper-plugin-utils@7.24.8': - resolution: {integrity: sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-plugin-utils@7.25.9': - resolution: {integrity: sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==} - engines: {node: '>=6.9.0'} - '@babel/helper-plugin-utils@7.27.1': resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} engines: {node: '>=6.9.0'} @@ -2216,42 +2225,18 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-skip-transparent-expression-wrappers@7.24.7': - resolution: {integrity: sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==} - engines: {node: '>=6.9.0'} - '@babel/helper-skip-transparent-expression-wrappers@7.27.1': resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.24.7': - resolution: {integrity: sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.25.9': - resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} - engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.24.7': - resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.25.9': - resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} - engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.27.1': resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@7.25.9': - resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} - engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@7.27.1': resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} @@ -2260,18 +2245,14 @@ packages: resolution: {integrity: sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.26.0': - resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} - engines: {node: '>=6.9.0'} - - '@babel/helpers@7.26.9': - resolution: {integrity: sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==} - engines: {node: '>=6.9.0'} - '@babel/helpers@7.27.6': resolution: {integrity: sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==} engines: {node: '>=6.9.0'} + '@babel/helpers@7.28.3': + resolution: {integrity: sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==} + engines: {node: '>=6.9.0'} + '@babel/highlight@7.24.7': resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} engines: {node: '>=6.9.0'} @@ -2285,20 +2266,45 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/parser@7.26.2': - resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==} + '@babel/parser@7.28.0': + resolution: {integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/parser@7.26.9': - resolution: {integrity: sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==} + '@babel/parser@7.28.3': + resolution: {integrity: sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/parser@7.28.0': - resolution: {integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==} - engines: {node: '>=6.0.0'} - hasBin: true + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1': + resolution: {integrity: sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1': + resolution: {integrity: sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1': + resolution: {integrity: sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1': + resolution: {integrity: sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.13.0 + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.3': + resolution: {integrity: sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 '@babel/plugin-proposal-decorators@7.28.0': resolution: {integrity: sha512-zOiZqvANjWDUaUS9xMxbMcK/Zccztbe/6ikvUXaG9nsPH3w6qh5UaPGAnirI/WhIbZ8m3OHU0ReyPrknG+ZKeg==} @@ -2312,6 +2318,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2': + resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-proposal-private-property-in-object@7.21.11': resolution: {integrity: sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==} engines: {node: '>=6.9.0'} @@ -2363,6 +2375,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-import-assertions@7.27.1': + resolution: {integrity: sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-import-attributes@7.27.1': resolution: {integrity: sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==} engines: {node: '>=6.9.0'} @@ -2433,6 +2451,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-unicode-sets-regex@7.18.6': + resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/plugin-transform-arrow-functions@7.27.1': resolution: {integrity: sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==} engines: {node: '>=6.9.0'} @@ -2451,6 +2475,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-block-scoped-functions@7.27.1': + resolution: {integrity: sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-block-scoping@7.28.0': resolution: {integrity: sha512-gKKnwjpdx5sER/wl0WN0efUBFzF/56YZO0RJrSYP4CljXnP31ByY7fol89AzomdlLNzI36AvOTmYHsnZTCkq8Q==} engines: {node: '>=6.9.0'} @@ -2463,8 +2493,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-classes@7.28.0': - resolution: {integrity: sha512-IjM1IoJNw72AZFlj33Cu8X0q2XK/6AaVC3jQu+cgQ5lThWD5ajnuUAml80dqRmOhmPkTH8uAwnpMu9Rvj0LTRA==} + '@babel/plugin-transform-class-static-block@7.28.3': + resolution: {integrity: sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.12.0 + + '@babel/plugin-transform-classes@7.28.3': + resolution: {integrity: sha512-DoEWC5SuxuARF2KdKmGUq3ghfPMO6ZzR12Dnp5gubwbeWJo4dbNWXJPVlwvh4Zlq6Z7YVvL8VFxeSOJgjsx4Sg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -2481,6 +2517,42 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-dotall-regex@7.27.1': + resolution: {integrity: sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-keys@7.27.1': + resolution: {integrity: sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.27.1': + resolution: {integrity: sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-dynamic-import@7.27.1': + resolution: {integrity: sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-explicit-resource-management@7.28.0': + resolution: {integrity: sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-exponentiation-operator@7.27.1': + resolution: {integrity: sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-export-namespace-from@7.27.1': resolution: {integrity: sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==} engines: {node: '>=6.9.0'} @@ -2505,6 +2577,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-json-strings@7.27.1': + resolution: {integrity: sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-literals@7.27.1': resolution: {integrity: sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==} engines: {node: '>=6.9.0'} @@ -2517,18 +2595,48 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-member-expression-literals@7.27.1': + resolution: {integrity: sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-amd@7.27.1': + resolution: {integrity: sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-modules-commonjs@7.27.1': resolution: {integrity: sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-modules-systemjs@7.27.1': + resolution: {integrity: sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-umd@7.27.1': + resolution: {integrity: sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-named-capturing-groups-regex@7.27.1': resolution: {integrity: sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 + '@babel/plugin-transform-new-target@7.27.1': + resolution: {integrity: sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-nullish-coalescing-operator@7.27.1': resolution: {integrity: sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==} engines: {node: '>=6.9.0'} @@ -2547,6 +2655,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-object-super@7.27.1': + resolution: {integrity: sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-optional-catch-binding@7.27.1': resolution: {integrity: sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==} engines: {node: '>=6.9.0'} @@ -2577,6 +2691,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-property-literals@7.27.1': + resolution: {integrity: sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-display-name@7.28.0': resolution: {integrity: sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==} engines: {node: '>=6.9.0'} @@ -2625,14 +2745,26 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-regenerator@7.28.1': - resolution: {integrity: sha512-P0QiV/taaa3kXpLY+sXla5zec4E+4t4Aqc9ggHlfZ7a2cp8/x/Gv08jfwEtn9gnnYIMvHx6aoOZ8XJL8eU71Dg==} + '@babel/plugin-transform-regenerator@7.28.3': + resolution: {integrity: sha512-K3/M/a4+ESb5LEldjQb+XSrpY0nF+ZBFlTCbSnKaYAMfD8v33O6PMs4uYnOk19HlcsI8WMu3McdFPTiQHF/1/A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regexp-modifiers@7.27.1': + resolution: {integrity: sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-reserved-words@7.27.1': + resolution: {integrity: sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-runtime@7.28.0': - resolution: {integrity: sha512-dGopk9nZrtCs2+nfIem25UuHyt5moSJamArzIoh9/vezUQPmYDOzjaHDCkAzuGJibCIkPup8rMT2+wYB6S73cA==} + '@babel/plugin-transform-runtime@7.28.3': + resolution: {integrity: sha512-Y6ab1kGqZ0u42Zv/4a7l0l72n9DKP/MKoKWaUSBylrhNZO2prYuqFOLbn5aW5SIFXwSH93yfjbgllL8lxuGKLg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -2655,18 +2787,59 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-template-literals@7.27.1': + resolution: {integrity: sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typeof-symbol@7.27.1': + resolution: {integrity: sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-typescript@7.28.0': resolution: {integrity: sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-unicode-escapes@7.27.1': + resolution: {integrity: sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-property-regex@7.27.1': + resolution: {integrity: sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-unicode-regex@7.27.1': resolution: {integrity: sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-unicode-sets-regex@7.27.1': + resolution: {integrity: sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/preset-env@7.28.3': + resolution: {integrity: sha512-ROiDcM+GbYVPYBOeCR6uBXKkQpBExLl8k9HO1ygXEyds39j+vCCsjmj7S8GOniZQlEs81QlkdJZe76IpLSiqpg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-modules@0.1.6-no-external-plugins': + resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} + peerDependencies: + '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 + '@babel/preset-react@7.27.1': resolution: {integrity: sha512-oJHWh2gLhU9dW9HHr42q0cI0/iHHXTLGe39qvpAZZzagHy0MzYLCnCVV0symeRvzmjHyVU7mw2K06E6u/JwbhA==} engines: {node: '>=6.9.0'} @@ -2679,28 +2852,16 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/runtime@7.24.7': - resolution: {integrity: sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==} - engines: {node: '>=6.9.0'} - - '@babel/runtime@7.26.0': - resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==} - engines: {node: '>=6.9.0'} - '@babel/runtime@7.27.6': resolution: {integrity: sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==} engines: {node: '>=6.9.0'} - '@babel/template@7.25.0': - resolution: {integrity: sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==} - engines: {node: '>=6.9.0'} - - '@babel/template@7.25.9': - resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} + '@babel/runtime@7.28.3': + resolution: {integrity: sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==} engines: {node: '>=6.9.0'} - '@babel/template@7.26.9': - resolution: {integrity: sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==} + '@babel/template@7.25.0': + resolution: {integrity: sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==} engines: {node: '>=6.9.0'} '@babel/template@7.27.2': @@ -2711,38 +2872,22 @@ packages: resolution: {integrity: sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.25.9': - resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==} - engines: {node: '>=6.9.0'} - - '@babel/traverse@7.26.9': - resolution: {integrity: sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==} - engines: {node: '>=6.9.0'} - '@babel/traverse@7.28.0': resolution: {integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==} engines: {node: '>=6.9.0'} - '@babel/types@7.24.7': - resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.26.0': - resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.26.9': - resolution: {integrity: sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.28.0': - resolution: {integrity: sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==} + '@babel/traverse@7.28.3': + resolution: {integrity: sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==} engines: {node: '>=6.9.0'} '@babel/types@7.28.1': resolution: {integrity: sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==} engines: {node: '>=6.9.0'} + '@babel/types@7.28.2': + resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} + engines: {node: '>=6.9.0'} + '@braintree/sanitize-url@7.1.1': resolution: {integrity: sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==} @@ -4380,9 +4525,6 @@ packages: '@jridgewell/sourcemap-codec@1.5.0': resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - '@jridgewell/trace-mapping@0.3.29': resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} @@ -7276,18 +7418,21 @@ packages: '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + '@types/babel-generator@6.25.8': + resolution: {integrity: sha512-f5l89J0UpYhTE6TFCxy3X+8pJVru1eig1fcvF9qHmOk9h1VxZimd+++tu5GShntCOdhE/MoZZ0SlpGTyh4XrKg==} + + '@types/babel-types@7.0.16': + resolution: {integrity: sha512-5QXs9GBFTNTmilLlWBhnsprqpjfrotyrnzUdwDrywEL/DA4LuCWQT300BTOXA3Y9ngT9F2uvmCoIxI6z8DlJEA==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} - '@types/babel__generator@7.6.8': - resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} '@types/babel__template@7.4.4': resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} - '@types/babel__traverse@7.20.6': - resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} - '@types/babel__traverse@7.20.7': resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==} @@ -14510,10 +14655,6 @@ packages: tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} - to-fast-properties@2.0.0: - resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} - engines: {node: '>=4'} - to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -15630,8 +15771,8 @@ snapshots: '@ampproject/remapping@2.3.0': dependencies: - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/gen-mapping': 0.3.12 + '@jridgewell/trace-mapping': 0.3.29 '@antfu/install-pkg@1.1.0': dependencies: @@ -16199,7 +16340,7 @@ snapshots: '@babel/code-frame@7.26.2': dependencies: - '@babel/helper-validator-identifier': 7.25.9 + '@babel/helper-validator-identifier': 7.27.1 js-tokens: 4.0.0 picocolors: 1.1.1 @@ -16209,62 +16350,40 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.26.2': {} - '@babel/compat-data@7.28.0': {} - '@babel/core@7.26.0': - dependencies: - '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.26.2 - '@babel/generator': 7.26.2 - '@babel/helper-compilation-targets': 7.25.9 - '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) - '@babel/helpers': 7.26.0 - '@babel/parser': 7.26.2 - '@babel/template': 7.25.9 - '@babel/traverse': 7.25.9 - '@babel/types': 7.26.0 - convert-source-map: 2.0.0 - debug: 4.4.0 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/core@7.26.9': + '@babel/core@7.28.0': dependencies: '@ampproject/remapping': 2.3.0 '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.0 + '@babel/generator': 7.28.3 '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.27.3(@babel/core@7.26.9) - '@babel/helpers': 7.26.9 + '@babel/helper-module-transforms': 7.27.3(@babel/core@7.28.0) + '@babel/helpers': 7.27.6 '@babel/parser': 7.28.0 '@babel/template': 7.27.2 '@babel/traverse': 7.28.0 - '@babel/types': 7.28.0 + '@babel/types': 7.28.1 convert-source-map: 2.0.0 - debug: 4.4.1 + debug: 4.4.0 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/core@7.28.0': + '@babel/core@7.28.3': dependencies: '@ampproject/remapping': 2.3.0 '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.0 + '@babel/generator': 7.28.3 '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.27.3(@babel/core@7.28.0) - '@babel/helpers': 7.27.6 - '@babel/parser': 7.28.0 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.3) + '@babel/helpers': 7.28.3 + '@babel/parser': 7.28.3 '@babel/template': 7.27.2 - '@babel/traverse': 7.28.0 - '@babel/types': 7.28.1 + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 convert-source-map: 2.0.0 debug: 4.4.1 gensync: 1.0.0-beta.2 @@ -16273,45 +16392,21 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.26.2': - dependencies: - '@babel/parser': 7.26.2 - '@babel/types': 7.26.0 - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 - jsesc: 3.0.2 - - '@babel/generator@7.26.9': + '@babel/generator@7.28.3': dependencies: - '@babel/parser': 7.26.9 - '@babel/types': 7.26.9 - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 - jsesc: 3.0.2 - - '@babel/generator@7.28.0': - dependencies: - '@babel/parser': 7.28.0 - '@babel/types': 7.28.0 + '@babel/parser': 7.28.3 + '@babel/types': 7.28.2 '@jridgewell/gen-mapping': 0.3.12 '@jridgewell/trace-mapping': 0.3.29 jsesc: 3.0.2 '@babel/helper-annotate-as-pure@7.24.7': dependencies: - '@babel/types': 7.26.0 + '@babel/types': 7.28.2 '@babel/helper-annotate-as-pure@7.27.3': dependencies: - '@babel/types': 7.28.1 - - '@babel/helper-compilation-targets@7.25.9': - dependencies: - '@babel/compat-data': 7.26.2 - '@babel/helper-validator-option': 7.25.9 - browserslist: 4.24.4 - lru-cache: 5.1.1 - semver: 6.3.1 + '@babel/types': 7.28.2 '@babel/helper-compilation-targets@7.27.2': dependencies: @@ -16321,42 +16416,55 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.25.0(@babel/core@7.26.0)': + '@babel/helper-create-class-features-plugin@7.25.0(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-annotate-as-pure': 7.24.7 '@babel/helper-member-expression-to-functions': 7.24.8 '@babel/helper-optimise-call-expression': 7.24.7 - '@babel/helper-replace-supers': 7.25.0(@babel/core@7.26.0) - '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 - '@babel/traverse': 7.26.9 + '@babel/helper-replace-supers': 7.25.0(@babel/core@7.28.3) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.28.3 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/helper-create-class-features-plugin@7.27.1(@babel/core@7.26.0)': + '@babel/helper-create-class-features-plugin@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-member-expression-to-functions': 7.27.1 '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/helper-replace-supers': 7.27.1(@babel/core@7.26.0) + '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.3) '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/traverse': 7.28.0 + '@babel/traverse': 7.28.3 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/helper-create-regexp-features-plugin@7.27.1(@babel/core@7.26.0)': + '@babel/helper-create-class-features-plugin@7.28.3(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.27.1 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.3) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.28.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-create-regexp-features-plugin@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 '@babel/helper-annotate-as-pure': 7.27.3 regexpu-core: 6.2.0 semver: 6.3.1 - '@babel/helper-define-polyfill-provider@0.6.5(@babel/core@7.26.0)': + '@babel/helper-define-polyfill-provider@0.6.5(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-plugin-utils': 7.27.1 debug: 4.4.1 @@ -16369,168 +16477,110 @@ snapshots: '@babel/helper-member-expression-to-functions@7.24.8': dependencies: - '@babel/traverse': 7.26.9 - '@babel/types': 7.26.9 + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 transitivePeerDependencies: - supports-color '@babel/helper-member-expression-to-functions@7.27.1': dependencies: - '@babel/traverse': 7.28.0 - '@babel/types': 7.28.1 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-imports@7.24.7': - dependencies: - '@babel/traverse': 7.26.9 - '@babel/types': 7.24.7 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-imports@7.25.9': - dependencies: - '@babel/traverse': 7.26.9 - '@babel/types': 7.26.9 + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 transitivePeerDependencies: - supports-color '@babel/helper-module-imports@7.27.1': dependencies: - '@babel/traverse': 7.28.0 - '@babel/types': 7.28.0 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0)': - dependencies: - '@babel/core': 7.26.0 - '@babel/helper-module-imports': 7.25.9 - '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.27.3(@babel/core@7.26.0)': - dependencies: - '@babel/core': 7.26.0 - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.28.0 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-transforms@7.27.3(@babel/core@7.26.9)': + '@babel/helper-module-transforms@7.27.3(@babel/core@7.28.0)': dependencies: - '@babel/core': 7.26.9 + '@babel/core': 7.28.0 '@babel/helper-module-imports': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 '@babel/traverse': 7.28.0 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.27.3(@babel/core@7.28.0)': + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.3 '@babel/helper-module-imports': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.28.0 + '@babel/traverse': 7.28.3 transitivePeerDependencies: - supports-color '@babel/helper-optimise-call-expression@7.24.7': dependencies: - '@babel/types': 7.26.9 + '@babel/types': 7.28.2 '@babel/helper-optimise-call-expression@7.27.1': dependencies: - '@babel/types': 7.28.1 - - '@babel/helper-plugin-utils@7.24.8': {} - - '@babel/helper-plugin-utils@7.25.9': {} + '@babel/types': 7.28.2 '@babel/helper-plugin-utils@7.27.1': {} - '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.26.0)': + '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-wrap-function': 7.27.1 - '@babel/traverse': 7.28.0 + '@babel/traverse': 7.28.3 transitivePeerDependencies: - supports-color - '@babel/helper-replace-supers@7.25.0(@babel/core@7.26.0)': + '@babel/helper-replace-supers@7.25.0(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-member-expression-to-functions': 7.24.8 '@babel/helper-optimise-call-expression': 7.24.7 - '@babel/traverse': 7.26.9 + '@babel/traverse': 7.28.3 transitivePeerDependencies: - supports-color - '@babel/helper-replace-supers@7.27.1(@babel/core@7.26.0)': + '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-member-expression-to-functions': 7.27.1 '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/traverse': 7.28.0 - transitivePeerDependencies: - - supports-color - - '@babel/helper-skip-transparent-expression-wrappers@7.24.7': - dependencies: - '@babel/traverse': 7.26.9 - '@babel/types': 7.26.9 + '@babel/traverse': 7.28.3 transitivePeerDependencies: - supports-color '@babel/helper-skip-transparent-expression-wrappers@7.27.1': dependencies: - '@babel/traverse': 7.28.0 - '@babel/types': 7.28.1 + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 transitivePeerDependencies: - supports-color - '@babel/helper-string-parser@7.24.7': {} - - '@babel/helper-string-parser@7.25.9': {} - '@babel/helper-string-parser@7.27.1': {} - '@babel/helper-validator-identifier@7.24.7': {} - - '@babel/helper-validator-identifier@7.25.9': {} - '@babel/helper-validator-identifier@7.27.1': {} - '@babel/helper-validator-option@7.25.9': {} - '@babel/helper-validator-option@7.27.1': {} '@babel/helper-wrap-function@7.27.1': dependencies: '@babel/template': 7.27.2 - '@babel/traverse': 7.28.0 - '@babel/types': 7.28.1 + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 transitivePeerDependencies: - supports-color - '@babel/helpers@7.26.0': - dependencies: - '@babel/template': 7.25.9 - '@babel/types': 7.26.0 - - '@babel/helpers@7.26.9': + '@babel/helpers@7.27.6': dependencies: '@babel/template': 7.27.2 - '@babel/types': 7.28.0 + '@babel/types': 7.28.2 - '@babel/helpers@7.27.6': + '@babel/helpers@7.28.3': dependencies: '@babel/template': 7.27.2 - '@babel/types': 7.28.1 + '@babel/types': 7.28.2 '@babel/highlight@7.24.7': dependencies: @@ -16548,557 +16598,770 @@ snapshots: '@babel/parser@7.25.6': dependencies: - '@babel/types': 7.26.0 + '@babel/types': 7.28.2 - '@babel/parser@7.26.2': + '@babel/parser@7.28.0': dependencies: - '@babel/types': 7.26.0 + '@babel/types': 7.28.2 - '@babel/parser@7.26.9': + '@babel/parser@7.28.3': dependencies: - '@babel/types': 7.26.9 + '@babel/types': 7.28.2 - '@babel/parser@7.28.0': + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/traverse': 7.28.3 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-transform-optional-chaining': 7.27.1(@babel/core@7.28.3) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.3(@babel/core@7.28.3)': dependencies: - '@babel/types': 7.28.0 + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/traverse': 7.28.3 + transitivePeerDependencies: + - supports-color - '@babel/plugin-proposal-decorators@7.28.0(@babel/core@7.26.0)': + '@babel/plugin-proposal-decorators@7.28.0(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.26.0) + '@babel/core': 7.28.3 + '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.3) '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-decorators': 7.27.1(@babel/core@7.26.0) + '@babel/plugin-syntax-decorators': 7.27.1(@babel/core@7.28.3) transitivePeerDependencies: - supports-color - '@babel/plugin-proposal-export-default-from@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-proposal-export-default-from@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-proposal-private-property-in-object@7.21.11(@babel/core@7.26.0)': + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 + + '@babel/plugin-proposal-private-property-in-object@7.21.11(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 '@babel/helper-annotate-as-pure': 7.24.7 - '@babel/helper-create-class-features-plugin': 7.25.0(@babel/core@7.26.0) - '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.26.0) + '@babel/helper-create-class-features-plugin': 7.25.0(@babel/core@7.28.3) + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.3) transitivePeerDependencies: - supports-color - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.26.0)': + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-decorators@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.26.0)': + '@babel/plugin-syntax-export-default-from@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.26.0)': + '@babel/plugin-syntax-flow@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.26.0)': + '@babel/plugin-syntax-import-assertions@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-decorators@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.26.0)': + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-export-default-from@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-flow@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.26.0)': + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.26.0)': + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.26.0)': + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.26.0)': + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.26.0)': + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.26.0)': + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.26.0)': + '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.26.0)': + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.3) + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-async-generator-functions@7.28.0(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.3) + '@babel/traverse': 7.28.3 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-async-to-generator@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.3) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-block-scoping@7.28.0(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-class-properties@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.28.3) + '@babel/helper-plugin-utils': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-class-static-block@7.28.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.3) + '@babel/helper-plugin-utils': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-classes@7.28.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-globals': 7.28.0 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.3) + '@babel/traverse': 7.28.3 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-computed-properties@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/template': 7.27.2 + + '@babel/plugin-transform-destructuring@7.28.0(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/traverse': 7.28.3 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-dotall-regex@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 + '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.3) '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.26.0)': + '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.26.0)': + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 + '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.3) '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-explicit-resource-management@7.28.0(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-transform-destructuring': 7.28.0(@babel/core@7.28.3) + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-async-generator-functions@7.28.0(@babel/core@7.26.0)': + '@babel/plugin-transform-exponentiation-operator@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.26.0) - '@babel/traverse': 7.28.0 - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-async-to-generator@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-module-imports': 7.27.1 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.26.0) - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-block-scoping@7.28.0(@babel/core@7.26.0)': + '@babel/plugin-transform-flow-strip-types@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-flow': 7.27.1(@babel/core@7.28.3) - '@babel/plugin-transform-class-properties@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.26.0) + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-classes@7.28.0(@babel/core@7.26.0)': + '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/core': 7.28.3 '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-globals': 7.28.0 '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-replace-supers': 7.27.1(@babel/core@7.26.0) - '@babel/traverse': 7.28.0 + '@babel/traverse': 7.28.3 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-computed-properties@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-json-strings@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/template': 7.27.2 - '@babel/plugin-transform-destructuring@7.28.0(@babel/core@7.26.0)': + '@babel/plugin-transform-literals@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.28.0 - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-logical-assignment-operators@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-flow-strip-types@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-flow': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.3) '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-compilation-targets': 7.27.2 + '@babel/core': 7.28.3 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.3) '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.28.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-literals@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-modules-systemjs@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.3) '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.28.3 + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-logical-assignment-operators@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.3) '@babel/helper-plugin-utils': 7.27.1 + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-named-capturing-groups-regex@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-module-transforms': 7.27.3(@babel/core@7.26.0) + '@babel/core': 7.28.3 + '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.3) '@babel/helper-plugin-utils': 7.27.1 - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-named-capturing-groups-regex@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.26.0) + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-nullish-coalescing-operator@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-nullish-coalescing-operator@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-numeric-separator@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-numeric-separator@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-object-rest-spread@7.28.0(@babel/core@7.26.0)': + '@babel/plugin-transform-object-rest-spread@7.28.0(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-destructuring': 7.28.0(@babel/core@7.26.0) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.26.0) - '@babel/traverse': 7.28.0 + '@babel/plugin-transform-destructuring': 7.28.0(@babel/core@7.28.3) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.3) + '@babel/traverse': 7.28.3 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.3) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-optional-catch-binding@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-optional-catch-binding@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-optional-chaining@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-optional-chaining@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.26.0)': + '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-private-methods@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-private-methods@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.26.0) + '@babel/core': 7.28.3 + '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.28.3) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-private-property-in-object@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-private-property-in-object@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.26.0) + '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.28.3) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-display-name@7.28.0(@babel/core@7.26.0)': + '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-react-display-name@7.28.0(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 - '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.26.0) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.0)': - dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.9)': + '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.9 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.28.3 + '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.28.3) + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.28.0)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.0 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.9)': + '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.28.0)': dependencies: - '@babel/core': 7.26.9 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.28.0 + '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-module-imports': 7.27.1 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.26.0) - '@babel/types': 7.28.1 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.3) + '@babel/types': 7.28.2 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-pure-annotations@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-react-pure-annotations@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-regenerator@7.28.1(@babel/core@7.26.0)': + '@babel/plugin-transform-regenerator@7.28.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-regexp-modifiers@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.3) + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-runtime@7.28.0(@babel/core@7.26.0)': + '@babel/plugin-transform-runtime@7.28.3(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-module-imports': 7.27.1 '@babel/helper-plugin-utils': 7.27.1 - babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.26.0) - babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.26.0) - babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.26.0) + babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.28.3) + babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.28.3) + babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.28.3) semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-spread@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-spread@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-typescript@7.28.0(@babel/core@7.26.0)': + '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-typescript@7.28.0(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.26.0) + '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.3) '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.26.0) + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.3) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-unicode-property-regex@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.3) + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.26.0) + '@babel/core': 7.28.3 + '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.3) '@babel/helper-plugin-utils': 7.27.1 - '@babel/preset-react@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-unicode-sets-regex@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 + '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.3) + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/preset-env@7.28.3(@babel/core@7.28.3)': + dependencies: + '@babel/compat-data': 7.28.0 + '@babel/core': 7.28.3 + '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.26.0) - '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-react-pure-annotations': 7.27.1(@babel/core@7.26.0) + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.3(@babel/core@7.28.3) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.3) + '@babel/plugin-syntax-import-assertions': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.28.3) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-async-generator-functions': 7.28.0(@babel/core@7.28.3) + '@babel/plugin-transform-async-to-generator': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-block-scoping': 7.28.0(@babel/core@7.28.3) + '@babel/plugin-transform-class-properties': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-class-static-block': 7.28.3(@babel/core@7.28.3) + '@babel/plugin-transform-classes': 7.28.3(@babel/core@7.28.3) + '@babel/plugin-transform-computed-properties': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-destructuring': 7.28.0(@babel/core@7.28.3) + '@babel/plugin-transform-dotall-regex': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-explicit-resource-management': 7.28.0(@babel/core@7.28.3) + '@babel/plugin-transform-exponentiation-operator': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-json-strings': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-logical-assignment-operators': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-modules-systemjs': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-named-capturing-groups-regex': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-nullish-coalescing-operator': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-numeric-separator': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-object-rest-spread': 7.28.0(@babel/core@7.28.3) + '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-optional-catch-binding': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-optional-chaining': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.3) + '@babel/plugin-transform-private-methods': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-private-property-in-object': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-regenerator': 7.28.3(@babel/core@7.28.3) + '@babel/plugin-transform-regexp-modifiers': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-spread': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-unicode-property-regex': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-unicode-sets-regex': 7.27.1(@babel/core@7.28.3) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.28.3) + babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.28.3) + babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.28.3) + babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.28.3) + core-js-compat: 3.44.0 + semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/preset-typescript@7.27.1(@babel/core@7.26.0)': + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/types': 7.28.2 + esutils: 2.0.3 + + '@babel/preset-react@7.27.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.26.0) + '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.28.3) + '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-react-pure-annotations': 7.27.1(@babel/core@7.28.3) transitivePeerDependencies: - supports-color - '@babel/runtime@7.24.7': - dependencies: - regenerator-runtime: 0.14.1 - - '@babel/runtime@7.26.0': + '@babel/preset-typescript@7.27.1(@babel/core@7.28.3)': dependencies: - regenerator-runtime: 0.14.1 + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.3) + transitivePeerDependencies: + - supports-color '@babel/runtime@7.27.6': {} - '@babel/template@7.25.0': - dependencies: - '@babel/code-frame': 7.26.2 - '@babel/parser': 7.26.9 - '@babel/types': 7.26.0 - - '@babel/template@7.25.9': - dependencies: - '@babel/code-frame': 7.26.2 - '@babel/parser': 7.26.2 - '@babel/types': 7.26.0 + '@babel/runtime@7.28.3': {} - '@babel/template@7.26.9': + '@babel/template@7.25.0': dependencies: - '@babel/code-frame': 7.26.2 - '@babel/parser': 7.26.9 - '@babel/types': 7.26.9 + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.3 + '@babel/types': 7.28.2 '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.0 - '@babel/types': 7.28.0 + '@babel/parser': 7.28.3 + '@babel/types': 7.28.2 '@babel/traverse@7.25.6': dependencies: '@babel/code-frame': 7.24.7 - '@babel/generator': 7.26.2 - '@babel/parser': 7.26.9 + '@babel/generator': 7.28.3 + '@babel/parser': 7.28.3 '@babel/template': 7.25.0 - '@babel/types': 7.26.0 - debug: 4.4.0 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - - '@babel/traverse@7.25.9': - dependencies: - '@babel/code-frame': 7.26.2 - '@babel/generator': 7.26.9 - '@babel/parser': 7.26.9 - '@babel/template': 7.26.9 - '@babel/types': 7.26.9 + '@babel/types': 7.28.2 debug: 4.4.0 globals: 11.12.0 transitivePeerDependencies: - supports-color - '@babel/traverse@7.26.9': + '@babel/traverse@7.28.0': dependencies: - '@babel/code-frame': 7.26.2 - '@babel/generator': 7.26.9 - '@babel/parser': 7.26.9 - '@babel/template': 7.26.9 - '@babel/types': 7.26.9 - debug: 4.4.0 - globals: 11.12.0 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.3 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.3 + '@babel/template': 7.27.2 + '@babel/types': 7.28.2 + debug: 4.4.1 transitivePeerDependencies: - supports-color - '@babel/traverse@7.28.0': + '@babel/traverse@7.28.3': dependencies: '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.0 + '@babel/generator': 7.28.3 '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.0 + '@babel/parser': 7.28.3 '@babel/template': 7.27.2 - '@babel/types': 7.28.0 + '@babel/types': 7.28.2 debug: 4.4.1 transitivePeerDependencies: - supports-color - '@babel/types@7.24.7': - dependencies: - '@babel/helper-string-parser': 7.24.7 - '@babel/helper-validator-identifier': 7.24.7 - to-fast-properties: 2.0.0 - - '@babel/types@7.26.0': - dependencies: - '@babel/helper-string-parser': 7.25.9 - '@babel/helper-validator-identifier': 7.25.9 - - '@babel/types@7.26.9': - dependencies: - '@babel/helper-string-parser': 7.25.9 - '@babel/helper-validator-identifier': 7.25.9 - - '@babel/types@7.28.0': + '@babel/types@7.28.1': dependencies: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 - '@babel/types@7.28.1': + '@babel/types@7.28.2': dependencies: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 @@ -17305,8 +17568,8 @@ snapshots: '@emotion/babel-plugin@11.11.0': dependencies: - '@babel/helper-module-imports': 7.24.7 - '@babel/runtime': 7.26.0 + '@babel/helper-module-imports': 7.27.1 + '@babel/runtime': 7.27.6 '@emotion/hash': 0.9.1 '@emotion/memoize': 0.8.1 '@emotion/serialize': 1.1.4 @@ -17321,8 +17584,8 @@ snapshots: '@emotion/babel-plugin@11.12.0': dependencies: - '@babel/helper-module-imports': 7.25.9 - '@babel/runtime': 7.26.0 + '@babel/helper-module-imports': 7.27.1 + '@babel/runtime': 7.27.6 '@emotion/hash': 0.9.2 '@emotion/memoize': 0.9.0 '@emotion/serialize': 1.3.2 @@ -17369,7 +17632,7 @@ snapshots: '@emotion/react@11.11.4(@types/react@18.3.12)(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 + '@babel/runtime': 7.27.6 '@emotion/babel-plugin': 11.11.0 '@emotion/cache': 11.11.0 '@emotion/serialize': 1.1.4 @@ -17385,7 +17648,7 @@ snapshots: '@emotion/react@11.13.3(@types/react@18.3.12)(react@19.0.0)': dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.27.6 '@emotion/babel-plugin': 11.12.0 '@emotion/cache': 11.13.1 '@emotion/serialize': 1.3.2 @@ -17421,7 +17684,7 @@ snapshots: '@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.7 + '@babel/runtime': 7.27.6 '@emotion/babel-plugin': 11.11.0 '@emotion/is-prop-valid': 1.2.2 '@emotion/react': 11.11.4(@types/react@18.3.12)(react@18.3.1) @@ -17436,7 +17699,7 @@ snapshots: '@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@19.0.0))(@types/react@18.3.12)(react@19.0.0)': dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.27.6 '@emotion/babel-plugin': 11.12.0 '@emotion/is-prop-valid': 1.3.1 '@emotion/react': 11.13.3(@types/react@18.3.12)(react@19.0.0) @@ -17972,7 +18235,7 @@ snapshots: '@expo/cli@0.24.18': dependencies: '@0no-co/graphql.web': 1.1.2 - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.3 '@expo/code-signing-certificates': 0.0.5 '@expo/config': 11.0.13 '@expo/config-plugins': 10.1.2 @@ -18191,10 +18454,10 @@ snapshots: '@expo/metro-config@0.20.17': dependencies: - '@babel/core': 7.28.0 - '@babel/generator': 7.28.0 - '@babel/parser': 7.28.0 - '@babel/types': 7.28.1 + '@babel/core': 7.28.3 + '@babel/generator': 7.28.3 + '@babel/parser': 7.28.3 + '@babel/types': 7.28.2 '@expo/config': 11.0.13 '@expo/env': 1.0.7 '@expo/json-file': 9.1.5 @@ -18213,9 +18476,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))': + '@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))': dependencies: - react-native: 0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0) + react-native: 0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0) '@expo/osascript@2.2.5': dependencies: @@ -18275,11 +18538,11 @@ snapshots: '@expo/sudo-prompt@9.3.2': {} - '@expo/vector-icons@14.1.0(expo-font@13.3.2(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0)': + '@expo/vector-icons@14.1.0(expo-font@13.3.2(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0)': dependencies: - expo-font: 13.3.2(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react@19.0.0) + expo-font: 13.3.2(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react@19.0.0) react: 19.0.0 - react-native: 0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0) + react-native: 0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0) '@expo/ws-tunnel@1.0.6': {} @@ -18347,10 +18610,10 @@ snapshots: dependencies: tslib: 2.8.1 - '@fumadocs/mdx-remote@1.4.0(@types/react@18.3.12)(acorn@8.14.0)(fumadocs-core@15.3.3(@types/react@18.3.12)(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': + '@fumadocs/mdx-remote@1.4.0(@types/react@18.3.12)(acorn@8.14.0)(fumadocs-core@15.3.3(@types/react@18.3.12)(next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': dependencies: '@mdx-js/mdx': 3.1.0(acorn@8.14.0) - fumadocs-core: 15.3.3(@types/react@18.3.12)(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + fumadocs-core: 15.3.3(@types/react@18.3.12)(next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) gray-matter: 4.0.3 react: 18.3.1 zod: 4.0.5 @@ -18649,7 +18912,7 @@ snapshots: '@jest/transform@29.7.0': dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.3 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.29 babel-plugin-istanbul: 6.1.1 @@ -18693,7 +18956,7 @@ snapshots: dependencies: '@jridgewell/set-array': 1.2.1 '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/trace-mapping': 0.3.29 '@jridgewell/resolve-uri@3.1.2': {} @@ -18706,11 +18969,6 @@ snapshots: '@jridgewell/sourcemap-codec@1.5.0': {} - '@jridgewell/trace-mapping@0.3.25': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping@0.3.29': dependencies: '@jridgewell/resolve-uri': 3.1.2 @@ -18740,14 +18998,14 @@ snapshots: '@manypkg/find-root@1.1.0': dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.27.6 '@types/node': 12.20.55 find-up: 4.1.0 fs-extra: 8.1.0 '@manypkg/get-packages@1.1.3': dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.27.6 '@changesets/types': 4.1.0 '@manypkg/find-root': 1.1.0 fs-extra: 8.1.0 @@ -19292,7 +19550,7 @@ snapshots: '@types/shimmer': 1.2.0 import-in-the-middle: 1.12.0 require-in-the-middle: 7.4.0 - semver: 7.6.3 + semver: 7.7.2 shimmer: 1.2.1 transitivePeerDependencies: - supports-color @@ -19515,7 +19773,7 @@ snapshots: '@protobufjs/utf8@1.1.0': {} - '@quetzallabs/i18n@0.1.19(next@14.2.5(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.2.0))(react@18.2.0))': + '@quetzallabs/i18n@0.1.19(next@14.2.5(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.2.0))(react@18.2.0))': dependencies: '@babel/parser': 7.25.6 '@babel/traverse': 7.25.6 @@ -19523,7 +19781,7 @@ snapshots: dotenv: 10.0.0 i18next: 21.10.0 i18next-parser: 9.0.2 - next-intl: 3.19.1(next@14.2.5(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.2.0))(react@18.2.0))(react@18.3.1) + next-intl: 3.19.1(next@14.2.5(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.2.0))(react@18.2.0))(react@18.3.1) path: 0.12.7 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -19532,7 +19790,7 @@ snapshots: - next - supports-color - '@quetzallabs/i18n@0.1.19(next@14.2.5(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))': + '@quetzallabs/i18n@0.1.19(next@14.2.5(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))': dependencies: '@babel/parser': 7.25.6 '@babel/traverse': 7.25.6 @@ -19540,7 +19798,7 @@ snapshots: dotenv: 10.0.0 i18next: 21.10.0 i18next-parser: 9.0.2 - next-intl: 3.19.1(next@14.2.5(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + next-intl: 3.19.1(next@14.2.5(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) path: 0.12.7 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -19549,7 +19807,7 @@ snapshots: - next - supports-color - '@quetzallabs/i18n@0.1.19(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))': + '@quetzallabs/i18n@0.1.19(next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))': dependencies: '@babel/parser': 7.25.6 '@babel/traverse': 7.25.6 @@ -19557,7 +19815,7 @@ snapshots: dotenv: 10.0.0 i18next: 21.10.0 i18next-parser: 9.0.2 - next-intl: 3.19.1(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + next-intl: 3.19.1(next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) path: 0.12.7 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -19566,7 +19824,7 @@ snapshots: - next - supports-color - '@quetzallabs/i18n@0.1.19(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@18.3.1))(react@18.3.1))': + '@quetzallabs/i18n@0.1.19(next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@18.3.1))(react@18.3.1))': dependencies: '@babel/parser': 7.25.6 '@babel/traverse': 7.25.6 @@ -19574,7 +19832,7 @@ snapshots: dotenv: 10.0.0 i18next: 21.10.0 i18next-parser: 9.0.2 - next-intl: 3.19.1(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@18.3.1))(react@18.3.1))(react@18.3.1) + next-intl: 3.19.1(next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@18.3.1))(react@18.3.1))(react@18.3.1) path: 0.12.7 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -19770,7 +20028,7 @@ snapshots: '@radix-ui/react-compose-refs@1.0.0(react@18.3.1)': dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.27.6 react: 18.3.1 '@radix-ui/react-compose-refs@1.1.0(@types/react@18.3.12)(react@18.2.0)': @@ -20608,7 +20866,7 @@ snapshots: '@radix-ui/react-slot@1.0.1(react@18.3.1)': dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.27.6 '@radix-ui/react-compose-refs': 1.0.0(react@18.3.1) react: 18.3.1 @@ -21008,76 +21266,76 @@ snapshots: '@react-native/assets-registry@0.80.1': {} - '@react-native/babel-plugin-codegen@0.79.5(@babel/core@7.26.0)': + '@react-native/babel-plugin-codegen@0.79.5(@babel/core@7.28.3)': dependencies: - '@babel/traverse': 7.28.0 - '@react-native/codegen': 0.79.5(@babel/core@7.26.0) + '@babel/traverse': 7.28.3 + '@react-native/codegen': 0.79.5(@babel/core@7.28.3) transitivePeerDependencies: - '@babel/core' - supports-color - '@react-native/babel-preset@0.79.5(@babel/core@7.26.0)': - dependencies: - '@babel/core': 7.26.0 - '@babel/plugin-proposal-export-default-from': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.26.0) - '@babel/plugin-syntax-export-default-from': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.26.0) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.26.0) - '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-async-generator-functions': 7.28.0(@babel/core@7.26.0) - '@babel/plugin-transform-async-to-generator': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-block-scoping': 7.28.0(@babel/core@7.26.0) - '@babel/plugin-transform-class-properties': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-classes': 7.28.0(@babel/core@7.26.0) - '@babel/plugin-transform-computed-properties': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-destructuring': 7.28.0(@babel/core@7.26.0) - '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-logical-assignment-operators': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-named-capturing-groups-regex': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-nullish-coalescing-operator': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-numeric-separator': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-object-rest-spread': 7.28.0(@babel/core@7.26.0) - '@babel/plugin-transform-optional-catch-binding': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-optional-chaining': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.26.0) - '@babel/plugin-transform-private-methods': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-private-property-in-object': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.26.0) - '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-regenerator': 7.28.1(@babel/core@7.26.0) - '@babel/plugin-transform-runtime': 7.28.0(@babel/core@7.26.0) - '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-spread': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.26.0) - '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.26.0) + '@react-native/babel-preset@0.79.5(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/plugin-proposal-export-default-from': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-syntax-export-default-from': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-async-generator-functions': 7.28.0(@babel/core@7.28.3) + '@babel/plugin-transform-async-to-generator': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-block-scoping': 7.28.0(@babel/core@7.28.3) + '@babel/plugin-transform-class-properties': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-classes': 7.28.3(@babel/core@7.28.3) + '@babel/plugin-transform-computed-properties': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-destructuring': 7.28.0(@babel/core@7.28.3) + '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-logical-assignment-operators': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-named-capturing-groups-regex': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-nullish-coalescing-operator': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-numeric-separator': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-object-rest-spread': 7.28.0(@babel/core@7.28.3) + '@babel/plugin-transform-optional-catch-binding': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-optional-chaining': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.3) + '@babel/plugin-transform-private-methods': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-private-property-in-object': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.28.3) + '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-regenerator': 7.28.3(@babel/core@7.28.3) + '@babel/plugin-transform-runtime': 7.28.3(@babel/core@7.28.3) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-spread': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.3) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.28.3) '@babel/template': 7.27.2 - '@react-native/babel-plugin-codegen': 0.79.5(@babel/core@7.26.0) + '@react-native/babel-plugin-codegen': 0.79.5(@babel/core@7.28.3) babel-plugin-syntax-hermes-parser: 0.25.1 - babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.26.0) + babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.28.3) react-refresh: 0.14.2 transitivePeerDependencies: - supports-color - '@react-native/codegen@0.79.5(@babel/core@7.26.0)': + '@react-native/codegen@0.79.5(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 glob: 7.2.3 hermes-parser: 0.25.1 invariant: 2.2.4 nullthrows: 1.1.1 yargs: 17.7.2 - '@react-native/codegen@0.80.1(@babel/core@7.26.0)': + '@react-native/codegen@0.80.1(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 glob: 7.2.3 hermes-parser: 0.28.1 invariant: 2.2.4 @@ -21147,24 +21405,24 @@ snapshots: '@react-native/normalize-colors@0.80.1': {} - '@react-native/virtualized-lists@0.80.1(@types/react@18.3.12)(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0)': + '@react-native/virtualized-lists@0.80.1(@types/react@18.3.12)(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0)': dependencies: invariant: 2.2.4 nullthrows: 1.1.1 react: 19.0.0 - react-native: 0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0) + react-native: 0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0) optionalDependencies: '@types/react': 18.3.12 - '@react-navigation/bottom-tabs@7.4.2(@react-navigation/native@7.1.14(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1))(react-native-safe-area-context@5.5.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native-screens@4.11.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1)': + '@react-navigation/bottom-tabs@7.4.2(@react-navigation/native@7.1.14(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1))(react-native-safe-area-context@5.5.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native-screens@4.11.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1)': dependencies: - '@react-navigation/elements': 2.5.2(@react-navigation/native@7.1.14(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1))(react-native-safe-area-context@5.5.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1) - '@react-navigation/native': 7.1.14(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) + '@react-navigation/elements': 2.5.2(@react-navigation/native@7.1.14(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1))(react-native-safe-area-context@5.5.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1) + '@react-navigation/native': 7.1.14(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) color: 4.2.3 react: 18.3.1 - react-native: 0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0) - react-native-safe-area-context: 5.5.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) - react-native-screens: 4.11.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) + react-native: 0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0) + react-native-safe-area-context: 5.5.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) + react-native-screens: 4.11.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) transitivePeerDependencies: - '@react-native-masked-view/masked-view' @@ -21179,36 +21437,36 @@ snapshots: use-latest-callback: 0.2.4(react@19.0.0) use-sync-external-store: 1.5.0(react@19.0.0) - '@react-navigation/elements@2.5.2(@react-navigation/native@7.1.14(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1))(react-native-safe-area-context@5.5.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1)': + '@react-navigation/elements@2.5.2(@react-navigation/native@7.1.14(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1))(react-native-safe-area-context@5.5.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1)': dependencies: - '@react-navigation/native': 7.1.14(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) + '@react-navigation/native': 7.1.14(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) color: 4.2.3 react: 18.3.1 - react-native: 0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0) - react-native-safe-area-context: 5.5.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) + react-native: 0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0) + react-native-safe-area-context: 5.5.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) use-latest-callback: 0.2.4(react@18.3.1) use-sync-external-store: 1.5.0(react@18.3.1) - '@react-navigation/native-stack@7.3.21(@react-navigation/native@7.1.14(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1))(react-native-safe-area-context@5.5.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native-screens@4.11.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1)': + '@react-navigation/native-stack@7.3.21(@react-navigation/native@7.1.14(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1))(react-native-safe-area-context@5.5.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native-screens@4.11.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1)': dependencies: - '@react-navigation/elements': 2.5.2(@react-navigation/native@7.1.14(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1))(react-native-safe-area-context@5.5.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1) - '@react-navigation/native': 7.1.14(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) + '@react-navigation/elements': 2.5.2(@react-navigation/native@7.1.14(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1))(react-native-safe-area-context@5.5.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1) + '@react-navigation/native': 7.1.14(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) react: 18.3.1 - react-native: 0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0) - react-native-safe-area-context: 5.5.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) - react-native-screens: 4.11.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) + react-native: 0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0) + react-native-safe-area-context: 5.5.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) + react-native-screens: 4.11.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) warn-once: 0.1.1 transitivePeerDependencies: - '@react-native-masked-view/masked-view' - '@react-navigation/native@7.1.14(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0)': + '@react-navigation/native@7.1.14(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0)': dependencies: '@react-navigation/core': 7.12.1(react@19.0.0) escape-string-regexp: 4.0.0 fast-deep-equal: 3.1.3 nanoid: 3.3.11 react: 19.0.0 - react-native: 0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0) + react-native: 0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0) use-latest-callback: 0.2.4(react@19.0.0) '@react-navigation/routers@7.4.1': @@ -21470,7 +21728,7 @@ snapshots: '@sentry/bundler-plugin-core@2.22.6(encoding@0.1.13)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@sentry/babel-plugin-component-annotate': 2.22.6 '@sentry/cli': 2.38.2(encoding@0.1.13) dotenv: 16.4.7 @@ -21526,7 +21784,7 @@ snapshots: dependencies: '@sentry/types': 8.40.0 - '@sentry/nextjs@8.40.0(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)(webpack@5.92.0(@swc/core@1.3.101)(esbuild@0.24.2))': + '@sentry/nextjs@8.40.0(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.4.1(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)(webpack@5.92.0(@swc/core@1.3.101)(esbuild@0.24.2))': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation-http': 0.53.0(@opentelemetry/api@1.9.0) @@ -21541,7 +21799,7 @@ snapshots: '@sentry/vercel-edge': 8.40.0 '@sentry/webpack-plugin': 2.22.6(encoding@0.1.13)(webpack@5.92.0(@swc/core@1.3.101)(esbuild@0.24.2)) chalk: 3.0.0 - next: 15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + next: 15.4.1(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) resolve: 1.22.8 rollup: 3.29.5 stacktrace-parser: 0.1.10 @@ -21554,7 +21812,7 @@ snapshots: - supports-color - webpack - '@sentry/nextjs@8.40.0(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0)(webpack@5.92.0(@swc/core@1.3.101)(esbuild@0.24.2))': + '@sentry/nextjs@8.40.0(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.4.1(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0)(webpack@5.92.0(@swc/core@1.3.101)(esbuild@0.24.2))': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation-http': 0.53.0(@opentelemetry/api@1.9.0) @@ -21569,7 +21827,7 @@ snapshots: '@sentry/vercel-edge': 8.40.0 '@sentry/webpack-plugin': 2.22.6(encoding@0.1.13)(webpack@5.92.0(@swc/core@1.3.101)(esbuild@0.24.2)) chalk: 3.0.0 - next: 15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + next: 15.4.1(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) resolve: 1.22.8 rollup: 3.29.5 stacktrace-parser: 0.1.10 @@ -21582,7 +21840,7 @@ snapshots: - supports-color - webpack - '@sentry/nextjs@8.40.0(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)(webpack@5.92.0(@swc/core@1.3.101)(esbuild@0.24.2))': + '@sentry/nextjs@8.40.0(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.4.1(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)(webpack@5.92.0(@swc/core@1.3.101)(esbuild@0.24.2))': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation-http': 0.53.0(@opentelemetry/api@1.9.0) @@ -21597,7 +21855,7 @@ snapshots: '@sentry/vercel-edge': 8.40.0 '@sentry/webpack-plugin': 2.22.6(encoding@0.1.13)(webpack@5.92.0(@swc/core@1.3.101)(esbuild@0.24.2)) chalk: 3.0.0 - next: 15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + next: 15.4.1(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) resolve: 1.22.8 rollup: 3.29.5 stacktrace-parser: 0.1.10 @@ -22381,7 +22639,7 @@ snapshots: '@testing-library/dom@10.4.0': dependencies: '@babel/code-frame': 7.26.2 - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.27.6 '@types/aria-query': 5.0.4 aria-query: 5.3.0 chalk: 4.1.2 @@ -22391,7 +22649,7 @@ snapshots: '@testing-library/react@15.0.7(@types/react@18.3.12)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.27.6 '@testing-library/dom': 10.4.0 '@types/react-dom': 18.3.1 react: 19.1.0 @@ -22432,30 +22690,32 @@ snapshots: '@types/aria-query@5.0.4': {} + '@types/babel-generator@6.25.8': + dependencies: + '@types/babel-types': 7.0.16 + + '@types/babel-types@7.0.16': {} + '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.28.0 - '@babel/types': 7.28.0 - '@types/babel__generator': 7.6.8 + '@babel/parser': 7.28.3 + '@babel/types': 7.28.2 + '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 - '@types/babel__traverse': 7.20.6 + '@types/babel__traverse': 7.20.7 - '@types/babel__generator@7.6.8': + '@types/babel__generator@7.27.0': dependencies: - '@babel/types': 7.28.0 + '@babel/types': 7.28.2 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.28.0 - '@babel/types': 7.28.0 - - '@types/babel__traverse@7.20.6': - dependencies: - '@babel/types': 7.28.0 + '@babel/parser': 7.28.3 + '@babel/types': 7.28.2 '@types/babel__traverse@7.20.7': dependencies: - '@babel/types': 7.28.1 + '@babel/types': 7.28.2 '@types/bcryptjs@3.0.0': dependencies: @@ -22923,7 +23183,7 @@ snapshots: graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 - semver: 7.6.3 + semver: 7.7.2 ts-api-utils: 1.4.0(typescript@5.8.3) optionalDependencies: typescript: 5.8.3 @@ -23023,7 +23283,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 8.25.0(typescript@5.3.3) '@typescript-eslint/utils': 8.25.0(eslint@9.21.0(jiti@2.4.2))(typescript@5.3.3) - debug: 4.4.0 + debug: 4.4.1 eslint: 9.21.0(jiti@2.4.2) ts-api-utils: 2.0.1(typescript@5.3.3) typescript: 5.3.3 @@ -23042,7 +23302,7 @@ snapshots: globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 - semver: 7.6.3 + semver: 7.7.2 ts-api-utils: 1.4.0(typescript@5.3.3) optionalDependencies: typescript: 5.3.3 @@ -23057,7 +23317,7 @@ snapshots: globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 - semver: 7.6.3 + semver: 7.7.2 ts-api-utils: 1.4.0(typescript@5.8.3) optionalDependencies: typescript: 5.8.3 @@ -23068,11 +23328,11 @@ snapshots: dependencies: '@typescript-eslint/types': 8.25.0 '@typescript-eslint/visitor-keys': 8.25.0 - debug: 4.4.0 + debug: 4.4.1 fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 - semver: 7.6.3 + semver: 7.7.2 ts-api-utils: 2.0.1(typescript@5.3.3) typescript: 5.3.3 transitivePeerDependencies: @@ -23087,7 +23347,7 @@ snapshots: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.3) eslint: 8.30.0 - semver: 7.6.3 + semver: 7.7.2 transitivePeerDependencies: - supports-color - typescript @@ -23101,7 +23361,7 @@ snapshots: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.8.3) eslint: 8.30.0 - semver: 7.6.3 + semver: 7.7.2 transitivePeerDependencies: - supports-color - typescript @@ -23141,23 +23401,23 @@ snapshots: '@urql/core': 5.2.0 wonka: 6.3.5 - '@vercel/analytics@1.3.1(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)': + '@vercel/analytics@1.3.1(next@15.4.1(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)': dependencies: server-only: 0.0.1 optionalDependencies: - next: 15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + next: 15.4.1(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: 19.0.0 '@vercel/functions@2.0.0(@aws-sdk/credential-provider-web-identity@3.864.0)': optionalDependencies: '@aws-sdk/credential-provider-web-identity': 3.864.0 - '@vercel/mcp-adapter@1.0.0(@modelcontextprotocol/sdk@1.17.2)(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))': + '@vercel/mcp-adapter@1.0.0(@modelcontextprotocol/sdk@1.17.2)(next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))': dependencies: '@modelcontextprotocol/sdk': 1.17.2 - mcp-handler: 1.0.1(@modelcontextprotocol/sdk@1.17.2)(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) + mcp-handler: 1.0.1(@modelcontextprotocol/sdk@1.17.2)(next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) optionalDependencies: - next: 15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@vercel/otel@1.10.4(@opentelemetry/api-logs@0.53.0)(@opentelemetry/api@1.9.0)(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-logs@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-metrics@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))': dependencies: @@ -23169,16 +23429,16 @@ snapshots: '@opentelemetry/sdk-metrics': 1.26.0(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': 1.26.0(@opentelemetry/api@1.9.0) - '@vercel/speed-insights@1.0.12(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)': + '@vercel/speed-insights@1.0.12(next@15.4.1(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)': optionalDependencies: - next: 15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + next: 15.4.1(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: 19.0.0 '@vitejs/plugin-react@4.3.3(vite@6.1.0(@types/node@20.17.6)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.19.3)(yaml@2.6.0))': dependencies: - '@babel/core': 7.26.0 - '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.0) + '@babel/core': 7.28.0 + '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.28.0) + '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.28.0) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 vite: 6.1.0(@types/node@20.17.6)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.19.3)(yaml@2.6.0) @@ -23187,9 +23447,9 @@ snapshots: '@vitejs/plugin-react@4.3.4(vite@6.1.0(@types/node@22.15.34)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.19.3)(yaml@2.6.0))': dependencies: - '@babel/core': 7.26.9 - '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.9) - '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.9) + '@babel/core': 7.28.0 + '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.28.0) + '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.28.0) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 vite: 6.1.0(@types/node@22.15.34)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.19.3)(yaml@2.6.0) @@ -23374,7 +23634,7 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.4.0 + debug: 4.4.1 transitivePeerDependencies: - supports-color @@ -23693,13 +23953,13 @@ snapshots: b4a@1.6.6: {} - babel-jest@29.7.0(@babel/core@7.26.0): + babel-jest@29.7.0(@babel/core@7.28.3): dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 '@jest/transform': 29.7.0 '@types/babel__core': 7.20.5 babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.6.3(@babel/core@7.26.0) + babel-preset-jest: 29.6.3(@babel/core@7.28.3) chalk: 4.1.2 graceful-fs: 4.2.11 slash: 3.0.0 @@ -23719,37 +23979,37 @@ snapshots: babel-plugin-jest-hoist@29.6.3: dependencies: '@babel/template': 7.27.2 - '@babel/types': 7.28.1 + '@babel/types': 7.28.2 '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.20.7 babel-plugin-macros@3.1.0: dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.27.6 cosmiconfig: 7.1.0 resolve: 1.22.10 - babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.26.0): + babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.28.3): dependencies: '@babel/compat-data': 7.28.0 - '@babel/core': 7.26.0 - '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.26.0) + '@babel/core': 7.28.3 + '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.3) semver: 6.3.1 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.26.0): + babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.28.3): dependencies: - '@babel/core': 7.26.0 - '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.26.0) + '@babel/core': 7.28.3 + '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.3) core-js-compat: 3.44.0 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-regenerator@0.6.5(@babel/core@7.26.0): + babel-plugin-polyfill-regenerator@0.6.5(@babel/core@7.28.3): dependencies: - '@babel/core': 7.26.0 - '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.26.0) + '@babel/core': 7.28.3 + '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.3) transitivePeerDependencies: - supports-color @@ -23763,51 +24023,51 @@ snapshots: dependencies: hermes-parser: 0.28.1 - babel-plugin-transform-flow-enums@0.0.2(@babel/core@7.26.0): + babel-plugin-transform-flow-enums@0.0.2(@babel/core@7.28.3): dependencies: - '@babel/plugin-syntax-flow': 7.27.1(@babel/core@7.26.0) + '@babel/plugin-syntax-flow': 7.27.1(@babel/core@7.28.3) transitivePeerDependencies: - '@babel/core' - babel-preset-current-node-syntax@1.1.0(@babel/core@7.26.0): - dependencies: - '@babel/core': 7.26.0 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.26.0) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.26.0) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.26.0) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.26.0) - '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.26.0) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.26.0) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.26.0) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.26.0) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.26.0) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.26.0) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.26.0) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.26.0) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.26.0) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.26.0) - - babel-preset-expo@13.2.3(@babel/core@7.26.0): + babel-preset-current-node-syntax@1.1.0(@babel/core@7.28.3): + dependencies: + '@babel/core': 7.28.3 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.28.3) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.28.3) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.28.3) + '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.3) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.28.3) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.28.3) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.3) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.3) + + babel-preset-expo@13.2.3(@babel/core@7.28.3): dependencies: '@babel/helper-module-imports': 7.27.1 - '@babel/plugin-proposal-decorators': 7.28.0(@babel/core@7.26.0) - '@babel/plugin-proposal-export-default-from': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-syntax-export-default-from': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-object-rest-spread': 7.28.0(@babel/core@7.26.0) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.26.0) - '@babel/plugin-transform-private-methods': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-private-property-in-object': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-runtime': 7.28.0(@babel/core@7.26.0) - '@babel/preset-react': 7.27.1(@babel/core@7.26.0) - '@babel/preset-typescript': 7.27.1(@babel/core@7.26.0) - '@react-native/babel-preset': 0.79.5(@babel/core@7.26.0) + '@babel/plugin-proposal-decorators': 7.28.0(@babel/core@7.28.3) + '@babel/plugin-proposal-export-default-from': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-syntax-export-default-from': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-object-rest-spread': 7.28.0(@babel/core@7.28.3) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.3) + '@babel/plugin-transform-private-methods': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-private-property-in-object': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-runtime': 7.28.3(@babel/core@7.28.3) + '@babel/preset-react': 7.27.1(@babel/core@7.28.3) + '@babel/preset-typescript': 7.27.1(@babel/core@7.28.3) + '@react-native/babel-preset': 0.79.5(@babel/core@7.28.3) babel-plugin-react-native-web: 0.19.13 babel-plugin-syntax-hermes-parser: 0.25.1 - babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.26.0) + babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.28.3) debug: 4.4.1 react-refresh: 0.14.2 resolve-from: 5.0.0 @@ -23815,11 +24075,11 @@ snapshots: - '@babel/core' - supports-color - babel-preset-jest@29.6.3(@babel/core@7.26.0): + babel-preset-jest@29.6.3(@babel/core@7.28.3): dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.1.0(@babel/core@7.26.0) + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.28.3) bail@2.0.2: {} @@ -24872,7 +25132,7 @@ snapshots: date-fns@2.30.0: dependencies: - '@babel/runtime': 7.24.7 + '@babel/runtime': 7.27.6 date-fns@3.6.0: {} @@ -25021,7 +25281,7 @@ snapshots: dom-helpers@5.2.1: dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.27.6 csstype: 3.1.3 dom-serializer@2.0.0: @@ -25795,7 +26055,7 @@ snapshots: eslint-plugin-jsx-a11y@6.8.0(eslint@8.30.0): dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.27.6 aria-query: 5.3.0 array-includes: 3.1.8 array.prototype.flatmap: 1.3.2 @@ -26097,56 +26357,56 @@ snapshots: expand-template@2.0.3: {} - expo-asset@11.1.7(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0): + expo-asset@11.1.7(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0): dependencies: '@expo/image-utils': 0.7.6 - expo: 53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) - expo-constants: 17.1.7(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)) + expo: 53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) + expo-constants: 17.1.7(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)) react: 19.0.0 - react-native: 0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0) + react-native: 0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0) transitivePeerDependencies: - supports-color - expo-constants@17.0.8(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)): + expo-constants@17.0.8(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)): dependencies: '@expo/config': 10.0.11 '@expo/env': 0.4.2 - expo: 53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) - react-native: 0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0) + expo: 53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) + react-native: 0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0) transitivePeerDependencies: - supports-color - expo-constants@17.1.7(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)): + expo-constants@17.1.7(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)): dependencies: '@expo/config': 11.0.13 '@expo/env': 1.0.7 - expo: 53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) - react-native: 0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0) + expo: 53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) + react-native: 0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0) transitivePeerDependencies: - supports-color - expo-file-system@18.1.11(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)): + expo-file-system@18.1.11(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)): dependencies: - expo: 53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) - react-native: 0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0) + expo: 53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) + react-native: 0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0) - expo-font@13.3.2(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react@19.0.0): + expo-font@13.3.2(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react@19.0.0): dependencies: - expo: 53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) + expo: 53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) fontfaceobserver: 2.3.0 react: 19.0.0 - expo-keep-awake@14.1.4(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react@19.0.0): + expo-keep-awake@14.1.4(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react@19.0.0): dependencies: - expo: 53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) + expo: 53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) react: 19.0.0 - expo-linking@7.0.5(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0): + expo-linking@7.0.5(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0): dependencies: - expo-constants: 17.0.8(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)) + expo-constants: 17.0.8(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)) invariant: 2.2.4 react: 19.0.0 - react-native: 0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0) + react-native: 0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0) transitivePeerDependencies: - expo - supports-color @@ -26165,23 +26425,23 @@ snapshots: dependencies: invariant: 2.2.4 - ? expo-router@4.0.21(expo-constants@17.1.7(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(expo-linking@7.0.5(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react-native-safe-area-context@5.5.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native-screens@4.11.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1) + ? expo-router@4.0.21(expo-constants@17.1.7(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(expo-linking@7.0.5(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react-native-safe-area-context@5.5.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native-screens@4.11.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1) : dependencies: - '@expo/metro-runtime': 4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)) + '@expo/metro-runtime': 4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)) '@expo/server': 0.5.3 '@radix-ui/react-slot': 1.0.1(react@18.3.1) - '@react-navigation/bottom-tabs': 7.4.2(@react-navigation/native@7.1.14(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1))(react-native-safe-area-context@5.5.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native-screens@4.11.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1) - '@react-navigation/native': 7.1.14(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) - '@react-navigation/native-stack': 7.3.21(@react-navigation/native@7.1.14(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1))(react-native-safe-area-context@5.5.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native-screens@4.11.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1) + '@react-navigation/bottom-tabs': 7.4.2(@react-navigation/native@7.1.14(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1))(react-native-safe-area-context@5.5.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native-screens@4.11.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1) + '@react-navigation/native': 7.1.14(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) + '@react-navigation/native-stack': 7.3.21(@react-navigation/native@7.1.14(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1))(react-native-safe-area-context@5.5.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native-screens@4.11.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1) client-only: 0.0.1 - expo: 53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) - expo-constants: 17.1.7(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)) - expo-linking: 7.0.5(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) + expo: 53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) + expo-constants: 17.1.7(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)) + expo-linking: 7.0.5(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) react-helmet-async: 1.3.0(react-dom@19.0.0(react@19.0.0))(react@18.3.1) react-native-helmet-async: 2.0.4(react@18.3.1) - react-native-is-edge-to-edge: 1.2.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1) - react-native-safe-area-context: 5.5.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) - react-native-screens: 4.11.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) + react-native-is-edge-to-edge: 1.2.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1) + react-native-safe-area-context: 5.5.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) + react-native-screens: 4.11.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) schema-utils: 4.3.2 semver: 7.6.3 server-only: 0.0.1 @@ -26192,29 +26452,29 @@ snapshots: - react-native - supports-color - expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0): + expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0): dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.3 '@expo/cli': 0.24.18 '@expo/config': 11.0.13 '@expo/config-plugins': 10.1.2 '@expo/fingerprint': 0.13.4 '@expo/metro-config': 0.20.17 - '@expo/vector-icons': 14.1.0(expo-font@13.3.2(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) - babel-preset-expo: 13.2.3(@babel/core@7.26.0) - expo-asset: 11.1.7(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) - expo-constants: 17.1.7(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)) - expo-file-system: 18.1.11(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)) - expo-font: 13.3.2(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react@19.0.0) - expo-keep-awake: 14.1.4(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react@19.0.0) + '@expo/vector-icons': 14.1.0(expo-font@13.3.2(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) + babel-preset-expo: 13.2.3(@babel/core@7.28.3) + expo-asset: 11.1.7(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) + expo-constants: 17.1.7(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)) + expo-file-system: 18.1.11(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)) + expo-font: 13.3.2(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react@19.0.0) + expo-keep-awake: 14.1.4(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react@19.0.0) expo-modules-autolinking: 2.1.13 expo-modules-core: 2.4.2 react: 19.0.0 - react-native: 0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0) - react-native-edge-to-edge: 1.6.0(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) + react-native: 0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0) + react-native-edge-to-edge: 1.6.0(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) whatwg-url-without-unicode: 8.0.0-3 optionalDependencies: - '@expo/metro-runtime': 4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)) + '@expo/metro-runtime': 4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)) transitivePeerDependencies: - '@babel/core' - babel-plugin-react-compiler @@ -26514,11 +26774,11 @@ snapshots: freeport-async@2.0.0: {} - ? freestyle-sandboxes@0.0.66(encoding@0.1.13)(expo-constants@17.1.7(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(expo-linking@7.0.5(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react-native-safe-area-context@5.5.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native-screens@4.11.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(ws@8.18.3) + ? freestyle-sandboxes@0.0.66(encoding@0.1.13)(expo-constants@17.1.7(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(expo-linking@7.0.5(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react-native-safe-area-context@5.5.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native-screens@4.11.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(ws@8.18.3) : dependencies: '@hey-api/client-fetch': 0.5.7 '@tanstack/react-query': 5.81.5(react@18.3.1) - expo-router: 4.0.21(expo-constants@17.1.7(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(expo-linking@7.0.5(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react-native-safe-area-context@5.5.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native-screens@4.11.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1) + expo-router: 4.0.21(expo-constants@17.1.7(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(expo-linking@7.0.5(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react-native-safe-area-context@5.5.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native-screens@4.11.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1) glob: 11.0.1 hono: 4.8.3 openai: 4.104.0(encoding@0.1.13)(ws@8.18.3)(zod@3.25.76) @@ -26541,13 +26801,13 @@ snapshots: - supports-color - ws - ? freestyle-sandboxes@0.0.92(encoding@0.1.13)(expo-constants@17.1.7(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(expo-linking@7.0.5(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react-native-safe-area-context@5.5.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native-screens@4.11.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(ws@8.18.3) + ? freestyle-sandboxes@0.0.92(encoding@0.1.13)(expo-constants@17.1.7(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(expo-linking@7.0.5(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react-native-safe-area-context@5.5.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native-screens@4.11.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(ws@8.18.3) : dependencies: '@hey-api/client-fetch': 0.5.7 '@tanstack/react-query': 5.81.5(react@18.3.1) '@types/react': 18.3.12 - expo-router: 4.0.21(expo-constants@17.1.7(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(expo-linking@7.0.5(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react-native-safe-area-context@5.5.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native-screens@4.11.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1) - freestyle-sandboxes: 0.0.66(encoding@0.1.13)(expo-constants@17.1.7(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(expo-linking@7.0.5(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(expo@53.0.17(@babel/core@7.26.0)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react-native-safe-area-context@5.5.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native-screens@4.11.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(ws@8.18.3) + expo-router: 4.0.21(expo-constants@17.1.7(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(expo-linking@7.0.5(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react-native-safe-area-context@5.5.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native-screens@4.11.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1) + freestyle-sandboxes: 0.0.66(encoding@0.1.13)(expo-constants@17.1.7(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(expo-linking@7.0.5(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(expo@53.0.17(@babel/core@7.28.3)(@expo/metro-runtime@4.0.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0)))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react-native-safe-area-context@5.5.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native-screens@4.11.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0))(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(ws@8.18.3) glob: 11.0.1 hono: 4.8.3 openai: 4.104.0(encoding@0.1.13)(ws@8.18.3)(zod@3.25.76) @@ -26628,7 +26888,7 @@ snapshots: fsevents@2.3.3: optional: true - fumadocs-core@15.3.3(@types/react@18.3.12)(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + fumadocs-core@15.3.3(@types/react@18.3.12)(next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@formatjs/intl-localematcher': 0.6.1 '@orama/orama': 3.1.7 @@ -26646,14 +26906,14 @@ snapshots: shiki: 3.6.0 unist-util-visit: 5.0.0 optionalDependencies: - next: 15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) transitivePeerDependencies: - '@types/react' - supports-color - fumadocs-core@15.3.4(@types/react@18.3.12)(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + fumadocs-core@15.3.4(@types/react@18.3.12)(next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@formatjs/intl-localematcher': 0.6.1 '@orama/orama': 3.1.7 @@ -26671,14 +26931,14 @@ snapshots: shiki: 3.6.0 unist-util-visit: 5.0.0 optionalDependencies: - next: 15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) transitivePeerDependencies: - '@types/react' - supports-color - fumadocs-mdx@11.6.4(@fumadocs/mdx-remote@1.4.0(@types/react@18.3.12)(acorn@8.14.0)(fumadocs-core@15.3.3(@types/react@18.3.12)(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1))(acorn@8.14.0)(fumadocs-core@15.3.3(@types/react@18.3.12)(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)): + fumadocs-mdx@11.6.4(@fumadocs/mdx-remote@1.4.0(@types/react@18.3.12)(acorn@8.14.0)(fumadocs-core@15.3.3(@types/react@18.3.12)(next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1))(acorn@8.14.0)(fumadocs-core@15.3.3(@types/react@18.3.12)(next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)): dependencies: '@mdx-js/mdx': 3.1.0(acorn@8.14.0) '@standard-schema/spec': 1.0.0 @@ -26687,21 +26947,21 @@ snapshots: esbuild: 0.25.5 estree-util-value-to-estree: 3.4.0 fast-glob: 3.3.3 - fumadocs-core: 15.3.3(@types/react@18.3.12)(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + fumadocs-core: 15.3.3(@types/react@18.3.12)(next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) gray-matter: 4.0.3 js-yaml: 4.1.0 lru-cache: 11.1.0 - next: 15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) picocolors: 1.1.1 unist-util-visit: 5.0.0 zod: 3.25.76 optionalDependencies: - '@fumadocs/mdx-remote': 1.4.0(@types/react@18.3.12)(acorn@8.14.0)(fumadocs-core@15.3.3(@types/react@18.3.12)(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + '@fumadocs/mdx-remote': 1.4.0(@types/react@18.3.12)(acorn@8.14.0)(fumadocs-core@15.3.3(@types/react@18.3.12)(next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) transitivePeerDependencies: - acorn - supports-color - fumadocs-openapi@8.1.12(@types/react-dom@18.3.1)(@types/react@18.3.12)(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@4.1.9): + fumadocs-openapi@8.1.12(@types/react-dom@18.3.1)(@types/react@18.3.12)(next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@4.1.9): dependencies: '@fumari/json-schema-to-typescript': 1.1.3 '@radix-ui/react-dialog': 1.1.14(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -26710,12 +26970,12 @@ snapshots: '@scalar/openapi-parser': 0.10.17 ajv: 8.17.1 class-variance-authority: 0.7.1 - fumadocs-core: 15.3.4(@types/react@18.3.12)(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - fumadocs-ui: 15.3.4(@types/react-dom@18.3.1)(@types/react@18.3.12)(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@4.1.9) + fumadocs-core: 15.3.4(@types/react@18.3.12)(next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + fumadocs-ui: 15.3.4(@types/react-dom@18.3.1)(@types/react@18.3.12)(next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@4.1.9) github-slugger: 2.0.0 hast-util-to-jsx-runtime: 2.3.6 js-yaml: 4.1.0 - next: 15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-themes: 0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) openapi-sampler: 1.6.1 react: 18.3.1 @@ -26749,7 +27009,7 @@ snapshots: transitivePeerDependencies: - supports-color - fumadocs-ui@15.3.3(@types/react-dom@18.3.1)(@types/react@18.3.12)(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@4.1.9): + fumadocs-ui@15.3.3(@types/react-dom@18.3.1)(@types/react@18.3.12)(next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@4.1.9): dependencies: '@radix-ui/react-accordion': 1.2.11(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-collapsible': 1.1.11(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -26762,9 +27022,9 @@ snapshots: '@radix-ui/react-slot': 1.2.3(@types/react@18.3.12)(react@18.3.1) '@radix-ui/react-tabs': 1.1.12(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) class-variance-authority: 0.7.1 - fumadocs-core: 15.3.3(@types/react@18.3.12)(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + fumadocs-core: 15.3.3(@types/react@18.3.12)(next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) lodash.merge: 4.6.2 - next: 15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-themes: 0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) postcss-selector-parser: 7.1.0 react: 18.3.1 @@ -26781,7 +27041,7 @@ snapshots: - algoliasearch - supports-color - fumadocs-ui@15.3.4(@types/react-dom@18.3.1)(@types/react@18.3.12)(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@4.1.9): + fumadocs-ui@15.3.4(@types/react-dom@18.3.1)(@types/react@18.3.12)(next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@4.1.9): dependencies: '@radix-ui/react-accordion': 1.2.11(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-collapsible': 1.1.11(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -26794,9 +27054,9 @@ snapshots: '@radix-ui/react-slot': 1.2.3(@types/react@18.3.12)(react@18.3.1) '@radix-ui/react-tabs': 1.1.12(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) class-variance-authority: 0.7.1 - fumadocs-core: 15.3.4(@types/react@18.3.12)(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + fumadocs-core: 15.3.4(@types/react@18.3.12)(next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) lodash.merge: 4.6.2 - next: 15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-themes: 0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) postcss-selector-parser: 7.1.0 react: 18.3.1 @@ -26836,9 +27096,9 @@ snapshots: strip-ansi: 6.0.1 wide-align: 1.1.5 - geist@1.3.0(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)): + geist@1.3.0(next@15.4.1(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)): dependencies: - next: 15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + next: 15.4.1(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) generic-pool@3.9.0: {} @@ -27275,7 +27535,7 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.4.0 + debug: 4.4.1 transitivePeerDependencies: - supports-color @@ -27305,7 +27565,7 @@ snapshots: i18next-parser@9.0.2: dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.27.6 broccoli-plugin: 4.0.7 cheerio: 1.0.0 colors: 1.4.0 @@ -27327,11 +27587,11 @@ snapshots: i18next@21.10.0: dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.27.6 i18next@23.14.0: dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.27.6 iconv-lite@0.4.24: dependencies: @@ -27343,9 +27603,9 @@ snapshots: ieee754@1.2.1: {} - iframe-resizer-react@1.1.1(@babel/core@7.26.0)(prop-types@15.8.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + iframe-resizer-react@1.1.1(@babel/core@7.28.3)(prop-types@15.8.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: - '@babel/plugin-proposal-private-property-in-object': 7.21.11(@babel/core@7.26.0) + '@babel/plugin-proposal-private-property-in-object': 7.21.11(@babel/core@7.28.3) iframe-resizer: 4.4.5 prop-types: 15.8.1 react: 19.0.0 @@ -27655,8 +27915,8 @@ snapshots: istanbul-lib-instrument@5.2.1: dependencies: - '@babel/core': 7.28.0 - '@babel/parser': 7.28.0 + '@babel/core': 7.28.3 + '@babel/parser': 7.28.3 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 6.3.1 @@ -28251,14 +28511,14 @@ snapshots: math-intrinsics@1.1.0: {} - mcp-handler@1.0.1(@modelcontextprotocol/sdk@1.17.2)(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)): + mcp-handler@1.0.1(@modelcontextprotocol/sdk@1.17.2)(next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)): dependencies: '@modelcontextprotocol/sdk': 1.17.2 chalk: 5.3.0 commander: 11.1.0 redis: 4.7.1 optionalDependencies: - next: 15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) mdast-util-find-and-replace@3.0.2: dependencies: @@ -28466,7 +28726,7 @@ snapshots: metro-babel-transformer@0.82.5: dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.3 flow-enums-runtime: 0.0.6 hermes-parser: 0.29.1 nullthrows: 1.1.1 @@ -28532,14 +28792,14 @@ snapshots: metro-runtime@0.82.5: dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.3 flow-enums-runtime: 0.0.6 metro-source-map@0.82.5: dependencies: - '@babel/traverse': 7.28.0 - '@babel/traverse--for-generate-function-map': '@babel/traverse@7.28.0' - '@babel/types': 7.28.1 + '@babel/traverse': 7.28.3 + '@babel/traverse--for-generate-function-map': '@babel/traverse@7.28.3' + '@babel/types': 7.28.2 flow-enums-runtime: 0.0.6 invariant: 2.2.4 metro-symbolicate: 0.82.5 @@ -28563,10 +28823,10 @@ snapshots: metro-transform-plugins@0.82.5: dependencies: - '@babel/core': 7.28.0 - '@babel/generator': 7.28.0 + '@babel/core': 7.28.3 + '@babel/generator': 7.28.3 '@babel/template': 7.27.2 - '@babel/traverse': 7.28.0 + '@babel/traverse': 7.28.3 flow-enums-runtime: 0.0.6 nullthrows: 1.1.1 transitivePeerDependencies: @@ -28574,10 +28834,10 @@ snapshots: metro-transform-worker@0.82.5: dependencies: - '@babel/core': 7.28.0 - '@babel/generator': 7.28.0 - '@babel/parser': 7.28.0 - '@babel/types': 7.28.1 + '@babel/core': 7.28.3 + '@babel/generator': 7.28.3 + '@babel/parser': 7.28.3 + '@babel/types': 7.28.2 flow-enums-runtime: 0.0.6 metro: 0.82.5 metro-babel-transformer: 0.82.5 @@ -28595,12 +28855,12 @@ snapshots: metro@0.82.5: dependencies: '@babel/code-frame': 7.27.1 - '@babel/core': 7.28.0 - '@babel/generator': 7.28.0 - '@babel/parser': 7.28.0 + '@babel/core': 7.28.3 + '@babel/generator': 7.28.3 + '@babel/parser': 7.28.3 '@babel/template': 7.27.2 - '@babel/traverse': 7.28.0 - '@babel/types': 7.28.1 + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 accepts: 1.3.8 chalk: 4.1.2 ci-info: 2.0.0 @@ -29058,47 +29318,47 @@ snapshots: nested-error-stacks@2.0.1: {} - next-intl@3.19.1(next@14.2.5(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.2.0))(react@18.2.0))(react@18.3.1): + next-intl@3.19.1(next@14.2.5(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.2.0))(react@18.2.0))(react@18.3.1): dependencies: '@formatjs/intl-localematcher': 0.5.4 negotiator: 0.6.3 - next: 14.2.5(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.2.0))(react@18.2.0) + next: 14.2.5(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.2.0))(react@18.2.0) react: 18.3.1 use-intl: 3.19.1(react@18.3.1) - next-intl@3.19.1(next@14.2.5(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): + next-intl@3.19.1(next@14.2.5(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): dependencies: '@formatjs/intl-localematcher': 0.5.4 negotiator: 0.6.3 - next: 14.2.5(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 14.2.5(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 use-intl: 3.19.1(react@18.3.1) - next-intl@3.19.1(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): + next-intl@3.19.1(next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): dependencies: '@formatjs/intl-localematcher': 0.5.4 negotiator: 0.6.3 - next: 15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 use-intl: 3.19.1(react@18.3.1) - next-intl@3.19.1(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@18.3.1))(react@18.3.1))(react@18.3.1): + next-intl@3.19.1(next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@18.3.1))(react@18.3.1))(react@18.3.1): dependencies: '@formatjs/intl-localematcher': 0.5.4 negotiator: 0.6.3 - next: 15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@18.3.1))(react@18.3.1) + next: 15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@18.3.1))(react@18.3.1) react: 18.3.1 use-intl: 3.19.1(react@18.3.1) - next-themes@0.2.1(next@14.2.3(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next-themes@0.2.1(next@14.2.3(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - next: 14.2.3(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 14.2.3(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - next-themes@0.2.1(next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + next-themes@0.2.1(next@15.4.1(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: - next: 15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + next: 15.4.1(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: 19.0.0 react-dom: 19.0.0(react@19.0.0) @@ -29112,7 +29372,7 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - next@14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next@14.2.15(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@next/env': 14.2.15 '@swc/helpers': 0.5.5 @@ -29122,7 +29382,7 @@ snapshots: postcss: 8.4.31 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - styled-jsx: 5.1.1(@babel/core@7.26.0)(react@18.3.1) + styled-jsx: 5.1.1(@babel/core@7.28.0)(react@18.3.1) optionalDependencies: '@next/swc-darwin-arm64': 14.2.15 '@next/swc-darwin-x64': 14.2.15 @@ -29138,7 +29398,7 @@ snapshots: - '@babel/core' - babel-plugin-macros - next@14.2.3(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next@14.2.3(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@next/env': 14.2.3 '@swc/helpers': 0.5.5 @@ -29148,7 +29408,7 @@ snapshots: postcss: 8.4.31 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - styled-jsx: 5.1.1(@babel/core@7.26.0)(react@18.3.1) + styled-jsx: 5.1.1(@babel/core@7.28.0)(react@18.3.1) optionalDependencies: '@next/swc-darwin-arm64': 14.2.3 '@next/swc-darwin-x64': 14.2.3 @@ -29164,7 +29424,7 @@ snapshots: - '@babel/core' - babel-plugin-macros - next@14.2.5(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + next@14.2.5(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: '@next/env': 14.2.5 '@swc/helpers': 0.5.5 @@ -29174,7 +29434,7 @@ snapshots: postcss: 8.4.31 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - styled-jsx: 5.1.1(@babel/core@7.26.0)(react@18.2.0) + styled-jsx: 5.1.1(@babel/core@7.28.0)(react@18.2.0) optionalDependencies: '@next/swc-darwin-arm64': 14.2.5 '@next/swc-darwin-x64': 14.2.5 @@ -29190,7 +29450,7 @@ snapshots: - '@babel/core' - babel-plugin-macros - next@14.2.5(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.2.0))(react@18.2.0): + next@14.2.5(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.2.0))(react@18.2.0): dependencies: '@next/env': 14.2.5 '@swc/helpers': 0.5.5 @@ -29200,7 +29460,7 @@ snapshots: postcss: 8.4.31 react: 18.2.0 react-dom: 18.3.1(react@18.2.0) - styled-jsx: 5.1.1(@babel/core@7.26.0)(react@18.2.0) + styled-jsx: 5.1.1(@babel/core@7.28.0)(react@18.2.0) optionalDependencies: '@next/swc-darwin-arm64': 14.2.5 '@next/swc-darwin-x64': 14.2.5 @@ -29216,7 +29476,7 @@ snapshots: - '@babel/core' - babel-plugin-macros - next@14.2.5(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next@14.2.5(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@next/env': 14.2.5 '@swc/helpers': 0.5.5 @@ -29226,7 +29486,7 @@ snapshots: postcss: 8.4.31 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - styled-jsx: 5.1.1(@babel/core@7.26.0)(react@18.3.1) + styled-jsx: 5.1.1(@babel/core@7.28.0)(react@18.3.1) optionalDependencies: '@next/swc-darwin-arm64': 14.2.5 '@next/swc-darwin-x64': 14.2.5 @@ -29242,7 +29502,7 @@ snapshots: - '@babel/core' - babel-plugin-macros - next@14.3.0-canary.26(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next@14.3.0-canary.26(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@next/env': 14.3.0-canary.26 '@swc/helpers': 0.5.11 @@ -29252,7 +29512,7 @@ snapshots: postcss: 8.4.31 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - styled-jsx: 5.1.1(@babel/core@7.26.0)(react@18.3.1) + styled-jsx: 5.1.1(@babel/core@7.28.0)(react@18.3.1) optionalDependencies: '@next/swc-darwin-arm64': 14.3.0-canary.26 '@next/swc-darwin-x64': 14.3.0-canary.26 @@ -29269,15 +29529,39 @@ snapshots: - '@babel/core' - babel-plugin-macros - next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@next/env': 15.4.1 '@swc/helpers': 0.5.15 caniuse-lite: 1.0.30001726 postcss: 8.4.31 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - styled-jsx: 5.1.6(@babel/core@7.26.0)(react@18.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + styled-jsx: 5.1.6(@babel/core@7.28.0)(react@18.3.1) + optionalDependencies: + '@next/swc-darwin-arm64': 15.4.1 + '@next/swc-darwin-x64': 15.4.1 + '@next/swc-linux-arm64-gnu': 15.4.1 + '@next/swc-linux-arm64-musl': 15.4.1 + '@next/swc-linux-x64-gnu': 15.4.1 + '@next/swc-linux-x64-musl': 15.4.1 + '@next/swc-win32-arm64-msvc': 15.4.1 + '@next/swc-win32-x64-msvc': 15.4.1 + '@opentelemetry/api': 1.9.0 + sharp: 0.34.3 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + + next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@next/env': 15.4.1 + '@swc/helpers': 0.5.15 + caniuse-lite: 1.0.30001726 + postcss: 8.4.31 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + styled-jsx: 5.1.6(@babel/core@7.28.0)(react@19.0.0) optionalDependencies: '@next/swc-darwin-arm64': 15.4.1 '@next/swc-darwin-x64': 15.4.1 @@ -29293,15 +29577,15 @@ snapshots: - '@babel/core' - babel-plugin-macros - next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next@15.4.1(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@18.3.1))(react@18.3.1): dependencies: '@next/env': 15.4.1 '@swc/helpers': 0.5.15 caniuse-lite: 1.0.30001726 postcss: 8.4.31 react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - styled-jsx: 5.1.6(@babel/core@7.26.0)(react@18.3.1) + react-dom: 19.1.0(react@18.3.1) + styled-jsx: 5.1.6(@babel/core@7.28.0)(react@18.3.1) optionalDependencies: '@next/swc-darwin-arm64': 15.4.1 '@next/swc-darwin-x64': 15.4.1 @@ -29317,15 +29601,15 @@ snapshots: - '@babel/core' - babel-plugin-macros - next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + next@15.4.1(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: '@next/env': 15.4.1 '@swc/helpers': 0.5.15 caniuse-lite: 1.0.30001726 postcss: 8.4.31 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - styled-jsx: 5.1.6(@babel/core@7.26.0)(react@19.0.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + styled-jsx: 5.1.6(@babel/core@7.28.3)(react@18.2.0) optionalDependencies: '@next/swc-darwin-arm64': 15.4.1 '@next/swc-darwin-x64': 15.4.1 @@ -29341,15 +29625,15 @@ snapshots: - '@babel/core' - babel-plugin-macros - next@15.4.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@18.3.1))(react@18.3.1): + next@15.4.1(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: '@next/env': 15.4.1 '@swc/helpers': 0.5.15 caniuse-lite: 1.0.30001726 postcss: 8.4.31 - react: 18.3.1 - react-dom: 19.1.0(react@18.3.1) - styled-jsx: 5.1.6(@babel/core@7.26.0)(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + styled-jsx: 5.1.6(@babel/core@7.28.3)(react@19.0.0) optionalDependencies: '@next/swc-darwin-arm64': 15.4.1 '@next/swc-darwin-x64': 15.4.1 @@ -29372,7 +29656,7 @@ snapshots: node-abi@3.65.0: dependencies: - semver: 7.6.3 + semver: 7.7.2 node-addon-api@5.1.0: {} @@ -29730,7 +30014,7 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.26.2 + '@babel/code-frame': 7.27.1 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -29915,7 +30199,7 @@ snapshots: polished@4.3.1: dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.27.6 possible-typed-array-names@1.0.0: {} @@ -30362,7 +30646,7 @@ snapshots: react-helmet-async@1.3.0(react-dom@19.0.0(react@19.0.0))(react@18.3.1): dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.27.6 invariant: 2.2.4 prop-types: 15.8.1 react: 18.3.1 @@ -30434,10 +30718,10 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-native-edge-to-edge@1.6.0(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0): + react-native-edge-to-edge@1.6.0(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0): dependencies: react: 19.0.0 - react-native: 0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0) + react-native: 0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0) react-native-helmet-async@2.0.4(react@18.3.1): dependencies: @@ -30446,43 +30730,43 @@ snapshots: react-fast-compare: 3.2.2 shallowequal: 1.1.0 - react-native-is-edge-to-edge@1.2.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1): + react-native-is-edge-to-edge@1.2.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@18.3.1): dependencies: react: 18.3.1 - react-native: 0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0) + react-native: 0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0) - react-native-is-edge-to-edge@1.2.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0): + react-native-is-edge-to-edge@1.2.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0): dependencies: react: 19.0.0 - react-native: 0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0) + react-native: 0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0) - react-native-safe-area-context@5.5.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0): + react-native-safe-area-context@5.5.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0): dependencies: react: 19.0.0 - react-native: 0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0) + react-native: 0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0) - react-native-screens@4.11.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0): + react-native-screens@4.11.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0): dependencies: react: 19.0.0 react-freeze: 1.0.4(react@19.0.0) - react-native: 0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0) - react-native-is-edge-to-edge: 1.2.1(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) + react-native: 0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0) + react-native-is-edge-to-edge: 1.2.1(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) warn-once: 0.1.1 - react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0): + react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0): dependencies: '@jest/create-cache-key-function': 29.7.0 '@react-native/assets-registry': 0.80.1 - '@react-native/codegen': 0.80.1(@babel/core@7.26.0) + '@react-native/codegen': 0.80.1(@babel/core@7.28.3) '@react-native/community-cli-plugin': 0.80.1 '@react-native/gradle-plugin': 0.80.1 '@react-native/js-polyfills': 0.80.1 '@react-native/normalize-colors': 0.80.1 - '@react-native/virtualized-lists': 0.80.1(@types/react@18.3.12)(react-native@0.80.1(@babel/core@7.26.0)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) + '@react-native/virtualized-lists': 0.80.1(@types/react@18.3.12)(react-native@0.80.1(@babel/core@7.28.3)(@types/react@18.3.12)(react@19.0.0))(react@19.0.0) abort-controller: 3.0.0 anser: 1.4.10 ansi-regex: 5.0.1 - babel-jest: 29.7.0(@babel/core@7.26.0) + babel-jest: 29.7.0(@babel/core@7.28.3) babel-plugin-syntax-hermes-parser: 0.28.1 base64-js: 1.5.1 chalk: 4.1.2 @@ -30630,7 +30914,7 @@ snapshots: react-syntax-highlighter@15.6.1(react@19.0.0): dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.27.6 highlight.js: 10.7.3 highlightjs-vue: 1.0.0 lowlight: 1.20.0 @@ -30640,7 +30924,7 @@ snapshots: react-textarea-autosize@8.5.9(@types/react@18.3.12)(react@19.0.0): dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.27.6 react: 19.0.0 use-composed-ref: 1.4.0(@types/react@18.3.12)(react@19.0.0) use-latest: 1.3.0(@types/react@18.3.12)(react@19.0.0) @@ -30649,7 +30933,7 @@ snapshots: react-transition-group@4.4.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.27.6 dom-helpers: 5.2.1 loose-envify: 1.4.0 prop-types: 15.8.1 @@ -31317,7 +31601,7 @@ snapshots: dependencies: color: 4.2.3 detect-libc: 2.0.3 - semver: 7.6.3 + semver: 7.7.2 optionalDependencies: '@img/sharp-darwin-arm64': 0.33.4 '@img/sharp-darwin-x64': 0.33.4 @@ -31678,40 +31962,47 @@ snapshots: dependencies: inline-style-parser: 0.2.4 - styled-jsx@5.1.1(@babel/core@7.26.0)(react@18.2.0): + styled-jsx@5.1.1(@babel/core@7.28.0)(react@18.2.0): dependencies: client-only: 0.0.1 react: 18.2.0 optionalDependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.0 - styled-jsx@5.1.1(@babel/core@7.26.0)(react@18.3.1): + styled-jsx@5.1.1(@babel/core@7.28.0)(react@18.3.1): dependencies: client-only: 0.0.1 react: 18.3.1 optionalDependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.0 - styled-jsx@5.1.6(@babel/core@7.26.0)(react@18.2.0): + styled-jsx@5.1.6(@babel/core@7.28.0)(react@18.3.1): dependencies: client-only: 0.0.1 - react: 18.2.0 + react: 18.3.1 optionalDependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.0 - styled-jsx@5.1.6(@babel/core@7.26.0)(react@18.3.1): + styled-jsx@5.1.6(@babel/core@7.28.0)(react@19.0.0): dependencies: client-only: 0.0.1 - react: 18.3.1 + react: 19.0.0 optionalDependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.0 + + styled-jsx@5.1.6(@babel/core@7.28.3)(react@18.2.0): + dependencies: + client-only: 0.0.1 + react: 18.2.0 + optionalDependencies: + '@babel/core': 7.28.3 - styled-jsx@5.1.6(@babel/core@7.26.0)(react@19.0.0): + styled-jsx@5.1.6(@babel/core@7.28.3)(react@19.0.0): dependencies: client-only: 0.0.1 react: 19.0.0 optionalDependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.3 stylis@4.2.0: {} @@ -31753,9 +32044,9 @@ snapshots: transitivePeerDependencies: - encoding - svix-react@1.13.0(@babel/core@7.26.0)(prop-types@15.8.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(svix@1.32.0(encoding@0.1.13)): + svix-react@1.13.0(@babel/core@7.28.3)(prop-types@15.8.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(svix@1.32.0(encoding@0.1.13)): dependencies: - iframe-resizer-react: 1.1.1(@babel/core@7.26.0)(prop-types@15.8.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + iframe-resizer-react: 1.1.1(@babel/core@7.28.3)(prop-types@15.8.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: 19.0.0 react-dom: 19.0.0(react@19.0.0) svix: 1.32.0(encoding@0.1.13) @@ -31802,7 +32093,7 @@ snapshots: tailwind-merge@2.3.0: dependencies: - '@babel/runtime': 7.24.7 + '@babel/runtime': 7.27.6 tailwind-merge@2.5.4: {} @@ -32069,8 +32360,6 @@ snapshots: tmpl@1.0.5: {} - to-fast-properties@2.0.0: {} - to-regex-range@5.0.1: dependencies: is-number: 7.0.0 From e1ae1a0a367efeefe7f23af4732d8c91c5268563 Mon Sep 17 00:00:00 2001 From: Konstantin Wohlwend Date: Tue, 19 Aug 2025 01:12:21 -0700 Subject: [PATCH 17/17] Transpile classes --- packages/stack-shared/package.json | 5 + .../js-serializer/serialization-helpers.ts | 1 - .../helpers/js-serializer/transpiler.test.ts | 996 ++++++++++++++++-- .../src/helpers/js-serializer/transpiler.ts | 51 +- pnpm-lock.yaml | 105 +- 5 files changed, 940 insertions(+), 218 deletions(-) diff --git a/packages/stack-shared/package.json b/packages/stack-shared/package.json index e28412b4c3..8d5519f2a3 100644 --- a/packages/stack-shared/package.json +++ b/packages/stack-shared/package.json @@ -77,6 +77,11 @@ "uuid": "^9.0.1" }, "devDependencies": { + "@babel/plugin-transform-class-properties": "^7.27.1", + "@babel/plugin-transform-class-static-block": "^7.28.3", + "@babel/plugin-transform-classes": "^7.28.3", + "@babel/plugin-transform-private-methods": "^7.27.1", + "@babel/plugin-transform-private-property-in-object": "^7.27.1", "@sentry/nextjs": "^8.40.0", "@simplewebauthn/types": "^11.0.0", "@types/babel-generator": "^6.25.8", diff --git a/packages/stack-shared/src/helpers/js-serializer/serialization-helpers.ts b/packages/stack-shared/src/helpers/js-serializer/serialization-helpers.ts index 167003ecd1..1e35d76adb 100644 --- a/packages/stack-shared/src/helpers/js-serializer/serialization-helpers.ts +++ b/packages/stack-shared/src/helpers/js-serializer/serialization-helpers.ts @@ -73,7 +73,6 @@ export function getSerializationHelpers() { if (isShallowEqual(Object.getOwnPropertyDescriptor(obj, key), descriptor)) { return; } - console.log("defining property", key, descriptor, Object.getOwnPropertyDescriptor(obj, key)); Object.defineProperty(obj, key, descriptor); }; diff --git a/packages/stack-shared/src/helpers/js-serializer/transpiler.test.ts b/packages/stack-shared/src/helpers/js-serializer/transpiler.test.ts index 38744df57b..d95c1e289a 100644 --- a/packages/stack-shared/src/helpers/js-serializer/transpiler.test.ts +++ b/packages/stack-shared/src/helpers/js-serializer/transpiler.test.ts @@ -1038,19 +1038,48 @@ describe('transpileJsToSerializableJs', () => { const output = transpileJsToSerializableJs(input); expect(getUserCode(output)).toMatchInlineSnapshot(` - " + "__STACK_SerializableJs.registerFunction(_toPrimitive, { + scope: () => ({}), + source: "function _toPrimitive(t,r){if(\\"object\\"!=typeof t||!t)return t;var e=t[Symbol.toPrimitive];if(void 0!==e){var i=e.call(t,r||\\"default\\");if(\\"object\\"!=typeof i)return i;throw new TypeError(\\"@@toPrimitive must return a primitive value.\\");}return(\\"string\\"===r?String:Number)(t);}" + }); const classVar = 'test'; - class MyClass { - method() { - return classVar; + let MyClass = /*#__PURE__*/__STACK_SerializableJs.registerFunction(function () { + function MyClass() { + _classCallCheck(this, MyClass); } - } - __STACK_SerializableJs.registerClass(MyClass, { + __STACK_SerializableJs.registerFunction(MyClass, { + scope: () => ({ + _classCallCheck, + MyClass + }), + source: "function MyClass(){__$getFromScope(\\"_classCallCheck\\")(this,__$getFromScope(\\"MyClass\\"));}" + }); + __STACK_SerializableJs.registerFunction(MyClass, { + scope: () => ({ + _classCallCheck, + MyClass + }), + source: "function MyClass(){__$getFromScope(\\"_classCallCheck\\")(this,__$getFromScope(\\"MyClass\\"));}" + }); + return _createClass(MyClass, [{ + key: "method", + value: __STACK_SerializableJs.registerFunction(function method() { + return classVar; + }, { + scope: () => ({ + classVar + }), + source: "function method(){return __$getFromScope(\\"classVar\\");}" + }) + }]); + }, { scope: () => ({ + _classCallCheck, + _createClass, classVar }), - source: "class MyClass{method(){return __$getFromScope(\\"classVar\\");}}" - })" + source: "(function(){function MyClass(){__$getFromScope(\\"_classCallCheck\\")(this,MyClass);}__STACK_SerializableJs.registerFunction(MyClass,{scope:()=>({_classCallCheck:__$getFromScope(\\"_classCallCheck\\"),MyClass}),source:\\"function MyClass(){__$getFromScope(\\\\\\"_classCallCheck\\\\\\")(this,__$getFromScope(\\\\\\"MyClass\\\\\\"));}\\"});return __$getFromScope(\\"_createClass\\")(MyClass,[{key:\\"method\\",value:__STACK_SerializableJs.registerFunction(function method(){return __$getFromScope(\\"classVar\\");},{scope:()=>({classVar:__$getFromScope(\\"classVar\\")}),source:\\"function method(){return __$getFromScope(\\\\\\"classVar\\\\\\");}\\"})}]);})" + })()" `); }); @@ -1064,22 +1093,27 @@ describe('transpileJsToSerializableJs', () => { const output = transpileJsToSerializableJs(input); expect(getUserCode(output)).toMatchInlineSnapshot(` - " + "__STACK_SerializableJs.registerFunction(_toPrimitive, { + scope: () => ({}), + source: "function _toPrimitive(t,r){if(\\"object\\"!=typeof t||!t)return t;var e=t[Symbol.toPrimitive];if(void 0!==e){var i=e.call(t,r||\\"default\\");if(\\"object\\"!=typeof i)return i;throw new TypeError(\\"@@toPrimitive must return a primitive value.\\");}return(\\"string\\"===r?String:Number)(t);}" + }); const x = 1; - class MyClass { - method = __STACK_SerializableJs.registerFunction(() => x, { + let MyClass = /*#__PURE__*/_createClass(__STACK_SerializableJs.registerFunction(function MyClass() { + _classCallCheck(this, MyClass); + _defineProperty(this, "method", __STACK_SerializableJs.registerFunction(() => x, { scope: () => ({ x }), source: "()=>__$getFromScope(\\"x\\")" - }); - } - __STACK_SerializableJs.registerClass(MyClass, { + })); + }, { scope: () => ({ + _classCallCheck, + _defineProperty, x }), - source: "class MyClass{method=__STACK_SerializableJs.registerFunction(()=>__$getFromScope(\\"x\\"),{scope:()=>({x:__$getFromScope(\\"x\\")}),source:\\"()=>__$getFromScope(\\\\\\"x\\\\\\")\\"});}" - })" + source: "function MyClass(){__$getFromScope(\\"_classCallCheck\\")(this,MyClass);__$getFromScope(\\"_defineProperty\\")(this,\\"method\\",__STACK_SerializableJs.registerFunction(()=>__$getFromScope(\\"x\\"),{scope:()=>({x:__$getFromScope(\\"x\\")}),source:\\"()=>__$getFromScope(\\\\\\"x\\\\\\")\\"}));}" + }))" `); }); @@ -1095,19 +1129,48 @@ describe('transpileJsToSerializableJs', () => { const output = transpileJsToSerializableJs(input); expect(getUserCode(output)).toMatchInlineSnapshot(` - " + "__STACK_SerializableJs.registerFunction(_toPrimitive, { + scope: () => ({}), + source: "function _toPrimitive(t,r){if(\\"object\\"!=typeof t||!t)return t;var e=t[Symbol.toPrimitive];if(void 0!==e){var i=e.call(t,r||\\"default\\");if(\\"object\\"!=typeof i)return i;throw new TypeError(\\"@@toPrimitive must return a primitive value.\\");}return(\\"string\\"===r?String:Number)(t);}" + }); const staticVar = 'static'; - class MyClass { - static staticMethod() { - return staticVar; + let MyClass = /*#__PURE__*/__STACK_SerializableJs.registerFunction(function () { + function MyClass() { + _classCallCheck(this, MyClass); } - } - __STACK_SerializableJs.registerClass(MyClass, { + __STACK_SerializableJs.registerFunction(MyClass, { + scope: () => ({ + _classCallCheck, + MyClass + }), + source: "function MyClass(){__$getFromScope(\\"_classCallCheck\\")(this,__$getFromScope(\\"MyClass\\"));}" + }); + __STACK_SerializableJs.registerFunction(MyClass, { + scope: () => ({ + _classCallCheck, + MyClass + }), + source: "function MyClass(){__$getFromScope(\\"_classCallCheck\\")(this,__$getFromScope(\\"MyClass\\"));}" + }); + return _createClass(MyClass, null, [{ + key: "staticMethod", + value: __STACK_SerializableJs.registerFunction(function staticMethod() { + return staticVar; + }, { + scope: () => ({ + staticVar + }), + source: "function staticMethod(){return __$getFromScope(\\"staticVar\\");}" + }) + }]); + }, { scope: () => ({ + _classCallCheck, + _createClass, staticVar }), - source: "class MyClass{static staticMethod(){return __$getFromScope(\\"staticVar\\");}}" - })" + source: "(function(){function MyClass(){__$getFromScope(\\"_classCallCheck\\")(this,MyClass);}__STACK_SerializableJs.registerFunction(MyClass,{scope:()=>({_classCallCheck:__$getFromScope(\\"_classCallCheck\\"),MyClass}),source:\\"function MyClass(){__$getFromScope(\\\\\\"_classCallCheck\\\\\\")(this,__$getFromScope(\\\\\\"MyClass\\\\\\"));}\\"});return __$getFromScope(\\"_createClass\\")(MyClass,null,[{key:\\"staticMethod\\",value:__STACK_SerializableJs.registerFunction(function staticMethod(){return __$getFromScope(\\"staticVar\\");},{scope:()=>({staticVar:__$getFromScope(\\"staticVar\\")}),source:\\"function staticMethod(){return __$getFromScope(\\\\\\"staticVar\\\\\\");}\\"})}]);})" + })()" `); }); @@ -1125,40 +1188,68 @@ describe('transpileJsToSerializableJs', () => { expect(getUserCode(output)).toMatchInlineSnapshot(` " const data = 'async data'; - class MyClass { - fetchData() { - return _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(__STACK_SerializableJs.registerFunction(function _callee() { - return _regeneratorRuntime.wrap(__STACK_SerializableJs.registerFunction(function (_context) { - while (1) switch (_context.prev = _context.next) { - case 0: - return _context.abrupt("return", data); - case 1: - case "end": - return _context.stop(); - } + let MyClass = /*#__PURE__*/__STACK_SerializableJs.registerFunction(function () { + function MyClass() { + _classCallCheck(this, MyClass); + } + __STACK_SerializableJs.registerFunction(MyClass, { + scope: () => ({ + _classCallCheck, + MyClass + }), + source: "function MyClass(){__$getFromScope(\\"_classCallCheck\\")(this,__$getFromScope(\\"MyClass\\"));}" + }); + __STACK_SerializableJs.registerFunction(MyClass, { + scope: () => ({ + _classCallCheck, + MyClass + }), + source: "function MyClass(){__$getFromScope(\\"_classCallCheck\\")(this,__$getFromScope(\\"MyClass\\"));}" + }); + return _createClass(MyClass, [{ + key: "fetchData", + value: __STACK_SerializableJs.registerFunction(function fetchData() { + return _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(__STACK_SerializableJs.registerFunction(function _callee() { + return _regeneratorRuntime.wrap(__STACK_SerializableJs.registerFunction(function (_context) { + while (1) switch (_context.prev = _context.next) { + case 0: + return _context.abrupt("return", data); + case 1: + case "end": + return _context.stop(); + } + }, { + scope: () => ({ + data + }), + source: "(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.abrupt(\\"return\\",__$getFromScope(\\"data\\"));case 1:case\\"end\\":return _context.stop();}})" + }), _callee); }, { scope: () => ({ + _regeneratorRuntime, data }), - source: "(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.abrupt(\\"return\\",__$getFromScope(\\"data\\"));case 1:case\\"end\\":return _context.stop();}})" - }), _callee); + source: "function _callee(){return __$getFromScope(\\"_regeneratorRuntime\\").wrap(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.abrupt(\\"return\\",__$getFromScope(\\"data\\"));case 1:case\\"end\\":return _context.stop();}},{scope:()=>({data:__$getFromScope(\\"data\\")}),source:\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.abrupt(\\\\\\"return\\\\\\",__$getFromScope(\\\\\\"data\\\\\\"));case 1:case\\\\\\"end\\\\\\":return _context.stop();}})\\"}),_callee);}" + })))(); }, { scope: () => ({ + _asyncToGenerator, _regeneratorRuntime, data }), - source: "function _callee(){return __$getFromScope(\\"_regeneratorRuntime\\").wrap(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.abrupt(\\"return\\",__$getFromScope(\\"data\\"));case 1:case\\"end\\":return _context.stop();}},{scope:()=>({data:__$getFromScope(\\"data\\")}),source:\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.abrupt(\\\\\\"return\\\\\\",__$getFromScope(\\\\\\"data\\\\\\"));case 1:case\\\\\\"end\\\\\\":return _context.stop();}})\\"}),_callee);}" - })))(); - } - } - __STACK_SerializableJs.registerClass(MyClass, { + source: "function fetchData(){return __$getFromScope(\\"_asyncToGenerator\\")(__$getFromScope(\\"_regeneratorRuntime\\").mark(__STACK_SerializableJs.registerFunction(function _callee(){return __$getFromScope(\\"_regeneratorRuntime\\").wrap(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.abrupt(\\"return\\",__$getFromScope(\\"data\\"));case 1:case\\"end\\":return _context.stop();}},{scope:()=>({data:__$getFromScope(\\"data\\")}),source:\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.abrupt(\\\\\\"return\\\\\\",__$getFromScope(\\\\\\"data\\\\\\"));case 1:case\\\\\\"end\\\\\\":return _context.stop();}})\\"}),_callee);},{scope:()=>({_regeneratorRuntime:__$getFromScope(\\"_regeneratorRuntime\\"),data:__$getFromScope(\\"data\\")}),source:\\"function _callee(){return __$getFromScope(\\\\\\"_regeneratorRuntime\\\\\\").wrap(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.abrupt(\\\\\\"return\\\\\\",__$getFromScope(\\\\\\"data\\\\\\"));case 1:case\\\\\\"end\\\\\\":return _context.stop();}},{scope:()=>({data:__$getFromScope(\\\\\\"data\\\\\\")}),source:\\\\\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.abrupt(\\\\\\\\\\\\\\"return\\\\\\\\\\\\\\",__$getFromScope(\\\\\\\\\\\\\\"data\\\\\\\\\\\\\\"));case 1:case\\\\\\\\\\\\\\"end\\\\\\\\\\\\\\":return _context.stop();}})\\\\\\"}),_callee);}\\"})))();}" + }) + }]); + }, { scope: () => ({ + _classCallCheck, + _createClass, _asyncToGenerator, _regeneratorRuntime, data }), - source: "class MyClass{fetchData(){return __$getFromScope(\\"_asyncToGenerator\\")(__$getFromScope(\\"_regeneratorRuntime\\").mark(__STACK_SerializableJs.registerFunction(function _callee(){return __$getFromScope(\\"_regeneratorRuntime\\").wrap(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.abrupt(\\"return\\",__$getFromScope(\\"data\\"));case 1:case\\"end\\":return _context.stop();}},{scope:()=>({data:__$getFromScope(\\"data\\")}),source:\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.abrupt(\\\\\\"return\\\\\\",__$getFromScope(\\\\\\"data\\\\\\"));case 1:case\\\\\\"end\\\\\\":return _context.stop();}})\\"}),_callee);},{scope:()=>({_regeneratorRuntime:__$getFromScope(\\"_regeneratorRuntime\\"),data:__$getFromScope(\\"data\\")}),source:\\"function _callee(){return __$getFromScope(\\\\\\"_regeneratorRuntime\\\\\\").wrap(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.abrupt(\\\\\\"return\\\\\\",__$getFromScope(\\\\\\"data\\\\\\"));case 1:case\\\\\\"end\\\\\\":return _context.stop();}},{scope:()=>({data:__$getFromScope(\\\\\\"data\\\\\\")}),source:\\\\\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.abrupt(\\\\\\\\\\\\\\"return\\\\\\\\\\\\\\",__$getFromScope(\\\\\\\\\\\\\\"data\\\\\\\\\\\\\\"));case 1:case\\\\\\\\\\\\\\"end\\\\\\\\\\\\\\":return _context.stop();}})\\\\\\"}),_callee);}\\"})))();}}" - })" + source: "(function(){function MyClass(){__$getFromScope(\\"_classCallCheck\\")(this,MyClass);}__STACK_SerializableJs.registerFunction(MyClass,{scope:()=>({_classCallCheck:__$getFromScope(\\"_classCallCheck\\"),MyClass}),source:\\"function MyClass(){__$getFromScope(\\\\\\"_classCallCheck\\\\\\")(this,__$getFromScope(\\\\\\"MyClass\\\\\\"));}\\"});return __$getFromScope(\\"_createClass\\")(MyClass,[{key:\\"fetchData\\",value:__STACK_SerializableJs.registerFunction(function fetchData(){return __$getFromScope(\\"_asyncToGenerator\\")(__$getFromScope(\\"_regeneratorRuntime\\").mark(__STACK_SerializableJs.registerFunction(function _callee(){return __$getFromScope(\\"_regeneratorRuntime\\").wrap(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.abrupt(\\"return\\",__$getFromScope(\\"data\\"));case 1:case\\"end\\":return _context.stop();}},{scope:()=>({data:__$getFromScope(\\"data\\")}),source:\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.abrupt(\\\\\\"return\\\\\\",__$getFromScope(\\\\\\"data\\\\\\"));case 1:case\\\\\\"end\\\\\\":return _context.stop();}})\\"}),_callee);},{scope:()=>({_regeneratorRuntime:__$getFromScope(\\"_regeneratorRuntime\\"),data:__$getFromScope(\\"data\\")}),source:\\"function _callee(){return __$getFromScope(\\\\\\"_regeneratorRuntime\\\\\\").wrap(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.abrupt(\\\\\\"return\\\\\\",__$getFromScope(\\\\\\"data\\\\\\"));case 1:case\\\\\\"end\\\\\\":return _context.stop();}},{scope:()=>({data:__$getFromScope(\\\\\\"data\\\\\\")}),source:\\\\\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.abrupt(\\\\\\\\\\\\\\"return\\\\\\\\\\\\\\",__$getFromScope(\\\\\\\\\\\\\\"data\\\\\\\\\\\\\\"));case 1:case\\\\\\\\\\\\\\"end\\\\\\\\\\\\\\":return _context.stop();}})\\\\\\"}),_callee);}\\"})))();},{scope:()=>({_asyncToGenerator:__$getFromScope(\\"_asyncToGenerator\\"),_regeneratorRuntime:__$getFromScope(\\"_regeneratorRuntime\\"),data:__$getFromScope(\\"data\\")}),source:\\"function fetchData(){return __$getFromScope(\\\\\\"_asyncToGenerator\\\\\\")(__$getFromScope(\\\\\\"_regeneratorRuntime\\\\\\").mark(__STACK_SerializableJs.registerFunction(function _callee(){return __$getFromScope(\\\\\\"_regeneratorRuntime\\\\\\").wrap(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.abrupt(\\\\\\"return\\\\\\",__$getFromScope(\\\\\\"data\\\\\\"));case 1:case\\\\\\"end\\\\\\":return _context.stop();}},{scope:()=>({data:__$getFromScope(\\\\\\"data\\\\\\")}),source:\\\\\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.abrupt(\\\\\\\\\\\\\\"return\\\\\\\\\\\\\\",__$getFromScope(\\\\\\\\\\\\\\"data\\\\\\\\\\\\\\"));case 1:case\\\\\\\\\\\\\\"end\\\\\\\\\\\\\\":return _context.stop();}})\\\\\\"}),_callee);},{scope:()=>({_regeneratorRuntime:__$getFromScope(\\\\\\"_regeneratorRuntime\\\\\\"),data:__$getFromScope(\\\\\\"data\\\\\\")}),source:\\\\\\"function _callee(){return __$getFromScope(\\\\\\\\\\\\\\"_regeneratorRuntime\\\\\\\\\\\\\\").wrap(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.abrupt(\\\\\\\\\\\\\\"return\\\\\\\\\\\\\\",__$getFromScope(\\\\\\\\\\\\\\"data\\\\\\\\\\\\\\"));case 1:case\\\\\\\\\\\\\\"end\\\\\\\\\\\\\\":return _context.stop();}},{scope:()=>({data:__$getFromScope(\\\\\\\\\\\\\\"data\\\\\\\\\\\\\\")}),source:\\\\\\\\\\\\\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.abrupt(\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\"return\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\",__$getFromScope(\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\"data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\"));case 1:case\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\"end\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\":return _context.stop();}})\\\\\\\\\\\\\\"}),_callee);}\\\\\\"})))();}\\"})}]);})" + })()" `); }); @@ -1176,39 +1267,66 @@ describe('transpileJsToSerializableJs', () => { expect(getUserCode(output)).toMatchInlineSnapshot(` " const items = [1, 2, 3]; - class MyClass { - generator() { - return /*#__PURE__*/_regeneratorRuntime.mark(__STACK_SerializableJs.registerFunction(function _callee() { - return _regeneratorRuntime.wrap(__STACK_SerializableJs.registerFunction(function (_context) { - while (1) switch (_context.prev = _context.next) { - case 0: - return _context.delegateYield(items, "t0", 1); - case 1: - case "end": - return _context.stop(); - } + let MyClass = /*#__PURE__*/__STACK_SerializableJs.registerFunction(function () { + function MyClass() { + _classCallCheck(this, MyClass); + } + __STACK_SerializableJs.registerFunction(MyClass, { + scope: () => ({ + _classCallCheck, + MyClass + }), + source: "function MyClass(){__$getFromScope(\\"_classCallCheck\\")(this,__$getFromScope(\\"MyClass\\"));}" + }); + __STACK_SerializableJs.registerFunction(MyClass, { + scope: () => ({ + _classCallCheck, + MyClass + }), + source: "function MyClass(){__$getFromScope(\\"_classCallCheck\\")(this,__$getFromScope(\\"MyClass\\"));}" + }); + return _createClass(MyClass, [{ + key: "generator", + value: __STACK_SerializableJs.registerFunction(function generator() { + return /*#__PURE__*/_regeneratorRuntime.mark(__STACK_SerializableJs.registerFunction(function _callee() { + return _regeneratorRuntime.wrap(__STACK_SerializableJs.registerFunction(function (_context) { + while (1) switch (_context.prev = _context.next) { + case 0: + return _context.delegateYield(items, "t0", 1); + case 1: + case "end": + return _context.stop(); + } + }, { + scope: () => ({ + items + }), + source: "(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.delegateYield(__$getFromScope(\\"items\\"),\\"t0\\",1);case 1:case\\"end\\":return _context.stop();}})" + }), _callee); }, { scope: () => ({ + _regeneratorRuntime, items }), - source: "(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.delegateYield(__$getFromScope(\\"items\\"),\\"t0\\",1);case 1:case\\"end\\":return _context.stop();}})" - }), _callee); + source: "function _callee(){return __$getFromScope(\\"_regeneratorRuntime\\").wrap(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.delegateYield(__$getFromScope(\\"items\\"),\\"t0\\",1);case 1:case\\"end\\":return _context.stop();}},{scope:()=>({items:__$getFromScope(\\"items\\")}),source:\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.delegateYield(__$getFromScope(\\\\\\"items\\\\\\"),\\\\\\"t0\\\\\\",1);case 1:case\\\\\\"end\\\\\\":return _context.stop();}})\\"}),_callee);}" + }))(); }, { scope: () => ({ _regeneratorRuntime, items }), - source: "function _callee(){return __$getFromScope(\\"_regeneratorRuntime\\").wrap(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.delegateYield(__$getFromScope(\\"items\\"),\\"t0\\",1);case 1:case\\"end\\":return _context.stop();}},{scope:()=>({items:__$getFromScope(\\"items\\")}),source:\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.delegateYield(__$getFromScope(\\\\\\"items\\\\\\"),\\\\\\"t0\\\\\\",1);case 1:case\\\\\\"end\\\\\\":return _context.stop();}})\\"}),_callee);}" - }))(); - } - } - __STACK_SerializableJs.registerClass(MyClass, { + source: "function generator(){return __$getFromScope(\\"_regeneratorRuntime\\").mark(__STACK_SerializableJs.registerFunction(function _callee(){return __$getFromScope(\\"_regeneratorRuntime\\").wrap(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.delegateYield(__$getFromScope(\\"items\\"),\\"t0\\",1);case 1:case\\"end\\":return _context.stop();}},{scope:()=>({items:__$getFromScope(\\"items\\")}),source:\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.delegateYield(__$getFromScope(\\\\\\"items\\\\\\"),\\\\\\"t0\\\\\\",1);case 1:case\\\\\\"end\\\\\\":return _context.stop();}})\\"}),_callee);},{scope:()=>({_regeneratorRuntime:__$getFromScope(\\"_regeneratorRuntime\\"),items:__$getFromScope(\\"items\\")}),source:\\"function _callee(){return __$getFromScope(\\\\\\"_regeneratorRuntime\\\\\\").wrap(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.delegateYield(__$getFromScope(\\\\\\"items\\\\\\"),\\\\\\"t0\\\\\\",1);case 1:case\\\\\\"end\\\\\\":return _context.stop();}},{scope:()=>({items:__$getFromScope(\\\\\\"items\\\\\\")}),source:\\\\\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.delegateYield(__$getFromScope(\\\\\\\\\\\\\\"items\\\\\\\\\\\\\\"),\\\\\\\\\\\\\\"t0\\\\\\\\\\\\\\",1);case 1:case\\\\\\\\\\\\\\"end\\\\\\\\\\\\\\":return _context.stop();}})\\\\\\"}),_callee);}\\"}))();}" + }) + }]); + }, { scope: () => ({ + _classCallCheck, + _createClass, _regeneratorRuntime, items }), - source: "class MyClass{generator(){return __$getFromScope(\\"_regeneratorRuntime\\").mark(__STACK_SerializableJs.registerFunction(function _callee(){return __$getFromScope(\\"_regeneratorRuntime\\").wrap(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.delegateYield(__$getFromScope(\\"items\\"),\\"t0\\",1);case 1:case\\"end\\":return _context.stop();}},{scope:()=>({items:__$getFromScope(\\"items\\")}),source:\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.delegateYield(__$getFromScope(\\\\\\"items\\\\\\"),\\\\\\"t0\\\\\\",1);case 1:case\\\\\\"end\\\\\\":return _context.stop();}})\\"}),_callee);},{scope:()=>({_regeneratorRuntime:__$getFromScope(\\"_regeneratorRuntime\\"),items:__$getFromScope(\\"items\\")}),source:\\"function _callee(){return __$getFromScope(\\\\\\"_regeneratorRuntime\\\\\\").wrap(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.delegateYield(__$getFromScope(\\\\\\"items\\\\\\"),\\\\\\"t0\\\\\\",1);case 1:case\\\\\\"end\\\\\\":return _context.stop();}},{scope:()=>({items:__$getFromScope(\\\\\\"items\\\\\\")}),source:\\\\\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.delegateYield(__$getFromScope(\\\\\\\\\\\\\\"items\\\\\\\\\\\\\\"),\\\\\\\\\\\\\\"t0\\\\\\\\\\\\\\",1);case 1:case\\\\\\\\\\\\\\"end\\\\\\\\\\\\\\":return _context.stop();}})\\\\\\"}),_callee);}\\"}))();}}" - })" + source: "(function(){function MyClass(){__$getFromScope(\\"_classCallCheck\\")(this,MyClass);}__STACK_SerializableJs.registerFunction(MyClass,{scope:()=>({_classCallCheck:__$getFromScope(\\"_classCallCheck\\"),MyClass}),source:\\"function MyClass(){__$getFromScope(\\\\\\"_classCallCheck\\\\\\")(this,__$getFromScope(\\\\\\"MyClass\\\\\\"));}\\"});return __$getFromScope(\\"_createClass\\")(MyClass,[{key:\\"generator\\",value:__STACK_SerializableJs.registerFunction(function generator(){return __$getFromScope(\\"_regeneratorRuntime\\").mark(__STACK_SerializableJs.registerFunction(function _callee(){return __$getFromScope(\\"_regeneratorRuntime\\").wrap(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.delegateYield(__$getFromScope(\\"items\\"),\\"t0\\",1);case 1:case\\"end\\":return _context.stop();}},{scope:()=>({items:__$getFromScope(\\"items\\")}),source:\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.delegateYield(__$getFromScope(\\\\\\"items\\\\\\"),\\\\\\"t0\\\\\\",1);case 1:case\\\\\\"end\\\\\\":return _context.stop();}})\\"}),_callee);},{scope:()=>({_regeneratorRuntime:__$getFromScope(\\"_regeneratorRuntime\\"),items:__$getFromScope(\\"items\\")}),source:\\"function _callee(){return __$getFromScope(\\\\\\"_regeneratorRuntime\\\\\\").wrap(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.delegateYield(__$getFromScope(\\\\\\"items\\\\\\"),\\\\\\"t0\\\\\\",1);case 1:case\\\\\\"end\\\\\\":return _context.stop();}},{scope:()=>({items:__$getFromScope(\\\\\\"items\\\\\\")}),source:\\\\\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.delegateYield(__$getFromScope(\\\\\\\\\\\\\\"items\\\\\\\\\\\\\\"),\\\\\\\\\\\\\\"t0\\\\\\\\\\\\\\",1);case 1:case\\\\\\\\\\\\\\"end\\\\\\\\\\\\\\":return _context.stop();}})\\\\\\"}),_callee);}\\"}))();},{scope:()=>({_regeneratorRuntime:__$getFromScope(\\"_regeneratorRuntime\\"),items:__$getFromScope(\\"items\\")}),source:\\"function generator(){return __$getFromScope(\\\\\\"_regeneratorRuntime\\\\\\").mark(__STACK_SerializableJs.registerFunction(function _callee(){return __$getFromScope(\\\\\\"_regeneratorRuntime\\\\\\").wrap(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.delegateYield(__$getFromScope(\\\\\\"items\\\\\\"),\\\\\\"t0\\\\\\",1);case 1:case\\\\\\"end\\\\\\":return _context.stop();}},{scope:()=>({items:__$getFromScope(\\\\\\"items\\\\\\")}),source:\\\\\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.delegateYield(__$getFromScope(\\\\\\\\\\\\\\"items\\\\\\\\\\\\\\"),\\\\\\\\\\\\\\"t0\\\\\\\\\\\\\\",1);case 1:case\\\\\\\\\\\\\\"end\\\\\\\\\\\\\\":return _context.stop();}})\\\\\\"}),_callee);},{scope:()=>({_regeneratorRuntime:__$getFromScope(\\\\\\"_regeneratorRuntime\\\\\\"),items:__$getFromScope(\\\\\\"items\\\\\\")}),source:\\\\\\"function _callee(){return __$getFromScope(\\\\\\\\\\\\\\"_regeneratorRuntime\\\\\\\\\\\\\\").wrap(__STACK_SerializableJs.registerFunction(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.delegateYield(__$getFromScope(\\\\\\\\\\\\\\"items\\\\\\\\\\\\\\"),\\\\\\\\\\\\\\"t0\\\\\\\\\\\\\\",1);case 1:case\\\\\\\\\\\\\\"end\\\\\\\\\\\\\\":return _context.stop();}},{scope:()=>({items:__$getFromScope(\\\\\\\\\\\\\\"items\\\\\\\\\\\\\\")}),source:\\\\\\\\\\\\\\"(function(_context){while(1)switch(_context.prev=_context.next){case 0:return _context.delegateYield(__$getFromScope(\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\"items\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\"),\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\"t0\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\",1);case 1:case\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\"end\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\":return _context.stop();}})\\\\\\\\\\\\\\"}),_callee);}\\\\\\"}))();}\\"})}]);})" + })()" `); }); @@ -1224,21 +1342,50 @@ describe('transpileJsToSerializableJs', () => { const output = transpileJsToSerializableJs(input); expect(getUserCode(output)).toMatchInlineSnapshot(` - " + "__STACK_SerializableJs.registerFunction(_toPrimitive, { + scope: () => ({}), + source: "function _toPrimitive(t,r){if(\\"object\\"!=typeof t||!t)return t;var e=t[Symbol.toPrimitive];if(void 0!==e){var i=e.call(t,r||\\"default\\");if(\\"object\\"!=typeof i)return i;throw new TypeError(\\"@@toPrimitive must return a primitive value.\\");}return(\\"string\\"===r?String:Number)(t);}" + }); const store = { value: 42 }; - class MyClass { - get prop() { - return store.value; + let MyClass = /*#__PURE__*/__STACK_SerializableJs.registerFunction(function () { + function MyClass() { + _classCallCheck(this, MyClass); } - } - __STACK_SerializableJs.registerClass(MyClass, { + __STACK_SerializableJs.registerFunction(MyClass, { + scope: () => ({ + _classCallCheck, + MyClass + }), + source: "function MyClass(){__$getFromScope(\\"_classCallCheck\\")(this,__$getFromScope(\\"MyClass\\"));}" + }); + __STACK_SerializableJs.registerFunction(MyClass, { + scope: () => ({ + _classCallCheck, + MyClass + }), + source: "function MyClass(){__$getFromScope(\\"_classCallCheck\\")(this,__$getFromScope(\\"MyClass\\"));}" + }); + return _createClass(MyClass, [{ + key: "prop", + get: __STACK_SerializableJs.registerFunction(function () { + return store.value; + }, { + scope: () => ({ + store + }), + source: "(function(){return __$getFromScope(\\"store\\").value;})" + }) + }]); + }, { scope: () => ({ + _classCallCheck, + _createClass, store }), - source: "class MyClass{get prop(){return __$getFromScope(\\"store\\").value;}}" - })" + source: "(function(){function MyClass(){__$getFromScope(\\"_classCallCheck\\")(this,MyClass);}__STACK_SerializableJs.registerFunction(MyClass,{scope:()=>({_classCallCheck:__$getFromScope(\\"_classCallCheck\\"),MyClass}),source:\\"function MyClass(){__$getFromScope(\\\\\\"_classCallCheck\\\\\\")(this,__$getFromScope(\\\\\\"MyClass\\\\\\"));}\\"});return __$getFromScope(\\"_createClass\\")(MyClass,[{key:\\"prop\\",get:__STACK_SerializableJs.registerFunction(function(){return __$getFromScope(\\"store\\").value;},{scope:()=>({store:__$getFromScope(\\"store\\")}),source:\\"(function(){return __$getFromScope(\\\\\\"store\\\\\\").value;})\\"})}]);})" + })()" `); }); @@ -1254,17 +1401,45 @@ describe('transpileJsToSerializableJs', () => { const output = transpileJsToSerializableJs(input); expect(getUserCode(output)).toMatchInlineSnapshot(` - " + "__STACK_SerializableJs.registerFunction(_toPrimitive, { + scope: () => ({}), + source: "function _toPrimitive(t,r){if(\\"object\\"!=typeof t||!t)return t;var e=t[Symbol.toPrimitive];if(void 0!==e){var i=e.call(t,r||\\"default\\");if(\\"object\\"!=typeof i)return i;throw new TypeError(\\"@@toPrimitive must return a primitive value.\\");}return(\\"string\\"===r?String:Number)(t);}" + }); let stored = 0; - class MyClass { - set prop(v) { - stored = v; + let MyClass = /*#__PURE__*/__STACK_SerializableJs.registerFunction(function () { + function MyClass() { + _classCallCheck(this, MyClass); } - } - __STACK_SerializableJs.registerClass(MyClass, { - scope: () => ({}), - source: "class MyClass{set prop(v){stored=v;}}" - })" + __STACK_SerializableJs.registerFunction(MyClass, { + scope: () => ({ + _classCallCheck, + MyClass + }), + source: "function MyClass(){__$getFromScope(\\"_classCallCheck\\")(this,__$getFromScope(\\"MyClass\\"));}" + }); + __STACK_SerializableJs.registerFunction(MyClass, { + scope: () => ({ + _classCallCheck, + MyClass + }), + source: "function MyClass(){__$getFromScope(\\"_classCallCheck\\")(this,__$getFromScope(\\"MyClass\\"));}" + }); + return _createClass(MyClass, [{ + key: "prop", + set: __STACK_SerializableJs.registerFunction(function (v) { + stored = v; + }, { + scope: () => ({}), + source: "(function(v){stored=v;})" + }) + }]); + }, { + scope: () => ({ + _classCallCheck, + _createClass + }), + source: "(function(){function MyClass(){__$getFromScope(\\"_classCallCheck\\")(this,MyClass);}__STACK_SerializableJs.registerFunction(MyClass,{scope:()=>({_classCallCheck:__$getFromScope(\\"_classCallCheck\\"),MyClass}),source:\\"function MyClass(){__$getFromScope(\\\\\\"_classCallCheck\\\\\\")(this,__$getFromScope(\\\\\\"MyClass\\\\\\"));}\\"});return __$getFromScope(\\"_createClass\\")(MyClass,[{key:\\"prop\\",set:__STACK_SerializableJs.registerFunction(function(v){stored=v;},{scope:()=>({}),source:\\"(function(v){stored=v;})\\"})}]);})" + })()" `); }); @@ -1280,18 +1455,31 @@ describe('transpileJsToSerializableJs', () => { const output = transpileJsToSerializableJs(input); expect(getUserCode(output)).toMatchInlineSnapshot(` - " + "__STACK_SerializableJs.registerFunction(_checkPrivateRedeclaration, { + scope: () => ({}), + source: "function _checkPrivateRedeclaration(e,t){if(t.has(e))throw new TypeError(\\"Cannot initialize the same private elements twice on an object\\");}" + }); const secret = 'private'; - class MyClass { - #privateMethod() { - return secret; - } + var _MyClass_brand = /*#__PURE__*/new WeakSet(); + let MyClass = /*#__PURE__*/_createClass(__STACK_SerializableJs.registerFunction(function MyClass() { + _classCallCheck(this, MyClass); + _classPrivateMethodInitSpec(this, _MyClass_brand); + }, { + scope: () => ({ + _classCallCheck, + _classPrivateMethodInitSpec, + _MyClass_brand + }), + source: "function MyClass(){__$getFromScope(\\"_classCallCheck\\")(this,MyClass);__$getFromScope(\\"_classPrivateMethodInitSpec\\")(this,__$getFromScope(\\"_MyClass_brand\\"));}" + })); + function _privateMethod() { + return secret; } - __STACK_SerializableJs.registerClass(MyClass, { + __STACK_SerializableJs.registerFunction(_privateMethod, { scope: () => ({ secret }), - source: "class MyClass{#privateMethod(){return __$getFromScope(\\"secret\\");}}" + source: "function _privateMethod(){return __$getFromScope(\\"secret\\");}" })" `); }); @@ -1308,19 +1496,21 @@ describe('transpileJsToSerializableJs', () => { const output = transpileJsToSerializableJs(input); expect(getUserCode(output)).toMatchInlineSnapshot(` - " + "__STACK_SerializableJs.registerFunction(_classCallCheck, { + scope: () => ({}), + source: "function _classCallCheck(a,n){if(!(a instanceof n))throw new TypeError(\\"Cannot call a class as a function\\");}" + }); const defaultValue = 100; - class MyClass { - constructor() { - this.value = defaultValue; - } - } - __STACK_SerializableJs.registerClass(MyClass, { + let MyClass = /*#__PURE__*/_createClass(__STACK_SerializableJs.registerFunction(function MyClass() { + _classCallCheck(this, MyClass); + this.value = defaultValue; + }, { scope: () => ({ + _classCallCheck, defaultValue }), - source: "class MyClass{constructor(){this.value=__$getFromScope(\\"defaultValue\\");}}" - })" + source: "function MyClass(){__$getFromScope(\\"_classCallCheck\\")(this,MyClass);this.value=__$getFromScope(\\"defaultValue\\");}" + }))" `); }); }); @@ -1857,19 +2047,48 @@ describe('transpileJsToSerializableJs', () => { }), source: "(function(){var _ref=__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__$getFromScope(\\"_asyncToGenerator\\"))(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__$getFromScope(\\"_regeneratorRuntime\\")).mark)(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.registerFunction(function _callee(){return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__$getFromScope(\\"_regeneratorRuntime\\")).wrap)(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.registerFunction(function(_context){while(__STACK_SerializableJs.ensureSerializable(1))switch(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).prev=__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).next))){case __STACK_SerializableJs.ensureSerializable(0):return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).abrupt)(__STACK_SerializableJs.ensureSerializable(\\"return\\"),__STACK_SerializableJs.ensureSerializable(3)));case __STACK_SerializableJs.ensureSerializable(1):case __STACK_SerializableJs.ensureSerializable(\\"end\\"):return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).stop)());}},{scope:()=>({}),source:\\"(function(_context){while(__STACK_SerializableJs.ensureSerializable(1))switch(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).prev=__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).next))){case __STACK_SerializableJs.ensureSerializable(0):return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).abrupt)(__STACK_SerializableJs.ensureSerializable(\\\\\\"return\\\\\\"),__STACK_SerializableJs.ensureSerializable(3)));case __STACK_SerializableJs.ensureSerializable(1):case __STACK_SerializableJs.ensureSerializable(\\\\\\"end\\\\\\"):return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).stop)());}})\\"})),__STACK_SerializableJs.ensureSerializable(_callee)));},{scope:()=>({_regeneratorRuntime:__$getFromScope(\\"_regeneratorRuntime\\")}),source:\\"function _callee(){return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__$getFromScope(\\\\\\"_regeneratorRuntime\\\\\\")).wrap)(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.registerFunction(function(_context){while(__STACK_SerializableJs.ensureSerializable(1))switch(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).prev=__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).next))){case __STACK_SerializableJs.ensureSerializable(0):return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).abrupt)(__STACK_SerializableJs.ensureSerializable(\\\\\\"return\\\\\\"),__STACK_SerializableJs.ensureSerializable(3)));case __STACK_SerializableJs.ensureSerializable(1):case __STACK_SerializableJs.ensureSerializable(\\\\\\"end\\\\\\"):return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).stop)());}},{scope:()=>({}),source:\\\\\\"(function(_context){while(__STACK_SerializableJs.ensureSerializable(1))switch(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).prev=__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).next))){case __STACK_SerializableJs.ensureSerializable(0):return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).abrupt)(__STACK_SerializableJs.ensureSerializable(\\\\\\\\\\\\\\"return\\\\\\\\\\\\\\"),__STACK_SerializableJs.ensureSerializable(3)));case __STACK_SerializableJs.ensureSerializable(1):case __STACK_SerializableJs.ensureSerializable(\\\\\\\\\\\\\\"end\\\\\\\\\\\\\\"):return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_context).stop)());}})\\\\\\"})),__STACK_SerializableJs.ensureSerializable(_callee)));}\\"}))))));return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.registerFunction(function asyncFn(){return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_ref).apply)(__STACK_SerializableJs.ensureSerializable(this),__STACK_SerializableJs.ensureSerializable(arguments)));},{scope:()=>({_ref}),source:\\"function asyncFn(){return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__$getFromScope(\\\\\\"_ref\\\\\\")).apply)(__STACK_SerializableJs.ensureSerializable(this),__STACK_SerializableJs.ensureSerializable(arguments)));}\\"}));})" }))()); - class MyClass { - method() { - return __STACK_SerializableJs.ensureSerializable(4); + let MyClass = /*#__PURE__*/__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.registerFunction(function () { + function MyClass() { + __STACK_SerializableJs.ensureSerializable(_classCallCheck)(__STACK_SerializableJs.ensureSerializable(this), __STACK_SerializableJs.ensureSerializable(MyClass)); } - } - __STACK_SerializableJs.registerClass(MyClass, { - scope: () => ({}), - source: "class MyClass{method(){return __STACK_SerializableJs.ensureSerializable(4);}}" - }); - const ClassExpr = __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.registerClass(class CE {}, { - scope: () => ({}), - source: "class CE{}" - }))" + __STACK_SerializableJs.registerFunction(MyClass, { + scope: () => ({ + _classCallCheck, + MyClass + }), + source: "function MyClass(){__STACK_SerializableJs.ensureSerializable(__$getFromScope(\\"_classCallCheck\\"))(__STACK_SerializableJs.ensureSerializable(this),__STACK_SerializableJs.ensureSerializable(__$getFromScope(\\"MyClass\\")));}" + }); + __STACK_SerializableJs.registerFunction(MyClass, { + scope: () => ({ + _classCallCheck, + MyClass + }), + source: "function MyClass(){__STACK_SerializableJs.ensureSerializable(__$getFromScope(\\"_classCallCheck\\"))(__STACK_SerializableJs.ensureSerializable(this),__STACK_SerializableJs.ensureSerializable(__$getFromScope(\\"MyClass\\")));}" + }); + return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_createClass)(__STACK_SerializableJs.ensureSerializable(MyClass), __STACK_SerializableJs.ensureSerializable([__STACK_SerializableJs.ensureSerializable({ + key: __STACK_SerializableJs.ensureSerializable("method"), + value: __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.registerFunction(function method() { + return __STACK_SerializableJs.ensureSerializable(4); + }, { + scope: () => ({}), + source: "function method(){return __STACK_SerializableJs.ensureSerializable(4);}" + })) + })]))); + }, { + scope: () => ({ + _classCallCheck, + _createClass + }), + source: "(function(){function MyClass(){__STACK_SerializableJs.ensureSerializable(__$getFromScope(\\"_classCallCheck\\"))(__STACK_SerializableJs.ensureSerializable(this),__STACK_SerializableJs.ensureSerializable(MyClass));}__STACK_SerializableJs.registerFunction(MyClass,{scope:()=>({_classCallCheck:__$getFromScope(\\"_classCallCheck\\"),MyClass}),source:\\"function MyClass(){__STACK_SerializableJs.ensureSerializable(__$getFromScope(\\\\\\"_classCallCheck\\\\\\"))(__STACK_SerializableJs.ensureSerializable(this),__STACK_SerializableJs.ensureSerializable(__$getFromScope(\\\\\\"MyClass\\\\\\")));}\\"});return __STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(__$getFromScope(\\"_createClass\\"))(__STACK_SerializableJs.ensureSerializable(MyClass),__STACK_SerializableJs.ensureSerializable([__STACK_SerializableJs.ensureSerializable({key:__STACK_SerializableJs.ensureSerializable(\\"method\\"),value:__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.registerFunction(function method(){return __STACK_SerializableJs.ensureSerializable(4);},{scope:()=>({}),source:\\"function method(){return __STACK_SerializableJs.ensureSerializable(4);}\\"}))})])));})" + }))()); + const ClassExpr = /*#__PURE__*/__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_createClass)(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.registerFunction(function CE() { + __STACK_SerializableJs.ensureSerializable(_classCallCheck)(__STACK_SerializableJs.ensureSerializable(this), __STACK_SerializableJs.ensureSerializable(CE)); + }, { + scope: () => ({ + _classCallCheck + }), + source: "function CE(){__STACK_SerializableJs.ensureSerializable(__$getFromScope(\\"_classCallCheck\\"))(__STACK_SerializableJs.ensureSerializable(this),__STACK_SerializableJs.ensureSerializable(CE));}" + }))))" `); }); @@ -1882,7 +2101,10 @@ describe('transpileJsToSerializableJs', () => { const output = transpileJsToSerializableJs(input, { wrapExpressions: true }); expect(getUserCode(output)).toMatchInlineSnapshot(` - " + "__STACK_SerializableJs.registerFunction(_classCallCheck, { + scope: () => ({}), + source: "function _classCallCheck(a,n){if(__STACK_SerializableJs.ensureSerializable(!__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(a)instanceof __STACK_SerializableJs.ensureSerializable(n))))throw __STACK_SerializableJs.ensureSerializable(new(__STACK_SerializableJs.ensureSerializable(TypeError))(__STACK_SerializableJs.ensureSerializable(\\"Cannot call a class as a function\\")));}" + }); function myFunction() { return __STACK_SerializableJs.ensureSerializable(1); } @@ -1890,11 +2112,14 @@ describe('transpileJsToSerializableJs', () => { scope: () => ({}), source: "function myFunction(){return __STACK_SerializableJs.ensureSerializable(1);}" }); - class MyClass {} - __STACK_SerializableJs.registerClass(MyClass, { - scope: () => ({}), - source: "class MyClass{}" - }); + let MyClass = /*#__PURE__*/__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.ensureSerializable(_createClass)(__STACK_SerializableJs.ensureSerializable(__STACK_SerializableJs.registerFunction(function MyClass() { + __STACK_SerializableJs.ensureSerializable(_classCallCheck)(__STACK_SerializableJs.ensureSerializable(this), __STACK_SerializableJs.ensureSerializable(MyClass)); + }, { + scope: () => ({ + _classCallCheck + }), + source: "function MyClass(){__STACK_SerializableJs.ensureSerializable(__$getFromScope(\\"_classCallCheck\\"))(__STACK_SerializableJs.ensureSerializable(this),__STACK_SerializableJs.ensureSerializable(MyClass));}" + })))); const x = __STACK_SerializableJs.ensureSerializable(10)" `); }); @@ -1963,4 +2188,545 @@ describe('transpileJsToSerializableJs', () => { expect(getUserCode(outputDefault)).not.toContain('ensureSerializable'); }); }); + + describe('Class Transformation', () => { + it('should transform simple class to constructor function', () => { + const input = deindent` + class MyClass { + constructor(value) { + this.value = value; + } + + getValue() { + return this.value; + } + } + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + "__STACK_SerializableJs.registerFunction(_toPrimitive, { + scope: () => ({}), + source: "function _toPrimitive(t,r){if(\\"object\\"!=typeof t||!t)return t;var e=t[Symbol.toPrimitive];if(void 0!==e){var i=e.call(t,r||\\"default\\");if(\\"object\\"!=typeof i)return i;throw new TypeError(\\"@@toPrimitive must return a primitive value.\\");}return(\\"string\\"===r?String:Number)(t);}" + }); + let MyClass = /*#__PURE__*/__STACK_SerializableJs.registerFunction(function () { + function MyClass(value) { + _classCallCheck(this, MyClass); + this.value = value; + } + __STACK_SerializableJs.registerFunction(MyClass, { + scope: () => ({ + _classCallCheck, + MyClass + }), + source: "function MyClass(value){__$getFromScope(\\"_classCallCheck\\")(this,__$getFromScope(\\"MyClass\\"));this.value=value;}" + }); + __STACK_SerializableJs.registerFunction(MyClass, { + scope: () => ({ + _classCallCheck, + MyClass + }), + source: "function MyClass(value){__$getFromScope(\\"_classCallCheck\\")(this,__$getFromScope(\\"MyClass\\"));this.value=value;}" + }); + return _createClass(MyClass, [{ + key: "getValue", + value: __STACK_SerializableJs.registerFunction(function getValue() { + return this.value; + }, { + scope: () => ({}), + source: "function getValue(){return this.value;}" + }) + }]); + }, { + scope: () => ({ + _classCallCheck, + _createClass + }), + source: "(function(){function MyClass(value){__$getFromScope(\\"_classCallCheck\\")(this,MyClass);this.value=value;}__STACK_SerializableJs.registerFunction(MyClass,{scope:()=>({_classCallCheck:__$getFromScope(\\"_classCallCheck\\"),MyClass}),source:\\"function MyClass(value){__$getFromScope(\\\\\\"_classCallCheck\\\\\\")(this,__$getFromScope(\\\\\\"MyClass\\\\\\"));this.value=value;}\\"});return __$getFromScope(\\"_createClass\\")(MyClass,[{key:\\"getValue\\",value:__STACK_SerializableJs.registerFunction(function getValue(){return this.value;},{scope:()=>({}),source:\\"function getValue(){return this.value;}\\"})}]);})" + })()" + `); + }); + + it('should handle class with static methods and properties', () => { + const input = deindent` + class Calculator { + static PI = 3.14159; + + static add(a, b) { + return a + b; + } + + constructor(initial) { + this.value = initial; + } + + multiply(x) { + this.value *= x; + return this; + } + } + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + "__STACK_SerializableJs.registerFunction(_toPrimitive, { + scope: () => ({}), + source: "function _toPrimitive(t,r){if(\\"object\\"!=typeof t||!t)return t;var e=t[Symbol.toPrimitive];if(void 0!==e){var i=e.call(t,r||\\"default\\");if(\\"object\\"!=typeof i)return i;throw new TypeError(\\"@@toPrimitive must return a primitive value.\\");}return(\\"string\\"===r?String:Number)(t);}" + }); + let Calculator = /*#__PURE__*/__STACK_SerializableJs.registerFunction(function () { + function Calculator(initial) { + _classCallCheck(this, Calculator); + this.value = initial; + } + __STACK_SerializableJs.registerFunction(Calculator, { + scope: () => ({ + _classCallCheck, + Calculator + }), + source: "function Calculator(initial){__$getFromScope(\\"_classCallCheck\\")(this,__$getFromScope(\\"Calculator\\"));this.value=initial;}" + }); + __STACK_SerializableJs.registerFunction(Calculator, { + scope: () => ({ + _classCallCheck, + Calculator + }), + source: "function Calculator(initial){__$getFromScope(\\"_classCallCheck\\")(this,__$getFromScope(\\"Calculator\\"));this.value=initial;}" + }); + return _createClass(Calculator, [{ + key: "multiply", + value: __STACK_SerializableJs.registerFunction(function multiply(x) { + this.value *= x; + return this; + }, { + scope: () => ({}), + source: "function multiply(x){this.value*=x;return this;}" + }) + }], [{ + key: "add", + value: __STACK_SerializableJs.registerFunction(function add(a, b) { + return a + b; + }, { + scope: () => ({}), + source: "function add(a,b){return a+b;}" + }) + }]); + }, { + scope: () => ({ + _classCallCheck, + _createClass + }), + source: "(function(){function Calculator(initial){__$getFromScope(\\"_classCallCheck\\")(this,Calculator);this.value=initial;}__STACK_SerializableJs.registerFunction(Calculator,{scope:()=>({_classCallCheck:__$getFromScope(\\"_classCallCheck\\"),Calculator}),source:\\"function Calculator(initial){__$getFromScope(\\\\\\"_classCallCheck\\\\\\")(this,__$getFromScope(\\\\\\"Calculator\\\\\\"));this.value=initial;}\\"});return __$getFromScope(\\"_createClass\\")(Calculator,[{key:\\"multiply\\",value:__STACK_SerializableJs.registerFunction(function multiply(x){this.value*=x;return this;},{scope:()=>({}),source:\\"function multiply(x){this.value*=x;return this;}\\"})}],[{key:\\"add\\",value:__STACK_SerializableJs.registerFunction(function add(a,b){return a+b;},{scope:()=>({}),source:\\"function add(a,b){return a+b;}\\"})}]);})" + })(); + _defineProperty(Calculator, "PI", 3.14159)" + `); + }); + + it('should handle class with private fields', () => { + const input = deindent` + class Counter { + #count = 0; + + increment() { + this.#count++; + } + + getCount() { + return this.#count; + } + } + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + "let Counter = /*#__PURE__*/__STACK_SerializableJs.registerFunction(function () { + function Counter() { + _classCallCheck(this, Counter); + _classPrivateFieldInitSpec(this, _count, 0); + } + __STACK_SerializableJs.registerFunction(Counter, { + scope: () => ({ + _classCallCheck, + Counter, + _classPrivateFieldInitSpec, + _count + }), + source: "function Counter(){__$getFromScope(\\"_classCallCheck\\")(this,__$getFromScope(\\"Counter\\"));__$getFromScope(\\"_classPrivateFieldInitSpec\\")(this,__$getFromScope(\\"_count\\"),0);}" + }); + __STACK_SerializableJs.registerFunction(Counter, { + scope: () => ({ + _classCallCheck, + Counter, + _classPrivateFieldInitSpec, + _count + }), + source: "function Counter(){__$getFromScope(\\"_classCallCheck\\")(this,__$getFromScope(\\"Counter\\"));__$getFromScope(\\"_classPrivateFieldInitSpec\\")(this,__$getFromScope(\\"_count\\"),0);}" + }); + return _createClass(Counter, [{ + key: "increment", + value: __STACK_SerializableJs.registerFunction(function increment() { + var _this$count, _this$count2; + _classPrivateFieldSet(_count, this, (_this$count = _classPrivateFieldGet(_count, this), _this$count2 = _this$count++, _this$count)), _this$count2; + }, { + scope: () => ({ + _classPrivateFieldSet, + _count, + _classPrivateFieldGet + }), + source: "function increment(){var _this$count,_this$count2;__$getFromScope(\\"_classPrivateFieldSet\\")(__$getFromScope(\\"_count\\"),this,(_this$count=__$getFromScope(\\"_classPrivateFieldGet\\")(__$getFromScope(\\"_count\\"),this),_this$count2=_this$count++,_this$count)),_this$count2;}" + }) + }, { + key: "getCount", + value: __STACK_SerializableJs.registerFunction(function getCount() { + return _classPrivateFieldGet(_count, this); + }, { + scope: () => ({ + _classPrivateFieldGet, + _count + }), + source: "function getCount(){return __$getFromScope(\\"_classPrivateFieldGet\\")(__$getFromScope(\\"_count\\"),this);}" + }) + }]); + }, { + scope: () => ({ + _classCallCheck, + _classPrivateFieldInitSpec, + _count, + _createClass, + _classPrivateFieldSet, + _classPrivateFieldGet + }), + source: "(function(){function Counter(){__$getFromScope(\\"_classCallCheck\\")(this,Counter);__$getFromScope(\\"_classPrivateFieldInitSpec\\")(this,__$getFromScope(\\"_count\\"),0);}__STACK_SerializableJs.registerFunction(Counter,{scope:()=>({_classCallCheck:__$getFromScope(\\"_classCallCheck\\"),Counter,_classPrivateFieldInitSpec:__$getFromScope(\\"_classPrivateFieldInitSpec\\"),_count:__$getFromScope(\\"_count\\")}),source:\\"function Counter(){__$getFromScope(\\\\\\"_classCallCheck\\\\\\")(this,__$getFromScope(\\\\\\"Counter\\\\\\"));__$getFromScope(\\\\\\"_classPrivateFieldInitSpec\\\\\\")(this,__$getFromScope(\\\\\\"_count\\\\\\"),0);}\\"});return __$getFromScope(\\"_createClass\\")(Counter,[{key:\\"increment\\",value:__STACK_SerializableJs.registerFunction(function increment(){var _this$count,_this$count2;__$getFromScope(\\"_classPrivateFieldSet\\")(__$getFromScope(\\"_count\\"),this,(_this$count=__$getFromScope(\\"_classPrivateFieldGet\\")(__$getFromScope(\\"_count\\"),this),_this$count2=_this$count++,_this$count)),_this$count2;},{scope:()=>({_classPrivateFieldSet:__$getFromScope(\\"_classPrivateFieldSet\\"),_count:__$getFromScope(\\"_count\\"),_classPrivateFieldGet:__$getFromScope(\\"_classPrivateFieldGet\\")}),source:\\"function increment(){var _this$count,_this$count2;__$getFromScope(\\\\\\"_classPrivateFieldSet\\\\\\")(__$getFromScope(\\\\\\"_count\\\\\\"),this,(_this$count=__$getFromScope(\\\\\\"_classPrivateFieldGet\\\\\\")(__$getFromScope(\\\\\\"_count\\\\\\"),this),_this$count2=_this$count++,_this$count)),_this$count2;}\\"})},{key:\\"getCount\\",value:__STACK_SerializableJs.registerFunction(function getCount(){return __$getFromScope(\\"_classPrivateFieldGet\\")(__$getFromScope(\\"_count\\"),this);},{scope:()=>({_classPrivateFieldGet:__$getFromScope(\\"_classPrivateFieldGet\\"),_count:__$getFromScope(\\"_count\\")}),source:\\"function getCount(){return __$getFromScope(\\\\\\"_classPrivateFieldGet\\\\\\")(__$getFromScope(\\\\\\"_count\\\\\\"),this);}\\"})}]);})" + })()" + `); + }); + + it('should handle class inheritance with super', () => { + const input = deindent` + class Animal { + constructor(name) { + this.name = name; + } + + speak() { + return 'Some sound'; + } + } + + class Dog extends Animal { + constructor(name, breed) { + super(name); + this.breed = breed; + } + + speak() { + return super.speak() + ' - Woof!'; + } + } + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + "__STACK_SerializableJs.registerFunction(_toPrimitive, { + scope: () => ({}), + source: "function _toPrimitive(t,r){if(\\"object\\"!=typeof t||!t)return t;var e=t[Symbol.toPrimitive];if(void 0!==e){var i=e.call(t,r||\\"default\\");if(\\"object\\"!=typeof i)return i;throw new TypeError(\\"@@toPrimitive must return a primitive value.\\");}return(\\"string\\"===r?String:Number)(t);}" + }); + let Animal = /*#__PURE__*/__STACK_SerializableJs.registerFunction(function () { + function Animal(name) { + _classCallCheck(this, Animal); + this.name = name; + } + __STACK_SerializableJs.registerFunction(Animal, { + scope: () => ({ + _classCallCheck, + Animal + }), + source: "function Animal(name){__$getFromScope(\\"_classCallCheck\\")(this,__$getFromScope(\\"Animal\\"));this.name=name;}" + }); + __STACK_SerializableJs.registerFunction(Animal, { + scope: () => ({ + _classCallCheck, + Animal + }), + source: "function Animal(name){__$getFromScope(\\"_classCallCheck\\")(this,__$getFromScope(\\"Animal\\"));this.name=name;}" + }); + return _createClass(Animal, [{ + key: "speak", + value: __STACK_SerializableJs.registerFunction(function speak() { + return 'Some sound'; + }, { + scope: () => ({}), + source: "function speak(){return'Some sound';}" + }) + }]); + }, { + scope: () => ({ + _classCallCheck, + _createClass + }), + source: "(function(){function Animal(name){__$getFromScope(\\"_classCallCheck\\")(this,Animal);this.name=name;}__STACK_SerializableJs.registerFunction(Animal,{scope:()=>({_classCallCheck:__$getFromScope(\\"_classCallCheck\\"),Animal}),source:\\"function Animal(name){__$getFromScope(\\\\\\"_classCallCheck\\\\\\")(this,__$getFromScope(\\\\\\"Animal\\\\\\"));this.name=name;}\\"});return __$getFromScope(\\"_createClass\\")(Animal,[{key:\\"speak\\",value:__STACK_SerializableJs.registerFunction(function speak(){return'Some sound';},{scope:()=>({}),source:\\"function speak(){return'Some sound';}\\"})}]);})" + })(); + let Dog = /*#__PURE__*/__STACK_SerializableJs.registerFunction(function (_Animal) { + function Dog(name, breed) { + var _this; + _classCallCheck(this, Dog); + _this = _callSuper(this, Dog, [name]); + _this.breed = breed; + return _this; + } + __STACK_SerializableJs.registerFunction(Dog, { + scope: () => ({ + _classCallCheck, + Dog, + _callSuper + }), + source: "function Dog(name,breed){var _this;__$getFromScope(\\"_classCallCheck\\")(this,__$getFromScope(\\"Dog\\"));_this=__$getFromScope(\\"_callSuper\\")(this,__$getFromScope(\\"Dog\\"),[name]);_this.breed=breed;return _this;}" + }); + __STACK_SerializableJs.registerFunction(Dog, { + scope: () => ({ + _classCallCheck, + Dog, + _callSuper + }), + source: "function Dog(name,breed){var _this;__$getFromScope(\\"_classCallCheck\\")(this,__$getFromScope(\\"Dog\\"));_this=__$getFromScope(\\"_callSuper\\")(this,__$getFromScope(\\"Dog\\"),[name]);_this.breed=breed;return _this;}" + }); + _inherits(Dog, _Animal); + return _createClass(Dog, [{ + key: "speak", + value: __STACK_SerializableJs.registerFunction(function speak() { + return _superPropGet(Dog, "speak", this, 3)([]) + ' - Woof!'; + }, { + scope: () => ({ + _superPropGet, + Dog + }), + source: "function speak(){return __$getFromScope(\\"_superPropGet\\")(__$getFromScope(\\"Dog\\"),\\"speak\\",this,3)([])+' - Woof!';}" + }) + }]); + }, { + scope: () => ({ + _classCallCheck, + _callSuper, + _inherits, + _createClass, + _superPropGet + }), + source: "(function(_Animal){function Dog(name,breed){var _this;__$getFromScope(\\"_classCallCheck\\")(this,Dog);_this=__$getFromScope(\\"_callSuper\\")(this,Dog,[name]);_this.breed=breed;return _this;}__STACK_SerializableJs.registerFunction(Dog,{scope:()=>({_classCallCheck:__$getFromScope(\\"_classCallCheck\\"),Dog,_callSuper:__$getFromScope(\\"_callSuper\\")}),source:\\"function Dog(name,breed){var _this;__$getFromScope(\\\\\\"_classCallCheck\\\\\\")(this,__$getFromScope(\\\\\\"Dog\\\\\\"));_this=__$getFromScope(\\\\\\"_callSuper\\\\\\")(this,__$getFromScope(\\\\\\"Dog\\\\\\"),[name]);_this.breed=breed;return _this;}\\"});__$getFromScope(\\"_inherits\\")(Dog,_Animal);return __$getFromScope(\\"_createClass\\")(Dog,[{key:\\"speak\\",value:__STACK_SerializableJs.registerFunction(function speak(){return __$getFromScope(\\"_superPropGet\\")(Dog,\\"speak\\",this,3)([])+' - Woof!';},{scope:()=>({_superPropGet:__$getFromScope(\\"_superPropGet\\"),Dog}),source:\\"function speak(){return __$getFromScope(\\\\\\"_superPropGet\\\\\\")(__$getFromScope(\\\\\\"Dog\\\\\\"),\\\\\\"speak\\\\\\",this,3)([])+' - Woof!';}\\"})}]);})" + })(Animal)" + `); + }); + + it('should handle class with getters and setters', () => { + const input = deindent` + class Person { + constructor(firstName, lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + get fullName() { + return \`\${this.firstName} \${this.lastName}\`; + } + + set fullName(value) { + const parts = value.split(' '); + this.firstName = parts[0]; + this.lastName = parts[1]; + } + + static get species() { + return 'Homo sapiens'; + } + } + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + "__STACK_SerializableJs.registerFunction(_toPrimitive, { + scope: () => ({}), + source: "function _toPrimitive(t,r){if(\\"object\\"!=typeof t||!t)return t;var e=t[Symbol.toPrimitive];if(void 0!==e){var i=e.call(t,r||\\"default\\");if(\\"object\\"!=typeof i)return i;throw new TypeError(\\"@@toPrimitive must return a primitive value.\\");}return(\\"string\\"===r?String:Number)(t);}" + }); + let Person = /*#__PURE__*/__STACK_SerializableJs.registerFunction(function () { + function Person(firstName, lastName) { + _classCallCheck(this, Person); + this.firstName = firstName; + this.lastName = lastName; + } + __STACK_SerializableJs.registerFunction(Person, { + scope: () => ({ + _classCallCheck, + Person + }), + source: "function Person(firstName,lastName){__$getFromScope(\\"_classCallCheck\\")(this,__$getFromScope(\\"Person\\"));this.firstName=firstName;this.lastName=lastName;}" + }); + __STACK_SerializableJs.registerFunction(Person, { + scope: () => ({ + _classCallCheck, + Person + }), + source: "function Person(firstName,lastName){__$getFromScope(\\"_classCallCheck\\")(this,__$getFromScope(\\"Person\\"));this.firstName=firstName;this.lastName=lastName;}" + }); + return _createClass(Person, [{ + key: "fullName", + get: __STACK_SerializableJs.registerFunction(function () { + return \`\${this.firstName} \${this.lastName}\`; + }, { + scope: () => ({}), + source: "(function(){return\`\${this.firstName} \${this.lastName}\`;})" + }), + set: __STACK_SerializableJs.registerFunction(function (value) { + const parts = value.split(' '); + this.firstName = parts[0]; + this.lastName = parts[1]; + }, { + scope: () => ({}), + source: "(function(value){const parts=value.split(' ');this.firstName=parts[0];this.lastName=parts[1];})" + }) + }], [{ + key: "species", + get: __STACK_SerializableJs.registerFunction(function () { + return 'Homo sapiens'; + }, { + scope: () => ({}), + source: "(function(){return'Homo sapiens';})" + }) + }]); + }, { + scope: () => ({ + _classCallCheck, + _createClass + }), + source: "(function(){function Person(firstName,lastName){__$getFromScope(\\"_classCallCheck\\")(this,Person);this.firstName=firstName;this.lastName=lastName;}__STACK_SerializableJs.registerFunction(Person,{scope:()=>({_classCallCheck:__$getFromScope(\\"_classCallCheck\\"),Person}),source:\\"function Person(firstName,lastName){__$getFromScope(\\\\\\"_classCallCheck\\\\\\")(this,__$getFromScope(\\\\\\"Person\\\\\\"));this.firstName=firstName;this.lastName=lastName;}\\"});return __$getFromScope(\\"_createClass\\")(Person,[{key:\\"fullName\\",get:__STACK_SerializableJs.registerFunction(function(){return\`\${this.firstName} \${this.lastName}\`;},{scope:()=>({}),source:\\"(function(){return\`\${this.firstName} \${this.lastName}\`;})\\"}),set:__STACK_SerializableJs.registerFunction(function(value){const parts=value.split(' ');this.firstName=parts[0];this.lastName=parts[1];},{scope:()=>({}),source:\\"(function(value){const parts=value.split(' ');this.firstName=parts[0];this.lastName=parts[1];})\\"})}],[{key:\\"species\\",get:__STACK_SerializableJs.registerFunction(function(){return'Homo sapiens';},{scope:()=>({}),source:\\"(function(){return'Homo sapiens';})\\"})}]);})" + })()" + `); + }); + + it('should handle class expressions', () => { + const input = deindent` + const MyClass = class { + constructor(value) { + this.value = value; + } + + getValue() { + return this.value; + } + }; + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + "__STACK_SerializableJs.registerFunction(_toPrimitive, { + scope: () => ({}), + source: "function _toPrimitive(t,r){if(\\"object\\"!=typeof t||!t)return t;var e=t[Symbol.toPrimitive];if(void 0!==e){var i=e.call(t,r||\\"default\\");if(\\"object\\"!=typeof i)return i;throw new TypeError(\\"@@toPrimitive must return a primitive value.\\");}return(\\"string\\"===r?String:Number)(t);}" + }); + const MyClass = /*#__PURE__*/__STACK_SerializableJs.registerFunction(function () { + function MyClass(value) { + _classCallCheck(this, MyClass); + this.value = value; + } + __STACK_SerializableJs.registerFunction(MyClass, { + scope: () => ({ + _classCallCheck, + MyClass + }), + source: "function MyClass(value){__$getFromScope(\\"_classCallCheck\\")(this,__$getFromScope(\\"MyClass\\"));this.value=value;}" + }); + __STACK_SerializableJs.registerFunction(MyClass, { + scope: () => ({ + _classCallCheck, + MyClass + }), + source: "function MyClass(value){__$getFromScope(\\"_classCallCheck\\")(this,__$getFromScope(\\"MyClass\\"));this.value=value;}" + }); + return _createClass(MyClass, [{ + key: "getValue", + value: __STACK_SerializableJs.registerFunction(function getValue() { + return this.value; + }, { + scope: () => ({}), + source: "function getValue(){return this.value;}" + }) + }]); + }, { + scope: () => ({ + _classCallCheck, + _createClass + }), + source: "(function(){function MyClass(value){__$getFromScope(\\"_classCallCheck\\")(this,MyClass);this.value=value;}__STACK_SerializableJs.registerFunction(MyClass,{scope:()=>({_classCallCheck:__$getFromScope(\\"_classCallCheck\\"),MyClass}),source:\\"function MyClass(value){__$getFromScope(\\\\\\"_classCallCheck\\\\\\")(this,__$getFromScope(\\\\\\"MyClass\\\\\\"));this.value=value;}\\"});return __$getFromScope(\\"_createClass\\")(MyClass,[{key:\\"getValue\\",value:__STACK_SerializableJs.registerFunction(function getValue(){return this.value;},{scope:()=>({}),source:\\"function getValue(){return this.value;}\\"})}]);})" + })()" + `); + }); + + it('should handle private methods', () => { + const input = deindent` + class SecureClass { + #privateMethod() { + return 'secret'; + } + + publicMethod() { + return this.#privateMethod(); + } + } + `; + + const output = transpileJsToSerializableJs(input); + expect(getUserCode(output)).toMatchInlineSnapshot(` + "let SecureClass = /*#__PURE__*/__STACK_SerializableJs.registerFunction(function () { + function SecureClass() { + _classCallCheck(this, SecureClass); + _classPrivateMethodInitSpec(this, _SecureClass_brand); + } + __STACK_SerializableJs.registerFunction(SecureClass, { + scope: () => ({ + _classCallCheck, + SecureClass, + _classPrivateMethodInitSpec, + _SecureClass_brand + }), + source: "function SecureClass(){__$getFromScope(\\"_classCallCheck\\")(this,__$getFromScope(\\"SecureClass\\"));__$getFromScope(\\"_classPrivateMethodInitSpec\\")(this,__$getFromScope(\\"_SecureClass_brand\\"));}" + }); + __STACK_SerializableJs.registerFunction(SecureClass, { + scope: () => ({ + _classCallCheck, + SecureClass, + _classPrivateMethodInitSpec, + _SecureClass_brand + }), + source: "function SecureClass(){__$getFromScope(\\"_classCallCheck\\")(this,__$getFromScope(\\"SecureClass\\"));__$getFromScope(\\"_classPrivateMethodInitSpec\\")(this,__$getFromScope(\\"_SecureClass_brand\\"));}" + }); + return _createClass(SecureClass, [{ + key: "publicMethod", + value: __STACK_SerializableJs.registerFunction(function publicMethod() { + return _assertClassBrand(_SecureClass_brand, this, _privateMethod).call(this); + }, { + scope: () => ({ + _assertClassBrand, + _SecureClass_brand, + _privateMethod + }), + source: "function publicMethod(){return __$getFromScope(\\"_assertClassBrand\\")(__$getFromScope(\\"_SecureClass_brand\\"),this,__$getFromScope(\\"_privateMethod\\")).call(this);}" + }) + }]); + }, { + scope: () => ({ + _classCallCheck, + _classPrivateMethodInitSpec, + _SecureClass_brand, + _createClass, + _assertClassBrand, + _privateMethod + }), + source: "(function(){function SecureClass(){__$getFromScope(\\"_classCallCheck\\")(this,SecureClass);__$getFromScope(\\"_classPrivateMethodInitSpec\\")(this,__$getFromScope(\\"_SecureClass_brand\\"));}__STACK_SerializableJs.registerFunction(SecureClass,{scope:()=>({_classCallCheck:__$getFromScope(\\"_classCallCheck\\"),SecureClass,_classPrivateMethodInitSpec:__$getFromScope(\\"_classPrivateMethodInitSpec\\"),_SecureClass_brand:__$getFromScope(\\"_SecureClass_brand\\")}),source:\\"function SecureClass(){__$getFromScope(\\\\\\"_classCallCheck\\\\\\")(this,__$getFromScope(\\\\\\"SecureClass\\\\\\"));__$getFromScope(\\\\\\"_classPrivateMethodInitSpec\\\\\\")(this,__$getFromScope(\\\\\\"_SecureClass_brand\\\\\\"));}\\"});return __$getFromScope(\\"_createClass\\")(SecureClass,[{key:\\"publicMethod\\",value:__STACK_SerializableJs.registerFunction(function publicMethod(){return __$getFromScope(\\"_assertClassBrand\\")(__$getFromScope(\\"_SecureClass_brand\\"),this,__$getFromScope(\\"_privateMethod\\")).call(this);},{scope:()=>({_assertClassBrand:__$getFromScope(\\"_assertClassBrand\\"),_SecureClass_brand:__$getFromScope(\\"_SecureClass_brand\\"),_privateMethod:__$getFromScope(\\"_privateMethod\\")}),source:\\"function publicMethod(){return __$getFromScope(\\\\\\"_assertClassBrand\\\\\\")(__$getFromScope(\\\\\\"_SecureClass_brand\\\\\\"),this,__$getFromScope(\\\\\\"_privateMethod\\\\\\")).call(this);}\\"})}]);})" + })(); + function _privateMethod() { + return 'secret'; + } + __STACK_SerializableJs.registerFunction(_privateMethod, { + scope: () => ({}), + source: "function _privateMethod(){return'secret';}" + })" + `); + }); + }); }); diff --git a/packages/stack-shared/src/helpers/js-serializer/transpiler.ts b/packages/stack-shared/src/helpers/js-serializer/transpiler.ts index f0f4852114..3ae2c76217 100644 --- a/packages/stack-shared/src/helpers/js-serializer/transpiler.ts +++ b/packages/stack-shared/src/helpers/js-serializer/transpiler.ts @@ -389,21 +389,7 @@ function wrapFunctions(code: string): string { ); path.skip(); }, - ClassMethod(path) { - // Skip all class methods - they'll be handled as part of the class - // Getters, setters, constructors, and regular methods all stay as-is - return; - }, - ClassDeclaration: { - exit(path) { - transformClass(path); - } - }, - ClassExpression: { - exit(path) { - transformClass(path); - } - } + // Classes are already transformed to functions in step 3, so we don't handle them here } }; @@ -458,7 +444,8 @@ function wrapFunctions(code: string): string { // Remove the old transformObjectWithGettersSetters and getKeyName functions - they're now in transformGettersSetters - function transformClass(path: babel.NodePath) { + // Classes are already transformed in step 3 + function REMOVED_transformClass(path: babel.NodePath) { const { node } = path; // Collect all variables in scope for the class @@ -869,6 +856,33 @@ function wrapExpressionsWithEnsureSerializable(code: string): string { return result?.code || code; } +/** + * Transform classes to constructor functions using Babel's official plugins + */ +function transformClassesToFunctions(code: string): string { + const result = babel.transformSync(code, { + plugins: [ + // Transform private methods and fields first + ['@babel/plugin-transform-private-methods', { loose: false }], + ['@babel/plugin-transform-private-property-in-object', { loose: false }], + ['@babel/plugin-transform-class-properties', { loose: false }], + ['@babel/plugin-transform-class-static-block'], + // Then transform the classes themselves + ['@babel/plugin-transform-classes', { loose: false }] + ], + parserOpts: { + sourceType: 'module', + plugins: ['jsx', 'typescript', 'classProperties', 'classPrivateProperties', 'classPrivateMethods', 'classStaticBlock'] + }, + generatorOpts: { + retainLines: false, + compact: false + } + }); + + return result?.code || code; +} + export function transpileJsToSerializableJs(js: string, options: TranspilerOptions = {}): string { // Step 1: Transform async functions and generators to use regenerator runtime let transformedCode = transformAsyncAndGenerators(js); @@ -876,7 +890,10 @@ export function transpileJsToSerializableJs(js: string, options: TranspilerOptio // Step 2: Bundle runtime helpers and replace imports transformedCode = bundleRuntimeHelpers(transformedCode); - // Step 3: Transform getters/setters to use Object.defineProperty + // Step 3: Transform classes to constructor functions + transformedCode = transformClassesToFunctions(transformedCode); + + // Step 4: Transform getters/setters to use Object.defineProperty transformedCode = transformGettersSetters(transformedCode); // Step 4 (Optional): Wrap all expressions with ensureSerializable diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2466999246..1fa84759fa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1519,6 +1519,21 @@ importers: specifier: ^1.4.0 version: 1.4.0 devDependencies: + '@babel/plugin-transform-class-properties': + specifier: ^7.27.1 + version: 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-class-static-block': + specifier: ^7.28.3 + version: 7.28.3(@babel/core@7.28.3) + '@babel/plugin-transform-classes': + specifier: ^7.28.3 + version: 7.28.3(@babel/core@7.28.3) + '@babel/plugin-transform-private-methods': + specifier: ^7.27.1 + version: 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-private-property-in-object': + specifier: ^7.27.1 + version: 7.27.1(@babel/core@7.28.3) '@sentry/nextjs': specifier: ^8.40.0 version: 8.40.0(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.4.1(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0)(webpack@5.92.0(@swc/core@1.3.101)(esbuild@0.24.2)) @@ -2126,10 +2141,6 @@ packages: resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} engines: {node: '>=6.9.0'} - '@babel/helper-annotate-as-pure@7.24.7': - resolution: {integrity: sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==} - engines: {node: '>=6.9.0'} - '@babel/helper-annotate-as-pure@7.27.3': resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} engines: {node: '>=6.9.0'} @@ -2138,18 +2149,6 @@ packages: resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} engines: {node: '>=6.9.0'} - '@babel/helper-create-class-features-plugin@7.25.0': - resolution: {integrity: sha512-GYM6BxeQsETc9mnct+nIIpf63SAyzvyYN7UB/IlTyd+MBg06afFGp0mIeUqGyWgS2mxad6vqbMrHVlaL3m70sQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-create-class-features-plugin@7.27.1': - resolution: {integrity: sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - '@babel/helper-create-class-features-plugin@7.28.3': resolution: {integrity: sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==} engines: {node: '>=6.9.0'} @@ -2171,10 +2170,6 @@ packages: resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} engines: {node: '>=6.9.0'} - '@babel/helper-member-expression-to-functions@7.24.8': - resolution: {integrity: sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==} - engines: {node: '>=6.9.0'} - '@babel/helper-member-expression-to-functions@7.27.1': resolution: {integrity: sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==} engines: {node: '>=6.9.0'} @@ -2195,10 +2190,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-optimise-call-expression@7.24.7': - resolution: {integrity: sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==} - engines: {node: '>=6.9.0'} - '@babel/helper-optimise-call-expression@7.27.1': resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} engines: {node: '>=6.9.0'} @@ -2213,12 +2204,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-replace-supers@7.25.0': - resolution: {integrity: sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - '@babel/helper-replace-supers@7.27.1': resolution: {integrity: sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==} engines: {node: '>=6.9.0'} @@ -16400,10 +16385,6 @@ snapshots: '@jridgewell/trace-mapping': 0.3.29 jsesc: 3.0.2 - '@babel/helper-annotate-as-pure@7.24.7': - dependencies: - '@babel/types': 7.28.2 - '@babel/helper-annotate-as-pure@7.27.3': dependencies: '@babel/types': 7.28.2 @@ -16416,32 +16397,6 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.25.0(@babel/core@7.28.3)': - dependencies: - '@babel/core': 7.28.3 - '@babel/helper-annotate-as-pure': 7.24.7 - '@babel/helper-member-expression-to-functions': 7.24.8 - '@babel/helper-optimise-call-expression': 7.24.7 - '@babel/helper-replace-supers': 7.25.0(@babel/core@7.28.3) - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/traverse': 7.28.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/helper-create-class-features-plugin@7.27.1(@babel/core@7.28.3)': - dependencies: - '@babel/core': 7.28.3 - '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-member-expression-to-functions': 7.27.1 - '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.3) - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/traverse': 7.28.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - '@babel/helper-create-class-features-plugin@7.28.3(@babel/core@7.28.3)': dependencies: '@babel/core': 7.28.3 @@ -16475,13 +16430,6 @@ snapshots: '@babel/helper-globals@7.28.0': {} - '@babel/helper-member-expression-to-functions@7.24.8': - dependencies: - '@babel/traverse': 7.28.3 - '@babel/types': 7.28.2 - transitivePeerDependencies: - - supports-color - '@babel/helper-member-expression-to-functions@7.27.1': dependencies: '@babel/traverse': 7.28.3 @@ -16514,10 +16462,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-optimise-call-expression@7.24.7': - dependencies: - '@babel/types': 7.28.2 - '@babel/helper-optimise-call-expression@7.27.1': dependencies: '@babel/types': 7.28.2 @@ -16533,15 +16477,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-replace-supers@7.25.0(@babel/core@7.28.3)': - dependencies: - '@babel/core': 7.28.3 - '@babel/helper-member-expression-to-functions': 7.24.8 - '@babel/helper-optimise-call-expression': 7.24.7 - '@babel/traverse': 7.28.3 - transitivePeerDependencies: - - supports-color - '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.3)': dependencies: '@babel/core': 7.28.3 @@ -16664,8 +16599,8 @@ snapshots: '@babel/plugin-proposal-private-property-in-object@7.21.11(@babel/core@7.28.3)': dependencies: '@babel/core': 7.28.3 - '@babel/helper-annotate-as-pure': 7.24.7 - '@babel/helper-create-class-features-plugin': 7.25.0(@babel/core@7.28.3) + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.3) '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.3) transitivePeerDependencies: @@ -16823,7 +16758,7 @@ snapshots: '@babel/plugin-transform-class-properties@7.27.1(@babel/core@7.28.3)': dependencies: '@babel/core': 7.28.3 - '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.28.3) + '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.3) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color @@ -17040,7 +16975,7 @@ snapshots: '@babel/plugin-transform-private-methods@7.27.1(@babel/core@7.28.3)': dependencies: '@babel/core': 7.28.3 - '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.28.3) + '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.3) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color @@ -17049,7 +16984,7 @@ snapshots: dependencies: '@babel/core': 7.28.3 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.28.3) + '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.3) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color