feat(oauth-gateway): implement multi-tenant OAuth gateway service

- Added a new OAuth gateway service to handle multi-tenant authentication callbacks.
- Implemented routing logic for provider callbacks, including tenant slug validation and host resolution.
- Introduced configuration management for environment variables and service settings.
- Created Dockerfile and package.json for service deployment and dependencies.
- Added HTML response for restricted tenant access and updated static web components accordingly.

Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
Innei
2025-11-12 16:33:24 +08:00
parent 1435eb0608
commit 74584cd230
19 changed files with 667 additions and 122 deletions

1
Dockerfile.oauth Symbolic link
View File

@@ -0,0 +1 @@
be/apps/oauth-gateway/Dockerfile

View File

@@ -1,6 +1,7 @@
import { ContextParam, Controller, Get, Param } from '@afilmory/framework'
import { isTenantSlugReserved } from '@afilmory/utils'
import { SkipTenantGuard } from 'core/decorators/skip-tenant.decorator'
import { PLACEHOLDER_TENANT_SLUG, ROOT_TENANT_SLUG } from 'core/modules/platform/tenant/tenant.constants'
import { ROOT_TENANT_SLUG } from 'core/modules/platform/tenant/tenant.constants'
import { getTenantContext, isPlaceholderTenantContext } from 'core/modules/platform/tenant/tenant.context'
import type { Context } from 'hono'
@@ -9,7 +10,7 @@ import { STATIC_DASHBOARD_BASENAME, StaticDashboardService } from './static-dash
import { StaticWebService } from './static-web.service'
const TENANT_MISSING_ENTRY_PATH = `${STATIC_DASHBOARD_BASENAME}/tenant-missing.html`
const RESTRICTED_STATIC_WEB_TENANT_SLUGS = new Set([ROOT_TENANT_SLUG, PLACEHOLDER_TENANT_SLUG])
const TENANT_RESTRICTED_ENTRY_PATH = `${STATIC_DASHBOARD_BASENAME}/tenant-restricted.html`
@Controller({ bypassGlobalPrefix: true })
export class StaticWebController {
@@ -29,8 +30,8 @@ export class StaticWebController {
@Get(`/explory`)
@SkipTenantGuard()
async getStaticWebIndex(@ContextParam() context: Context) {
if (this.shouldBlockReservedTenantStaticWebAccess()) {
return await this.renderTenantMissingPage()
if (this.isReservedTenant()) {
return await this.renderTenantRestrictedPage()
}
if (this.shouldRenderTenantMissingPage()) {
return await this.renderTenantMissingPage()
@@ -45,8 +46,8 @@ export class StaticWebController {
@Get(`/photos/:photoId`)
async getStaticPhotoPage(@ContextParam() context: Context, @Param('photoId') photoId: string) {
if (this.shouldBlockReservedTenantStaticWebAccess()) {
return await this.renderTenantMissingPage()
if (this.isReservedTenant()) {
return await this.renderTenantRestrictedPage()
}
if (this.shouldRenderTenantMissingPage()) {
return await this.renderTenantMissingPage()
@@ -64,11 +65,19 @@ export class StaticWebController {
async getStaticDashboardIndexWithBasename(@ContextParam() context: Context) {
const pathname = context.req.path
const isHtmlRoute = this.isHtmlRoute(pathname)
const normalizedPath = this.normalizePathname(pathname)
const allowTenantlessAccess = isHtmlRoute && this.shouldAllowTenantlessDashboardAccess(pathname)
const isRestrictedEntry = normalizedPath === TENANT_RESTRICTED_ENTRY_PATH
if (isHtmlRoute && this.isReservedTenant() && !isRestrictedEntry) {
return await this.renderTenantRestrictedPage()
}
if (isHtmlRoute && !allowTenantlessAccess && this.shouldRenderTenantMissingPage()) {
return await this.renderTenantMissingPage()
}
const response = await this.serve(context, this.staticDashboardService, false)
if (isHtmlRoute && this.isReservedTenant() && response.status === 404) {
return await this.renderTenantRestrictedPage()
}
if (isHtmlRoute && !allowTenantlessAccess && response.status === 404) {
return await this.renderTenantMissingPage()
}
@@ -179,13 +188,16 @@ export class StaticWebController {
return trimmed
}
private shouldBlockReservedTenantStaticWebAccess(): boolean {
private isReservedTenant(): boolean {
const tenantContext = getTenantContext()
const slug = tenantContext?.tenant.slug?.toLowerCase()
if (!slug) {
return false
}
return RESTRICTED_STATIC_WEB_TENANT_SLUGS.has(slug)
if (slug === ROOT_TENANT_SLUG) {
return false
}
return isTenantSlugReserved(slug)
}
private shouldRenderTenantMissingPage(): boolean {
@@ -202,6 +214,15 @@ export class StaticWebController {
return new Response('Workspace unavailable', { status: 404 })
}
private async renderTenantRestrictedPage(): Promise<Response> {
const response = await this.staticDashboardService.handleRequest(TENANT_RESTRICTED_ENTRY_PATH, false)
if (response) {
return this.cloneResponseWithStatus(response, 403)
}
return new Response('Workspace access restricted', { status: 403 })
}
private cloneResponseWithStatus(response: Response, status: number): Response {
const headers = new Headers(response.headers)
return new Response(response.body, {

View File

@@ -3,6 +3,7 @@ export const ROUTE_PATHS = {
ROOT_LOGIN: '/root-login',
WELCOME: '/welcome',
TENANT_MISSING: '/tenant-missing',
TENANT_RESTRICTED: '/tenant-restricted',
DEFAULT_AUTHENTICATED: '/',
SUPERADMIN_ROOT: '/superadmin',
SUPERADMIN_DEFAULT: '/superadmin/settings',
@@ -14,5 +15,6 @@ export const PUBLIC_ROUTES = new Set<string>([
ROUTE_PATHS.ROOT_LOGIN,
ROUTE_PATHS.WELCOME,
ROUTE_PATHS.TENANT_MISSING,
ROUTE_PATHS.TENANT_RESTRICTED,
ROUTE_PATHS.NO_ACCESS,
])

View File

@@ -0,0 +1,13 @@
import '../styles/index.css'
import { createRoot } from 'react-dom/client'
import { TenantRestrictedStandalone } from '../modules/welcome/components/TenantRestrictedStandalone'
const root = document.querySelector('#root')
if (!root) {
throw new Error('Root element not found for tenant restricted entry.')
}
createRoot(root).render(<TenantRestrictedStandalone />)

View File

@@ -2,44 +2,7 @@ import { Button } from '@afilmory/ui'
import { useMemo } from 'react'
import { LinearBorderContainer } from './LinearBorderContainer'
const getCurrentHostname = () => {
if (typeof window === 'undefined') {
return null
}
try {
return window.location.hostname
} catch {
return null
}
}
const buildRegistrationUrl = () => {
if (typeof window === 'undefined') {
return '/platform/welcome'
}
try {
const { protocol, host } = window.location
return `${protocol}//${host}/platform/welcome`
} catch {
return '/platform/welcome'
}
}
const buildHomeUrl = () => {
if (typeof window === 'undefined') {
return '/'
}
try {
const { protocol, hostname, port } = window.location
const normalizedPort = port ? `:${port}` : ''
return `${protocol}//${hostname}${normalizedPort}`
} catch {
return '/'
}
}
import { buildHomeUrl, buildRegistrationUrl, getCurrentHostname } from './tenant-utils'
export const TenantMissingStandalone = () => {
const hostname = useMemo(() => getCurrentHostname(), [])

View File

@@ -0,0 +1,58 @@
import { Button } from '@afilmory/ui'
import { useMemo } from 'react'
import { LinearBorderContainer } from './LinearBorderContainer'
import { buildHomeUrl, buildRegistrationUrl, getCurrentHostname } from './tenant-utils'
export const TenantRestrictedStandalone = () => {
const hostname = useMemo(() => getCurrentHostname(), [])
const registrationUrl = useMemo(() => buildRegistrationUrl(), [])
const homeUrl = useMemo(() => buildHomeUrl(), [])
return (
<div className="relative flex min-h-dvh flex-1 flex-col bg-background text-text">
<div className="flex flex-1 items-center justify-center px-4 py-10 sm:px-6">
<LinearBorderContainer>
<div className="relative w-full max-w-[640px] overflow-hidden border border-white/5">
<div className="pointer-events-none absolute inset-0 opacity-60">
<div className="absolute -inset-32 bg-linear-to-br from-accent/20 via-transparent to-transparent blur-3xl" />
<div className="absolute inset-0 bg-[radial-gradient(circle_at_top,rgba(255,255,255,0.08),transparent_55%)]" />
</div>
<div className="relative p-10 sm:p-12">
<div>
<p className="text-text-tertiary mb-3 text-xs font-semibold uppercase tracking-[0.55em]">403</p>
<h1 className="mb-4 text-3xl font-bold tracking-tight sm:text-4xl"></h1>
<p className="text-text-secondary mb-6 text-base leading-relaxed">
访访
Afilmory使
</p>
{hostname && (
<div className="bg-material-medium/40 border-fill-tertiary mb-6 rounded-2xl border px-5 py-4 text-sm">
<p className="text-text-secondary">
<span className="text-text font-medium">{hostname}</span>
</p>
</div>
)}
<div className="flex flex-col gap-3 sm:flex-row">
<Button
variant="primary"
className="glassmorphic-btn flex-1"
onClick={() => (window.location.href = registrationUrl)}
>
</Button>
<Button variant="ghost" className="flex-1" onClick={() => (window.location.href = homeUrl)}>
</Button>
</div>
</div>
</div>
</div>
</LinearBorderContainer>
</div>
</div>
)
}

View File

@@ -0,0 +1,37 @@
export const getCurrentHostname = (): string | null => {
if (typeof window === 'undefined') {
return null
}
try {
return window.location.hostname
} catch {
return null
}
}
export const buildRegistrationUrl = (): string => {
if (typeof window === 'undefined') {
return '/platform/welcome'
}
try {
const { protocol, host } = window.location
return `${protocol}//${host}/platform/welcome`
} catch {
return '/platform/welcome'
}
}
export const buildHomeUrl = (): string => {
if (typeof window === 'undefined') {
return '/'
}
try {
const { protocol, hostname, port } = window.location
const normalizedPort = port ? `:${port}` : ''
return `${protocol}//${hostname}${normalizedPort}`
} catch {
return '/'
}
}

View File

@@ -0,0 +1,26 @@
<!doctype html>
<html lang="zh-CN" data-theme="dark">
<head>
<meta charset="UTF-8" />
<meta name="description" content="当前空间为系统保留地址,无法访问仪表盘或公开站点。" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Afilmory - 空间访问受限</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Geist:wght@100..900&display=swap"
rel="stylesheet"
referrerpolicy="no-referrer"
/>
<style>
html {
font-family: 'Geist', ui-sans-serif, system-ui, sans-serif;
}
</style>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/entries/tenant-restricted.tsx"></script>
</body>
</html>

View File

@@ -77,6 +77,7 @@ export default defineConfig({
input: {
main: resolve(ROOT, 'index.html'),
'tenant-missing': resolve(ROOT, 'tenant-missing.html'),
'tenant-restricted': resolve(ROOT, 'tenant-restricted.html'),
},
},
},

View File

@@ -0,0 +1,29 @@
# syntax=docker/dockerfile:1.7
FROM node:20-alpine AS base
ENV PNPM_HOME=/pnpm
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable && corepack prepare pnpm@10.19.0 --activate
FROM base AS builder
WORKDIR /workspace
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml ./
COPY be/apps/oauth-gateway/package.json be/apps/oauth-gateway/package.json
RUN pnpm fetch --filter '@afilmory/oauth-gateway...'
COPY . .
RUN pnpm install --filter '@afilmory/oauth-gateway...' --frozen-lockfile
RUN pnpm --filter @afilmory/oauth-gateway build
FROM base AS runner
ENV NODE_ENV=production
WORKDIR /app
COPY --from=builder /workspace/be/apps/oauth-gateway/dist ./dist
EXPOSE 8790
CMD ["node", "./dist/main.js"]

View File

@@ -0,0 +1,57 @@
# OAuth Gateway
Multi-tenant OAuth callback router that lets every identity provider point to a single domain
(`auth.afilmory.art`, for example) while keeping the actual Better Auth handlers inside each tenant
subdomain.
## How It Works
1. Better Auth (running inside `be/apps/core`) builds provider redirect URLs using the tenant slug.
2. Instead of sending the provider back to the tenant domain, the redirect URL is set to
`https://auth.afilmory.art/api/auth/callback/{provider}?tenantSlug=<slug>`.
3. The gateway receives the provider callback, validates the slug/host, and issues a 302 redirect to
`https://<slug>.afilmory.art/api/auth/callback/{provider}` (preserving `code`, `state`, etc.).
Because the gateway only rewrites the callback target, it does **not** interact with provider APIs or
tokens. This keeps configuration simple (single callback URL in GitHub/Google) while ensuring tenant
sessions are still created on the correct host.
## Development
```bash
pnpm --filter @afilmory/oauth-gateway dev
```
The service starts on `http://0.0.0.0:8790` by default.
## Environment Variables
| Variable | Default | Description |
| --------------------------------- | -------------------- | ----------------------------------------------------------------- |
| `AUTH_GATEWAY_HOST` | `0.0.0.0` | Interface to bind. |
| `AUTH_GATEWAY_PORT` | `8790` | Port to listen on. |
| `AUTH_GATEWAY_BASE_DOMAIN` | `afilmory.art` | Root domain used when constructing tenant hosts. |
| `AUTH_GATEWAY_CALLBACK_BASE_PATH` | `/api/auth/callback` | Base path that the providers call. |
| `AUTH_GATEWAY_FORCE_HTTPS` | `true` | Forces redirects to `https` unless the host looks like localhost. |
| `AUTH_GATEWAY_ALLOW_CUSTOM_HOST` | `false` | Allow requests to pass an explicit `targetHost` query parameter. |
| `AUTH_GATEWAY_ROOT_SLUG` | `root` | Slug treated as the apex (no subdomain). |
## Callback Contract
`GET /api/auth/callback/:provider`
Query parameters:
- `tenantSlug` (preferred) or `tenant` — tenant slug to route to. Required unless `targetHost` is
provided or you want to hit the root domain.
- `targetHost` — explicit host override (opt-in via `ALLOW_CUSTOM_HOST`).
- All other query parameters (`code`, `state`, etc.) are forwarded verbatim.
Example redirect produced by the gateway:
```
https://auth.afilmory.art/api/auth/callback/github?tenantSlug=innei&code=...&state=...
⮕ 302 → https://innei.afilmory.art/api/auth/callback/github?code=...&state=...
```
This service is intentionally stateless so it can be deployed behind a simple load balancer.

View File

@@ -0,0 +1,6 @@
{
"ignore": ["dist"],
"watch": ["src"],
"ext": "ts,js,json",
"exec": "vite-node src/index.ts"
}

View File

@@ -0,0 +1,24 @@
{
"name": "@afilmory/oauth-gateway",
"type": "module",
"version": "0.1.0",
"private": true,
"scripts": {
"build": "vite build",
"dev": "nodemon",
"start": "node dist/main.js"
},
"dependencies": {
"@afilmory/utils": "workspace:*",
"@hono/node-server": "^1.13.5",
"hono": "^4.6.12",
"zod": "catalog:"
},
"devDependencies": {
"@types/node": "^22.10.2",
"nodemon": "3.1.10",
"typescript": "catalog:",
"vite": "7.1.12",
"vite-node": "3.2.4"
}
}

View File

@@ -0,0 +1,62 @@
import { DEFAULT_BASE_DOMAIN } from '@afilmory/utils'
import { z } from 'zod'
const booleanSchema = z
.union([z.boolean(), z.string()])
.optional()
.transform((value) => {
if (typeof value === 'boolean') {
return value
}
if (value === undefined) {
return
}
const normalized = value.trim().toLowerCase()
return !['false', '0', 'no', 'off'].includes(normalized)
})
const envSchema = z.object({
HOST: z.string().trim().min(1).default('0.0.0.0'),
PORT: z.coerce.number().int().min(1).max(65_535).default(8790),
BASE_DOMAIN: z
.string()
.trim()
.min(1)
.regex(/^[a-z0-9.-]+$/i, { message: 'BASE_DOMAIN must be a valid hostname.' })
.default(DEFAULT_BASE_DOMAIN),
FORCE_HTTPS: booleanSchema.default(true),
CALLBACK_BASE_PATH: z
.string()
.trim()
.default('/api/auth/callback')
.transform((value) => value.replace(/\/+$/, '') || '/api/auth/callback'),
ALLOW_CUSTOM_HOST: booleanSchema.default(false),
ROOT_SLUG: z
.string()
.trim()
.min(1)
.regex(/^[a-z0-9-]+$/i)
.default('root'),
})
const parsed = envSchema.parse({
HOST: process.env.AUTH_GATEWAY_HOST ?? process.env.HOST,
PORT: process.env.AUTH_GATEWAY_PORT ?? process.env.PORT,
BASE_DOMAIN: process.env.AUTH_GATEWAY_BASE_DOMAIN,
FORCE_HTTPS: process.env.AUTH_GATEWAY_FORCE_HTTPS,
CALLBACK_BASE_PATH: process.env.AUTH_GATEWAY_CALLBACK_BASE_PATH,
ALLOW_CUSTOM_HOST: process.env.AUTH_GATEWAY_ALLOW_CUSTOM_HOST,
ROOT_SLUG: process.env.AUTH_GATEWAY_ROOT_SLUG,
})
export const gatewayConfig = {
host: parsed.HOST,
port: parsed.PORT,
baseDomain: parsed.BASE_DOMAIN.toLowerCase(),
forceHttps: Boolean(parsed.FORCE_HTTPS),
callbackBasePath: parsed.CALLBACK_BASE_PATH,
allowCustomHost: Boolean(parsed.ALLOW_CUSTOM_HOST),
rootSlug: parsed.ROOT_SLUG.toLowerCase(),
} as const
export type GatewayConfig = typeof gatewayConfig

View File

@@ -0,0 +1,86 @@
import { serve } from '@hono/node-server'
import { Hono } from 'hono'
import { gatewayConfig } from './config'
import { buildForwardLocation, resolveTargetHost, sanitizeExplicitHost, sanitizeTenantSlug } from './resolver'
const app = new Hono()
app.get('/healthz', (c) =>
c.json({
status: 'ok',
service: 'oauth-gateway',
timestamp: new Date().toISOString(),
}),
)
const callbackRouter = new Hono()
callbackRouter.all('/:provider', (c) => {
const provider = c.req.param('provider')
if (!provider) {
return c.json({ error: 'missing_provider', message: 'Provider param is required.' }, 400)
}
const requestUrl = new URL(c.req.url)
const tenantSlugParam = requestUrl.searchParams.get('tenantSlug') ?? requestUrl.searchParams.get('tenant')
const explicitHostParam = requestUrl.searchParams.get('targetHost')
const tenantSlug = sanitizeTenantSlug(tenantSlugParam)
const explicitHost = sanitizeExplicitHost(explicitHostParam)
requestUrl.searchParams.delete('tenant')
requestUrl.searchParams.delete('tenantSlug')
requestUrl.searchParams.delete('targetHost')
if (tenantSlugParam && !tenantSlug) {
return c.json({ error: 'invalid_tenant', message: 'Tenant slug is invalid.' }, 400)
}
if (explicitHostParam && !explicitHost) {
return c.json({ error: 'invalid_host', message: 'Target host is invalid.' }, 400)
}
const targetHost = resolveTargetHost(gatewayConfig, { tenantSlug, explicitHost })
if (!targetHost) {
return c.json({ error: 'unresolvable_host', message: 'Unable to resolve target tenant host.' }, 400)
}
const location = buildForwardLocation({
config: gatewayConfig,
provider,
host: targetHost,
query: requestUrl.searchParams,
})
return c.redirect(location, 302)
})
app.route(gatewayConfig.callbackBasePath, callbackRouter)
app.notFound((c) =>
c.json(
{
error: 'not_found',
path: c.req.path,
},
404,
),
)
app.onError((err, c) => {
console.error('[oauth-gateway] Unhandled error', err)
return c.json({ error: 'internal_error', message: 'OAuth gateway encountered an unexpected error.' }, 500)
})
serve(
{
fetch: app.fetch,
hostname: gatewayConfig.host,
port: gatewayConfig.port,
},
(info) => {
console.info(
`[oauth-gateway] listening on http://${info.address}:${info.port} | forwarding to base domain ${gatewayConfig.baseDomain}`,
)
},
)

View File

@@ -0,0 +1,77 @@
import type { GatewayConfig } from './config'
export interface TargetResolutionInput {
tenantSlug?: string | null
explicitHost?: string | null
}
const SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i
const HOST_PATTERN = /^[a-z0-9.-]+(?::\d{1,5})?$/i
export function sanitizeTenantSlug(slug: string | null | undefined): string | null {
if (!slug) {
return null
}
const trimmed = slug.trim().toLowerCase()
if (!SLUG_PATTERN.test(trimmed)) {
return null
}
return trimmed
}
export function sanitizeExplicitHost(host: string | null | undefined): string | null {
if (!host) {
return null
}
const normalized = host.trim().toLowerCase()
let value = normalized
if (normalized.startsWith('http://') || normalized.startsWith('https://')) {
try {
value = new URL(normalized).host
} catch {
return null
}
}
if (!HOST_PATTERN.test(value)) {
return null
}
return value
}
export function resolveTargetHost(config: GatewayConfig, input: TargetResolutionInput): string | null {
if (input.explicitHost && config.allowCustomHost) {
return input.explicitHost
}
const slug = input.tenantSlug
if (slug && slug !== config.rootSlug) {
return `${slug}.${config.baseDomain}`
}
return config.baseDomain
}
export function resolveProtocol(config: GatewayConfig, host: string): 'http' | 'https' {
if (!config.forceHttps) {
return host.includes('localhost') || host.startsWith('127.') || host.endsWith('.local') ? 'http' : 'https'
}
if (host.includes('localhost') || host.startsWith('127.') || host.endsWith('.local')) {
return 'http'
}
return 'https'
}
export function buildForwardLocation(params: {
config: GatewayConfig
provider: string
host: string
query: URLSearchParams
}): string {
const basePath = `${params.config.callbackBasePath}/${params.provider}`
const queryString = params.query.toString()
const protocol = resolveProtocol(params.config, params.host)
const baseUrl = `${protocol}://${params.host}${basePath}`
return queryString ? `${baseUrl}?${queryString}` : baseUrl
}

View File

@@ -0,0 +1,17 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"verbatimModuleSyntax": true,
"strict": true,
"types": ["node"],
"outDir": "./dist",
"rootDir": "./src",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"resolveJsonModule": true
},
"include": ["./src/**/*.ts"]
}

View File

@@ -0,0 +1,28 @@
import { builtinModules } from 'node:module'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { defineConfig } from 'vite'
const NODE_BUILT_INS = builtinModules.filter((m) => !m.startsWith('_'))
NODE_BUILT_INS.push(...NODE_BUILT_INS.map((m) => `node:${m}`))
const __dirname = dirname(fileURLToPath(import.meta.url))
export default defineConfig({
ssr: {
noExternal: true,
},
build: {
ssr: true,
rollupOptions: {
external: NODE_BUILT_INS,
input: {
main: resolve(__dirname, 'src/index.ts'),
},
output: {
entryFileNames: 'main.js',
},
},
},
})

189
pnpm-lock.yaml generated
View File

@@ -200,7 +200,7 @@ importers:
version: 12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
next:
specifier: 16.0.1
version: 16.0.1(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
version: 16.0.1(@babel/core@7.28.5)(babel-plugin-react-compiler@19.1.0-rc.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
next-themes:
specifier: 0.4.6
version: 0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
@@ -430,7 +430,7 @@ importers:
version: 0.31.6
next:
specifier: 16.0.1
version: 16.0.1(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
version: 16.0.1(@babel/core@7.28.5)(babel-plugin-react-compiler@19.1.0-rc.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
postcss:
specifier: 8.5.6
version: 8.5.6
@@ -478,7 +478,7 @@ importers:
version: 2.2.9(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@lobehub/fluent-emoji':
specifier: 2.0.0
version: 2.0.0(@babel/core@7.28.4)(@types/react@19.2.2)(acorn@8.15.0)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
version: 2.0.0(@babel/core@7.28.5)(@types/react@19.2.2)(acorn@8.15.0)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@maplibre/maplibre-gl-geocoder':
specifier: ^1.9.1
version: 1.9.1(maplibre-gl@5.10.0)
@@ -541,7 +541,7 @@ importers:
version: 10.2.0
jotai:
specifier: 2.15.0
version: 2.15.0(@babel/core@7.28.4)(@babel/template@7.27.2)(@types/react@19.2.2)(react@19.2.0)
version: 2.15.0(@babel/core@7.28.5)(@babel/template@7.27.2)(@types/react@19.2.2)(react@19.2.0)
maplibre-gl:
specifier: ^5.10.0
version: 5.10.0
@@ -589,7 +589,7 @@ importers:
version: 7.9.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
react-scan:
specifier: 0.4.3
version: 0.4.3(@types/react@19.2.2)(next@16.0.1(@babel/core@7.28.4)(babel-plugin-react-compiler@19.1.0-rc.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react-router-dom@6.30.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-router@7.9.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0)(rollup@2.79.2)
version: 0.4.3(@types/react@19.2.2)(next@16.0.1(@babel/core@7.28.5)(babel-plugin-react-compiler@19.1.0-rc.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react-router-dom@6.30.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-router@7.9.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0)(rollup@2.79.2)
react-use-measure:
specifier: 2.1.7
version: 2.1.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
@@ -1066,6 +1066,37 @@ importers:
specifier: 5.1.4
version: 5.1.4(typescript@5.9.3)(vite@7.1.12(@types/node@24.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))
be/apps/oauth-gateway:
dependencies:
'@afilmory/utils':
specifier: workspace:*
version: link:../../../packages/utils
'@hono/node-server':
specifier: ^1.13.5
version: 1.19.6(hono@4.10.4)
hono:
specifier: ^4.6.12
version: 4.10.4
zod:
specifier: 'catalog:'
version: 4.1.12
devDependencies:
'@types/node':
specifier: ^22.10.2
version: 22.19.1
nodemon:
specifier: 3.1.10
version: 3.1.10
typescript:
specifier: 'catalog:'
version: 5.9.3
vite:
specifier: 7.1.12
version: 7.1.12(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)
vite-node:
specifier: 3.2.4
version: 3.2.4(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)
be/packages/db:
dependencies:
drizzle-orm:
@@ -5803,6 +5834,9 @@ packages:
'@types/node@20.19.24':
resolution: {integrity: sha512-FE5u0ezmi6y9OZEzlJfg37mqqf6ZDSF2V/NLjUyGrR9uTZ7Sb9F7bLNZ03S4XVUNRWGA7Ck4c1kK+YnuWjl+DA==}
'@types/node@22.19.1':
resolution: {integrity: sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==}
'@types/node@24.10.0':
resolution: {integrity: sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==}
@@ -12740,9 +12774,9 @@ snapshots:
regexpu-core: 6.4.0
semver: 6.3.1
'@babel/helper-define-polyfill-provider@0.6.4(@babel/core@7.28.4)':
'@babel/helper-define-polyfill-provider@0.6.4(@babel/core@7.28.5)':
dependencies:
'@babel/core': 7.28.4
'@babel/core': 7.28.5
'@babel/helper-compilation-targets': 7.27.2
'@babel/helper-plugin-utils': 7.27.1
debug: 4.4.3(supports-color@5.5.0)
@@ -13211,14 +13245,14 @@ snapshots:
'@babel/core': 7.28.5
'@babel/helper-plugin-utils': 7.27.1
'@babel/plugin-transform-runtime@7.27.4(@babel/core@7.28.4)':
'@babel/plugin-transform-runtime@7.27.4(@babel/core@7.28.5)':
dependencies:
'@babel/core': 7.28.4
'@babel/core': 7.28.5
'@babel/helper-module-imports': 7.27.1
'@babel/helper-plugin-utils': 7.27.1
babel-plugin-polyfill-corejs2: 0.4.13(@babel/core@7.28.4)
babel-plugin-polyfill-corejs3: 0.11.1(@babel/core@7.28.4)
babel-plugin-polyfill-regenerator: 0.6.4(@babel/core@7.28.4)
babel-plugin-polyfill-corejs2: 0.4.13(@babel/core@7.28.5)
babel-plugin-polyfill-corejs3: 0.11.1(@babel/core@7.28.5)
babel-plugin-polyfill-regenerator: 0.6.4(@babel/core@7.28.5)
semver: 6.3.1
transitivePeerDependencies:
- supports-color
@@ -14485,10 +14519,10 @@ snapshots:
'@lobehub/emojilib@1.0.0': {}
'@lobehub/fluent-emoji@2.0.0(@babel/core@7.28.4)(@types/react@19.2.2)(acorn@8.15.0)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
'@lobehub/fluent-emoji@2.0.0(@babel/core@7.28.5)(@types/react@19.2.2)(acorn@8.15.0)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
dependencies:
'@lobehub/emojilib': 1.0.0
'@lobehub/ui': 2.7.3(@babel/core@7.28.4)(@types/react@19.2.2)(acorn@8.15.0)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@lobehub/ui': 2.7.3(@babel/core@7.28.5)(@types/react@19.2.2)(acorn@8.15.0)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
antd: 5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
antd-style: 3.7.1(@types/react@19.2.2)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
emoji-regex: 10.4.0
@@ -14505,9 +14539,9 @@ snapshots:
- framer-motion
- supports-color
'@lobehub/icons@2.7.0(@babel/core@7.28.4)(@types/react@19.2.2)(acorn@8.15.0)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
'@lobehub/icons@2.7.0(@babel/core@7.28.5)(@types/react@19.2.2)(acorn@8.15.0)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
dependencies:
'@lobehub/ui': 2.7.3(@babel/core@7.28.4)(@types/react@19.2.2)(acorn@8.15.0)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@lobehub/ui': 2.7.3(@babel/core@7.28.5)(@types/react@19.2.2)(acorn@8.15.0)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
antd: 5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
antd-style: 3.7.1(@types/react@19.2.2)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
lucide-react: 0.469.0(react@19.2.0)
@@ -14522,7 +14556,7 @@ snapshots:
- framer-motion
- supports-color
'@lobehub/ui@2.7.3(@babel/core@7.28.4)(@types/react@19.2.2)(acorn@8.15.0)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
'@lobehub/ui@2.7.3(@babel/core@7.28.5)(@types/react@19.2.2)(acorn@8.15.0)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
dependencies:
'@ant-design/cssinjs': 1.23.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@dnd-kit/core': 6.3.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
@@ -14533,8 +14567,8 @@ snapshots:
'@emoji-mart/react': 1.1.1(emoji-mart@5.6.0)(react@19.2.0)
'@floating-ui/react': 0.27.12(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@giscus/react': 3.1.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@lobehub/fluent-emoji': 2.0.0(@babel/core@7.28.4)(@types/react@19.2.2)(acorn@8.15.0)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@lobehub/icons': 2.7.0(@babel/core@7.28.4)(@types/react@19.2.2)(acorn@8.15.0)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@lobehub/fluent-emoji': 2.0.0(@babel/core@7.28.5)(@types/react@19.2.2)(acorn@8.15.0)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@lobehub/icons': 2.7.0(@babel/core@7.28.5)(@types/react@19.2.2)(acorn@8.15.0)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@mdx-js/mdx': 3.1.0(acorn@8.15.0)
'@mdx-js/react': 3.1.1(@types/react@19.2.2)(react@19.2.0)
'@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0)
@@ -14564,7 +14598,7 @@ snapshots:
rc-menu: 9.16.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
re-resizable: 6.11.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
react: 19.2.0
react-avatar-editor: 13.0.2(@babel/core@7.28.4)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
react-avatar-editor: 13.0.2(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
react-dom: 19.2.0(react@19.2.0)
react-error-boundary: 5.0.0(react@19.2.0)
react-hotkeys-hook: 5.1.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
@@ -17344,6 +17378,10 @@ snapshots:
dependencies:
undici-types: 6.21.0
'@types/node@22.19.1':
dependencies:
undici-types: 6.21.0
'@types/node@24.10.0':
dependencies:
undici-types: 7.16.0
@@ -18103,11 +18141,11 @@ snapshots:
cosmiconfig: 7.1.0
resolve: 1.22.11
babel-plugin-polyfill-corejs2@0.4.13(@babel/core@7.28.4):
babel-plugin-polyfill-corejs2@0.4.13(@babel/core@7.28.5):
dependencies:
'@babel/compat-data': 7.28.0
'@babel/core': 7.28.4
'@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.28.4)
'@babel/core': 7.28.5
'@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.28.5)
semver: 6.3.1
transitivePeerDependencies:
- supports-color
@@ -18121,10 +18159,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
babel-plugin-polyfill-corejs3@0.11.1(@babel/core@7.28.4):
babel-plugin-polyfill-corejs3@0.11.1(@babel/core@7.28.5):
dependencies:
'@babel/core': 7.28.4
'@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.28.4)
'@babel/core': 7.28.5
'@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.28.5)
core-js-compat: 3.46.0
transitivePeerDependencies:
- supports-color
@@ -18137,10 +18175,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
babel-plugin-polyfill-regenerator@0.6.4(@babel/core@7.28.4):
babel-plugin-polyfill-regenerator@0.6.4(@babel/core@7.28.5):
dependencies:
'@babel/core': 7.28.4
'@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.28.4)
'@babel/core': 7.28.5
'@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.28.5)
transitivePeerDependencies:
- supports-color
@@ -18184,7 +18222,7 @@ snapshots:
nanostores: 1.0.1
zod: 4.1.12
optionalDependencies:
next: 16.0.1(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
next: 16.0.1(@babel/core@7.28.5)(babel-plugin-react-compiler@19.1.0-rc.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
react: 19.2.0
react-dom: 19.2.0(react@19.2.0)
@@ -21048,13 +21086,6 @@ snapshots:
jose@6.1.0: {}
jotai@2.15.0(@babel/core@7.28.4)(@babel/template@7.27.2)(@types/react@19.2.2)(react@19.2.0):
optionalDependencies:
'@babel/core': 7.28.4
'@babel/template': 7.27.2
'@types/react': 19.2.2
react: 19.2.0
jotai@2.15.0(@babel/core@7.28.5)(@babel/template@7.27.2)(@types/react@19.2.2)(react@19.2.0):
optionalDependencies:
'@babel/core': 7.28.5
@@ -22101,32 +22132,7 @@ snapshots:
react: 19.2.0
react-dom: 19.2.0(react@19.2.0)
next@16.0.1(@babel/core@7.28.4)(babel-plugin-react-compiler@19.1.0-rc.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
dependencies:
'@next/env': 16.0.1
'@swc/helpers': 0.5.15
caniuse-lite: 1.0.30001752
postcss: 8.4.31
react: 19.2.0
react-dom: 19.2.0(react@19.2.0)
styled-jsx: 5.1.6(@babel/core@7.28.4)(react@19.2.0)
optionalDependencies:
'@next/swc-darwin-arm64': 16.0.1
'@next/swc-darwin-x64': 16.0.1
'@next/swc-linux-arm64-gnu': 16.0.1
'@next/swc-linux-arm64-musl': 16.0.1
'@next/swc-linux-x64-gnu': 16.0.1
'@next/swc-linux-x64-musl': 16.0.1
'@next/swc-win32-arm64-msvc': 16.0.1
'@next/swc-win32-x64-msvc': 16.0.1
babel-plugin-react-compiler: 19.1.0-rc.3
sharp: 0.34.4
transitivePeerDependencies:
- '@babel/core'
- babel-plugin-macros
optional: true
next@16.0.1(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
next@16.0.1(@babel/core@7.28.5)(babel-plugin-react-compiler@19.1.0-rc.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
dependencies:
'@next/env': 16.0.1
'@swc/helpers': 0.5.15
@@ -22144,6 +22150,7 @@ snapshots:
'@next/swc-linux-x64-musl': 16.0.1
'@next/swc-win32-arm64-msvc': 16.0.1
'@next/swc-win32-x64-msvc': 16.0.1
babel-plugin-react-compiler: 19.1.0-rc.3
sharp: 0.34.4
transitivePeerDependencies:
- '@babel/core'
@@ -23063,9 +23070,9 @@ snapshots:
react: 19.2.0
react-dom: 19.2.0(react@19.2.0)
react-avatar-editor@13.0.2(@babel/core@7.28.4)(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
react-avatar-editor@13.0.2(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
dependencies:
'@babel/plugin-transform-runtime': 7.27.4(@babel/core@7.28.4)
'@babel/plugin-transform-runtime': 7.27.4(@babel/core@7.28.5)
'@babel/runtime': 7.28.4
prop-types: 15.8.1
react: 19.2.0
@@ -23246,7 +23253,7 @@ snapshots:
optionalDependencies:
react-dom: 19.2.0(react@19.2.0)
react-scan@0.4.3(@types/react@19.2.2)(next@16.0.1(@babel/core@7.28.4)(babel-plugin-react-compiler@19.1.0-rc.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react-router-dom@6.30.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-router@7.9.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0)(rollup@2.79.2):
react-scan@0.4.3(@types/react@19.2.2)(next@16.0.1(@babel/core@7.28.5)(babel-plugin-react-compiler@19.1.0-rc.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react-router-dom@6.30.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-router@7.9.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0)(rollup@2.79.2):
dependencies:
'@babel/core': 7.28.4
'@babel/generator': 7.28.3
@@ -23268,7 +23275,7 @@ snapshots:
react-dom: 19.2.0(react@19.2.0)
tsx: 4.20.6
optionalDependencies:
next: 16.0.1(@babel/core@7.28.4)(babel-plugin-react-compiler@19.1.0-rc.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
next: 16.0.1(@babel/core@7.28.5)(babel-plugin-react-compiler@19.1.0-rc.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
react-router: 7.9.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
react-router-dom: 6.30.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
unplugin: 2.1.0
@@ -23299,7 +23306,7 @@ snapshots:
react-dom: 19.2.0(react@19.2.0)
tsx: 4.20.6
optionalDependencies:
next: 16.0.1(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
next: 16.0.1(@babel/core@7.28.5)(babel-plugin-react-compiler@19.1.0-rc.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
react-router: 7.9.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
react-router-dom: 6.30.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
unplugin: 2.1.0
@@ -24164,14 +24171,6 @@ snapshots:
dependencies:
inline-style-parser: 0.2.4
styled-jsx@5.1.6(@babel/core@7.28.4)(react@19.2.0):
dependencies:
client-only: 0.0.1
react: 19.2.0
optionalDependencies:
'@babel/core': 7.28.4
optional: true
styled-jsx@5.1.6(@babel/core@7.28.5)(react@19.2.0):
dependencies:
client-only: 0.0.1
@@ -24813,6 +24812,27 @@ snapshots:
vite-bundle-analyzer@1.2.3: {}
vite-node@3.2.4(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1):
dependencies:
cac: 6.7.14
debug: 4.4.3(supports-color@5.5.0)
es-module-lexer: 1.7.0
pathe: 2.0.3
vite: 7.1.12(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)
transitivePeerDependencies:
- '@types/node'
- jiti
- less
- lightningcss
- sass
- sass-embedded
- stylus
- sugarss
- supports-color
- terser
- tsx
- yaml
vite-node@3.2.4(@types/node@24.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1):
dependencies:
cac: 6.7.14
@@ -24896,6 +24916,23 @@ snapshots:
- supports-color
- typescript
vite@7.1.12(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1):
dependencies:
esbuild: 0.25.11
fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3
postcss: 8.5.6
rollup: 4.52.5
tinyglobby: 0.2.15
optionalDependencies:
'@types/node': 22.19.1
fsevents: 2.3.3
jiti: 2.6.1
lightningcss: 1.30.2
terser: 5.44.1
tsx: 4.20.6
yaml: 2.8.1
vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1):
dependencies:
esbuild: 0.25.11