From 3a82416dfa0c7eecbb1f2dcabbb0b5af7b93c3e1 Mon Sep 17 00:00:00 2001 From: Elliot Braem Date: Thu, 13 Aug 2026 13:55:46 -0500 Subject: [PATCH 01/17] wip --- .env.example | 3 - api/src/contract.ts | 30 ++ .../db/migrations/0002_awesome_madame_web.sql | 3 + api/src/db/migrations/meta/0002_snapshot.json | 176 ++++++++ api/src/db/migrations/meta/_journal.json | 9 +- api/src/db/schema.ts | 13 +- api/src/index.ts | 10 + api/src/services/tenants.ts | 52 ++- api/tests/unit/tenants-service.test.ts | 56 +++ bos.config.json | 3 - host/.env.example | 3 - host/src/services/binding-resolver.ts | 148 +++++++ host/src/services/tenant-runtime.ts | 164 +------- .../integration/tenant-host-nested.test.ts | 45 ++- host/tests/integration/tenant-runtime.test.ts | 322 ++++++++++----- tests/regression/http/tenant_bindings_test.go | 184 +++++++++ ui/src/routes/_layout.tsx | 380 +----------------- ui/src/routes/_layout/_authenticated.tsx | 251 +++++++++++- .../_layout/_authenticated/admin/index.tsx | 104 +++++ .../_layout/_authenticated/admin/system.tsx | 73 ++++ ui/src/routes/_layout/_public.tsx | 53 +++ ui/src/routes/_layout/{ => _public}/about.tsx | 2 +- ui/src/routes/_layout/_public/index.tsx | 111 +++++ ui/src/routes/_layout/{ => _public}/login.tsx | 50 +-- ui/src/routes/_layout/index.tsx | 122 ------ ui/src/routes/_layout/skill.tsx | 126 ------ 26 files changed, 1563 insertions(+), 930 deletions(-) create mode 100644 api/src/db/migrations/0002_awesome_madame_web.sql create mode 100644 api/src/db/migrations/meta/0002_snapshot.json create mode 100644 host/src/services/binding-resolver.ts create mode 100644 tests/regression/http/tenant_bindings_test.go create mode 100644 ui/src/routes/_layout/_authenticated/admin/index.tsx create mode 100644 ui/src/routes/_layout/_authenticated/admin/system.tsx create mode 100644 ui/src/routes/_layout/_public.tsx rename ui/src/routes/_layout/{ => _public}/about.tsx (99%) create mode 100644 ui/src/routes/_layout/_public/index.tsx rename ui/src/routes/_layout/{ => _public}/login.tsx (86%) delete mode 100644 ui/src/routes/_layout/index.tsx delete mode 100644 ui/src/routes/_layout/skill.tsx diff --git a/.env.example b/.env.example index 450772d4..452789fd 100644 --- a/.env.example +++ b/.env.example @@ -3,9 +3,6 @@ # app.host CORS_ORIGIN=http://localhost:4100 -TENANT_WHITELIST= -ALLOW_OVERRIDE= -ALLOW_UNTRUSTED_SSR= CSP_STRICT= # app.api diff --git a/api/src/contract.ts b/api/src/contract.ts index 5887408c..92bc79b2 100644 --- a/api/src/contract.ts +++ b/api/src/contract.ts @@ -20,6 +20,9 @@ export const TenantSchema = z.object({ orgId: z.string(), name: z.string(), status: TenantStatusSchema, + allowUiOverrides: z.boolean(), + allowBackendOverrides: z.boolean(), + allowSsr: z.boolean(), createdAt: z.string(), updatedAt: z.string(), deletedAt: z.string().nullable(), @@ -27,6 +30,17 @@ export const TenantSchema = z.object({ export type Tenant = z.infer; +export const TenantBindingSchema = z.object({ + hostname: z + .string() + .describe("Subdomain hostname that routes to this tenant on the parent domain"), + accountId: z.string(), + allowUiOverrides: z.boolean(), + allowBackendOverrides: z.boolean(), + allowSsr: z.boolean(), + status: TenantStatusSchema, +}); + const ThingSchema = z.object({ thingId: z.string().describe("Unique identifier for the thing"), type: z.string().describe("Plugin-derived thing type"), @@ -80,6 +94,9 @@ export const contract = oc.router({ name: z.string(), accountId: z.string(), status: z.enum(["active", "pending"]).optional(), + allowUiOverrides: z.boolean().default(true), + allowBackendOverrides: z.boolean().default(false), + allowSsr: z.boolean().default(false), }), ) .output(TenantSchema) @@ -94,6 +111,9 @@ export const contract = oc.router({ subdomain: z.string().optional(), accountId: z.string().optional(), status: TenantStatusSchema.optional(), + allowUiOverrides: z.boolean().optional(), + allowBackendOverrides: z.boolean().optional(), + allowSsr: z.boolean().optional(), }), ) .output(TenantSchema) @@ -128,6 +148,16 @@ export const contract = oc.router({ .output(TenantSchema) .errors({ NOT_FOUND }), + listTenantBindings: oc + .route({ + method: "GET", + path: "/tenants/bindings", + summary: "List all active tenant domain bindings", + description: + "Public — returns the subdomain-to-config mapping used by the host's BindingResolver.", + }) + .output(z.array(TenantBindingSchema)), + tenantPreflight: oc .route({ method: "POST", path: "/tenants/preflight" }) .input( diff --git a/api/src/db/migrations/0002_awesome_madame_web.sql b/api/src/db/migrations/0002_awesome_madame_web.sql new file mode 100644 index 00000000..e39a3327 --- /dev/null +++ b/api/src/db/migrations/0002_awesome_madame_web.sql @@ -0,0 +1,3 @@ +ALTER TABLE "tenants" ADD COLUMN "allow_ui_overrides" boolean DEFAULT true NOT NULL;--> statement-breakpoint +ALTER TABLE "tenants" ADD COLUMN "allow_backend_overrides" boolean DEFAULT false NOT NULL;--> statement-breakpoint +ALTER TABLE "tenants" ADD COLUMN "allow_ssr" boolean DEFAULT false NOT NULL; \ No newline at end of file diff --git a/api/src/db/migrations/meta/0002_snapshot.json b/api/src/db/migrations/meta/0002_snapshot.json new file mode 100644 index 00000000..fda7cde0 --- /dev/null +++ b/api/src/db/migrations/meta/0002_snapshot.json @@ -0,0 +1,176 @@ +{ + "id": "7b8a421a-d0c7-414a-96e8-0c259935178b", + "prevId": "3e88c17e-53d5-4809-b872-33d2f87a8a90", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.tenants": { + "name": "tenants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "subdomain": { + "name": "subdomain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "tenant_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "allow_ui_overrides": { + "name": "allow_ui_overrides", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "allow_backend_overrides": { + "name": "allow_backend_overrides", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allow_ssr": { + "name": "allow_ssr", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "tenants_subdomain_idx": { + "name": "tenants_subdomain_idx", + "columns": [ + { + "expression": "subdomain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tenants_account_id_idx": { + "name": "tenants_account_id_idx", + "columns": [ + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tenants_subdomain_unique": { + "name": "tenants_subdomain_unique", + "nullsNotDistinct": false, + "columns": [ + "subdomain" + ] + }, + "tenants_account_id_unique": { + "name": "tenants_account_id_unique", + "nullsNotDistinct": false, + "columns": [ + "account_id" + ] + }, + "tenants_org_id_unique": { + "name": "tenants_org_id_unique", + "nullsNotDistinct": false, + "columns": [ + "org_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.tenant_status": { + "name": "tenant_status", + "schema": "public", + "values": [ + "active", + "pending", + "suspended", + "pending_deletion" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/api/src/db/migrations/meta/_journal.json b/api/src/db/migrations/meta/_journal.json index c939ec4a..9e5e42bb 100644 --- a/api/src/db/migrations/meta/_journal.json +++ b/api/src/db/migrations/meta/_journal.json @@ -15,6 +15,13 @@ "when": 1786078137908, "tag": "0001_brief_magik", "breakpoints": true + }, + { + "idx": 2, + "version": "7", + "when": 1786645659094, + "tag": "0002_awesome_madame_web", + "breakpoints": true } ] -} +} \ No newline at end of file diff --git a/api/src/db/schema.ts b/api/src/db/schema.ts index b141f76b..c689380d 100644 --- a/api/src/db/schema.ts +++ b/api/src/db/schema.ts @@ -1,4 +1,12 @@ -import { pgEnum, pgTable, text, timestamp, uniqueIndex, uuid } from "drizzle-orm/pg-core"; +import { + boolean, + pgEnum, + pgTable, + text, + timestamp, + uniqueIndex, + uuid, +} from "drizzle-orm/pg-core"; export const tenantStatus = pgEnum("tenant_status", [ "active", @@ -16,6 +24,9 @@ export const tenants = pgTable( orgId: text("org_id").notNull().unique(), name: text("name").notNull(), status: tenantStatus("status").default("active").notNull(), + allowUiOverrides: boolean("allow_ui_overrides").default(true).notNull(), + allowBackendOverrides: boolean("allow_backend_overrides").default(false).notNull(), + allowSsr: boolean("allow_ssr").default(false).notNull(), createdAt: timestamp("created_at", { mode: "date", withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp("updated_at", { mode: "date", withTimezone: true }).defaultNow().notNull(), deletedAt: timestamp("deleted_at", { mode: "date", withTimezone: true }), diff --git a/api/src/index.ts b/api/src/index.ts index df235000..9ebbaed8 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -144,6 +144,9 @@ export default createPlugin.withPlugins()({ accountId: input.accountId, orgId: context.organization.activeOrganizationId, status: input.status, + allowUiOverrides: input.allowUiOverrides, + allowBackendOverrides: input.allowBackendOverrides, + allowSsr: input.allowSsr, }); }), @@ -159,6 +162,9 @@ export default createPlugin.withPlugins()({ subdomain: input.subdomain, accountId: input.accountId, status: input.status, + allowUiOverrides: input.allowUiOverrides, + allowBackendOverrides: input.allowBackendOverrides, + allowSsr: input.allowSsr, }); }), @@ -223,6 +229,10 @@ export default createPlugin.withPlugins()({ return tenant; }), + listTenantBindings: builder.listTenantBindings.handler(async () => + services.tenants.listBindings(), + ), + tenantPreflight: builder.tenantPreflight.use(requireAuth).handler(async ({ input }) => { const subdomainValid = SUBDOMAIN_SEGMENT_REGEX.test(input.subdomain); const accountId = `${input.subdomain}.${input.parentAccount}`; diff --git a/api/src/services/tenants.ts b/api/src/services/tenants.ts index 2754489d..4cdb1aa5 100644 --- a/api/src/services/tenants.ts +++ b/api/src/services/tenants.ts @@ -13,25 +13,46 @@ export interface TenantRecord { orgId: string; name: string; status: TenantStatus; + allowUiOverrides: boolean; + allowBackendOverrides: boolean; + allowSsr: boolean; createdAt: string; updatedAt: string; deletedAt: string | null; } +export interface TenantBinding { + hostname: string; + accountId: string; + allowUiOverrides: boolean; + allowBackendOverrides: boolean; + allowSsr: boolean; + status: TenantStatus; +} + export interface TenantInput { subdomain: string; name: string; accountId: string; orgId: string; status?: TenantStatus; + allowUiOverrides?: boolean; + allowBackendOverrides?: boolean; + allowSsr?: boolean; } export interface TenantsService { listTenantsByOrgIds(orgIds: string[]): Promise; + listBindings(): Promise; createTenant(input: TenantInput): Promise; updateTenant( id: string, - input: Partial>, + input: Partial< + Pick< + TenantInput, + "name" | "subdomain" | "accountId" | "status" | "allowUiOverrides" | "allowBackendOverrides" | "allowSsr" + > + >, ): Promise; softDeleteTenant(id: string): Promise; suspendTenant(id: string): Promise; @@ -55,6 +76,9 @@ function toTenantRecord(row: TenantRow): TenantRecord { orgId: row.orgId, name: row.name, status: row.status, + allowUiOverrides: row.allowUiOverrides, + allowBackendOverrides: row.allowBackendOverrides, + allowSsr: row.allowSsr, createdAt: row.createdAt instanceof Date ? row.createdAt.toISOString() : String(row.createdAt), updatedAt: row.updatedAt instanceof Date ? row.updatedAt.toISOString() : String(row.updatedAt), deletedAt: row.deletedAt instanceof Date ? row.deletedAt.toISOString() : null, @@ -88,6 +112,27 @@ export const TenantsLive = Layer.effect( } }, + listBindings: async () => { + try { + const rows = await db + .select({ + subdomain: tenantsTable.subdomain, + accountId: tenantsTable.accountId, + allowUiOverrides: tenantsTable.allowUiOverrides, + allowBackendOverrides: tenantsTable.allowBackendOverrides, + allowSsr: tenantsTable.allowSsr, + status: tenantsTable.status, + }) + .from(tenantsTable); + return rows.map(({ subdomain, ...binding }) => ({ + ...binding, + hostname: subdomain, + })); + } catch (error) { + throw toOrpcError(error); + } + }, + createTenant: async (input) => { try { const [row] = await db @@ -98,6 +143,11 @@ export const TenantsLive = Layer.effect( accountId: input.accountId, orgId: input.orgId, ...(input.status !== undefined && { status: input.status }), + ...(input.allowUiOverrides !== undefined && { allowUiOverrides: input.allowUiOverrides }), + ...(input.allowBackendOverrides !== undefined && { + allowBackendOverrides: input.allowBackendOverrides, + }), + ...(input.allowSsr !== undefined && { allowSsr: input.allowSsr }), }) .onConflictDoNothing() .returning(); diff --git a/api/tests/unit/tenants-service.test.ts b/api/tests/unit/tenants-service.test.ts index 427ad07f..fd04bd4b 100644 --- a/api/tests/unit/tenants-service.test.ts +++ b/api/tests/unit/tenants-service.test.ts @@ -147,6 +147,62 @@ describe("TenantsService", () => { expect(await runService(layer, (svc) => svc.listTenantsByOrgIds([]))).toEqual([]); }); + it("lists active and non-deleted bindings with default permissions", async () => { + const layer = freshLayer(); + await runService(layer, (svc) => + svc.createTenant({ + ...baseInput, + subdomain: "acme", + accountId: "acme.example.near", + }), + ); + const suspended = await runService(layer, (svc) => + svc.createTenant({ + ...baseInput, + subdomain: "beta", + accountId: "beta.example.near", + orgId: "org-2", + }), + ); + await runService(layer, (svc) => svc.suspendTenant(suspended.id)); + + const bindings = await runService(layer, (svc) => svc.listBindings()); + + expect(bindings).toHaveLength(2); + expect(bindings.find((b) => b.hostname === "acme")).toMatchObject({ + hostname: "acme", + accountId: "acme.example.near", + allowUiOverrides: true, + allowBackendOverrides: false, + allowSsr: false, + status: "active", + }); + expect(bindings.find((b) => b.hostname === "beta")?.status).toBe("suspended"); + }); + + it("persists allow_* overrides on create and update", async () => { + const layer = freshLayer(); + const created = await runService(layer, (svc) => + svc.createTenant({ + ...baseInput, + allowUiOverrides: false, + allowBackendOverrides: true, + allowSsr: true, + }), + ); + expect(created).toMatchObject({ + allowUiOverrides: false, + allowBackendOverrides: true, + allowSsr: true, + }); + + const updated = await runService(layer, (svc) => + svc.updateTenant(created.id, { allowSsr: false }), + ); + expect(updated.allowSsr).toBe(false); + expect(updated.allowUiOverrides).toBe(false); + }); + it("updates a tenant name", async () => { const layer = freshLayer(); const created = await runService(layer, (svc) => svc.createTenant(baseInput)); diff --git a/bos.config.json b/bos.config.json index b31993ec..436021f2 100644 --- a/bos.config.json +++ b/bos.config.json @@ -18,9 +18,6 @@ "production": "https://elliot-braem-7253-host-everything-dev-nearbuilder-aa7d0803c-ze.zephyrcloud.app", "secrets": [ "CORS_ORIGIN", - "TENANT_WHITELIST", - "ALLOW_OVERRIDE", - "ALLOW_UNTRUSTED_SSR", "CSP_STRICT" ] }, diff --git a/host/.env.example b/host/.env.example index 1283ab78..f5d82332 100644 --- a/host/.env.example +++ b/host/.env.example @@ -1,8 +1,5 @@ # app.host CORS_ORIGIN=http://localhost:3000 -TENANT_WHITELIST= -ALLOW_OVERRIDE= -ALLOW_UNTRUSTED_SSR= CSP_STRICT= # app.api diff --git a/host/src/services/binding-resolver.ts b/host/src/services/binding-resolver.ts new file mode 100644 index 00000000..30ed5ee4 --- /dev/null +++ b/host/src/services/binding-resolver.ts @@ -0,0 +1,148 @@ +import { logger } from "../utils/logger"; +import { resolveDomain } from "../utils/normalize"; +import type { RuntimeConfig } from "./config"; + +export type TenantBindingStatus = "active" | "pending" | "suspended" | "pending_deletion"; + +export interface TenantBinding { + hostname: string; + accountId: string; + allowUiOverrides: boolean; + allowBackendOverrides: boolean; + allowSsr: boolean; + status: TenantBindingStatus; +} + +const BINDINGS_TTL_MS = 30_000; + +interface CachedBindings { + apiUrl: string; + expiresAt: number; + entries: Map; + refetching?: Promise>; +} + +let bindingsCache: CachedBindings | null = null; + +export function clearBindingResolverCache() { + bindingsCache = null; +} + +function isBaseHost(hostname: string, gatewayId: string): boolean { + const normalized = hostname.toLowerCase(); + return ( + normalized === gatewayId || + normalized === "localhost" || + normalized === "127.0.0.1" + ); +} + +async function fetchBindingsFromApi(apiUrl: string): Promise { + const endpoint = `${apiUrl.replace(/\/$/, "")}/api/tenants/bindings`; + let response: Response; + try { + response = await fetch(endpoint); + } catch (cause) { + throw new Error(`Failed to reach API at ${endpoint}: ${String(cause)}`, { cause }); + } + + if (!response.ok) { + throw new Error(`GET ${endpoint} failed with HTTP ${response.status}`); + } + + return (await response.json()) as TenantBinding[]; +} + +function resolveGatewayId(config: RuntimeConfig): string { + return resolveDomain(config.domain, config.host.url).toLowerCase(); +} + +function mapBindingsByHostname( + config: RuntimeConfig, + bindings: TenantBinding[], +): Map { + const gatewayId = resolveGatewayId(config); + const entries = new Map(); + for (const binding of bindings) { + entries.set(`${binding.hostname.toLowerCase()}.${gatewayId}`, binding); + } + return entries; +} + +function ensureBindingsLoaded( + config: RuntimeConfig, +): Promise> { + const apiUrl = config.api?.url; + if (!apiUrl) { + return Promise.resolve(new Map()); + } + + const now = Date.now(); + if ( + bindingsCache && + bindingsCache.apiUrl === apiUrl && + bindingsCache.expiresAt > now && + bindingsCache.entries.size > 0 + ) { + return Promise.resolve(bindingsCache.entries); + } + + if (bindingsCache?.apiUrl === apiUrl && bindingsCache.refetching) { + return bindingsCache.refetching; + } + + const staleEntries = + bindingsCache?.apiUrl === apiUrl ? bindingsCache.entries : null; + + const fetchPromise = fetchBindingsFromApi(apiUrl) + .then((bindings) => { + const entries = mapBindingsByHostname(config, bindings); + bindingsCache = { apiUrl, expiresAt: Date.now() + BINDINGS_TTL_MS, entries }; + return entries; + }) + .catch((cause) => { + if (staleEntries && staleEntries.size > 0) { + logger.error( + `[BindingResolver] Refresh failed, serving ${staleEntries.size} stale binding(s): ${cause instanceof Error ? cause.message : String(cause)}`, + ); + bindingsCache = { + apiUrl, + expiresAt: Date.now() + BINDINGS_TTL_MS, + entries: staleEntries, + }; + return staleEntries; + } + bindingsCache = null; + throw cause; + }); + + bindingsCache = { + apiUrl, + expiresAt: now + BINDINGS_TTL_MS, + entries: staleEntries ?? new Map(), + refetching: fetchPromise, + }; + + return fetchPromise; +} + +export interface BindingResolver { + resolve(hostname: string): Promise; + clear(): void; +} + +export function createBindingResolver(config: RuntimeConfig): BindingResolver { + return { + async resolve(hostname: string): Promise { + const normalized = hostname.toLowerCase(); + const gatewayId = resolveGatewayId(config); + if (isBaseHost(normalized, gatewayId)) { + return null; + } + + const entries = await ensureBindingsLoaded(config); + return entries.get(normalized) ?? null; + }, + clear: clearBindingResolverCache, + }; +} diff --git a/host/src/services/tenant-runtime.ts b/host/src/services/tenant-runtime.ts index 5909f09b..4f3b0cae 100644 --- a/host/src/services/tenant-runtime.ts +++ b/host/src/services/tenant-runtime.ts @@ -1,28 +1,25 @@ import { buildRuntimeConfig, - isRuntimeOverrideAllowed, loadRemoteConfig, - parseRuntimeOverrideTargets, type RuntimeConfig, } from "everything-dev/config"; import { verifySriForUrl } from "everything-dev/integrity"; import type { RuntimePlugin } from "../types"; import { logger } from "../utils/logger"; import { resolveDomain } from "../utils/normalize"; +import { createBindingResolver, type BindingResolver } from "./binding-resolver"; const REMOTE_CONFIG_TTL_MS = 30_000; const VERIFICATION_TTL_MS = 5 * 60_000; const MAX_REMOTE_CONFIG_CACHE_SIZE = 256; const MAX_VERIFICATION_CACHE_SIZE = 512; -const NEAR_ACCOUNT_ID_REGEX = - /^(?=.{2,64}$)([a-z0-9]+(?:[-_][a-z0-9]+)*)(\.([a-z0-9]+(?:[-_][a-z0-9]+)*))*$/; -type RuntimeOverrideTarget = ReturnType[number]; type BosEnv = "development" | "production" | "staging"; type IntegrityVerificationMode = "blocking" | "stale-while-revalidate"; interface ResolveRequestRuntimeOptions { verification?: IntegrityVerificationMode; + bindingResolver?: BindingResolver; } interface CachedRemoteConfig { @@ -55,9 +52,6 @@ export class TenantRuntimeError extends Error { const remoteConfigCache = new Map(); const verifiedUiCache = new Map(); -const unsupportedOverrideWarnings = new Set(); -let tenantWhitelistCache: { raw: string; value: Set } | null = null; -let allowedOverridesCache: { raw: string; value: RuntimeOverrideTarget[] } | null = null; function pruneExpiredCacheEntries( cache: Map, @@ -96,92 +90,6 @@ export function getTenantRuntimeErrorResponse(error: unknown): { status: number; export function clearTenantRuntimeCaches() { remoteConfigCache.clear(); verifiedUiCache.clear(); - unsupportedOverrideWarnings.clear(); - tenantWhitelistCache = null; - allowedOverridesCache = null; -} - -function parseBoolean(value: string | undefined): boolean { - if (!value) return false; - return ["1", "true", "yes", "on"].includes(value.toLowerCase()); -} - -function getTenantWhitelist(): Set { - const raw = process.env.TENANT_WHITELIST ?? ""; - if (tenantWhitelistCache?.raw === raw) { - return tenantWhitelistCache.value; - } - - const value = new Set( - raw - .split(",") - .map((entry) => entry.trim()) - .filter(Boolean), - ); - tenantWhitelistCache = { raw, value }; - return value; -} - -function getAllowedOverrides(): RuntimeOverrideTarget[] { - const raw = process.env.ALLOW_OVERRIDE ?? ""; - if (allowedOverridesCache?.raw === raw) { - return allowedOverridesCache.value; - } - - const value = parseRuntimeOverrideTargets(raw); - allowedOverridesCache = { raw, value }; - return value; -} - -function warnUnsupportedOverrideTargets(targets: ReadonlyArray) { - for (const target of targets) { - if (target === "ui" || target === "plugins" || target.startsWith("plugins.")) { - continue; - } - - if (!unsupportedOverrideWarnings.has(target)) { - unsupportedOverrideWarnings.add(target); - logger.warn( - `[Tenant Runtime] Ignoring unsupported override target "${target}" in fixed-core mode`, - ); - } - } -} - -function resolveTenantAccountId( - hostname: string, - gatewayId: string, - namespaceAccountId: string, -): string | null { - const normalizedHost = hostname.toLowerCase(); - const normalizedGateway = gatewayId.toLowerCase(); - const normalizedNamespaceAccountId = namespaceAccountId.toLowerCase(); - - if ( - normalizedHost === normalizedGateway || - normalizedHost === "localhost" || - normalizedHost === "127.0.0.1" - ) { - return null; - } - - const suffix = `.${normalizedGateway}`; - if (!normalizedHost.endsWith(suffix)) { - return null; - } - - const tenantLabel = normalizedHost.slice(0, -suffix.length); - const tenantSegments = tenantLabel.split(".").filter(Boolean); - if (tenantSegments.length === 0 || tenantSegments.join(".") !== tenantLabel) { - throw new TenantRuntimeError(`Invalid tenant host: ${hostname}`, 404); - } - - const accountId = `${tenantSegments.join(".")}.${normalizedNamespaceAccountId}`; - if (!NEAR_ACCOUNT_ID_REGEX.test(accountId)) { - throw new TenantRuntimeError(`Invalid tenant account: ${accountId}`, 404); - } - - return accountId; } function getRemoteConfigCached(bosUrl: string, env: BosEnv) { @@ -347,16 +255,6 @@ async function verifyPluginUiIntegrity( ); } -function isPluginOverrideAllowed( - allowedOverrides: ReadonlyArray, - pluginKey: string, -): boolean { - return ( - isRuntimeOverrideAllowed(allowedOverrides, "plugins") || - isRuntimeOverrideAllowed(allowedOverrides, `plugins.${pluginKey}`) - ); -} - function buildEffectivePluginConfig( basePlugin: RuntimePlugin, tenantPlugin: RuntimePlugin, @@ -372,10 +270,9 @@ function buildEffectiveRuntimeConfig( baseConfig: RuntimeConfig, tenantConfig: RuntimeConfig, tenantAccountId: string, - allowedOverrides: ReadonlyArray, + allowUiOverrides: boolean, + allowBackendOverrides: boolean, ): RuntimeConfig { - warnUnsupportedOverrideTargets(allowedOverrides); - const effectiveConfig: RuntimeConfig = { ...baseConfig, account: tenantAccountId, @@ -385,7 +282,7 @@ function buildEffectiveRuntimeConfig( repository: tenantConfig.repository, }; - if (isRuntimeOverrideAllowed(allowedOverrides, "ui")) { + if (allowUiOverrides) { effectiveConfig.ui = tenantConfig.ui; } @@ -399,7 +296,7 @@ function buildEffectiveRuntimeConfig( continue; } - if (!isPluginOverrideAllowed(allowedOverrides, pluginKey)) { + if (!allowBackendOverrides) { continue; } @@ -412,29 +309,6 @@ function buildEffectiveRuntimeConfig( return effectiveConfig; } -function matchesTenantPattern(accountId: string, pattern: string): boolean { - if (pattern === accountId) return true; - if (pattern.startsWith("*.") && accountId.endsWith(pattern.slice(1))) return true; - return false; -} - -function isSsrAllowed(accountId: string): boolean { - if (parseBoolean(process.env.ALLOW_UNTRUSTED_SSR)) { - return true; - } - - const whitelist = getTenantWhitelist(); - for (const entry of whitelist) { - if (matchesTenantPattern(accountId, entry)) return true; - } - return false; -} - -function getTenantStatus(remoteConfig: Awaited>): string { - const raw = remoteConfig.rawConfig as { status?: string } | undefined; - return raw?.status ?? "active"; -} - export async function resolveRequestRuntime( baseConfig: RuntimeConfig, request: Request, @@ -443,8 +317,9 @@ export async function resolveRequestRuntime( const verificationMode = options?.verification ?? "blocking"; const url = new URL(request.url); const gatewayId = resolveDomain(baseConfig.domain, baseConfig.host.url); - const tenantAccountId = resolveTenantAccountId(url.hostname, gatewayId, baseConfig.account); - if (!tenantAccountId) { + const bindingResolver = options?.bindingResolver ?? createBindingResolver(baseConfig); + const binding = await bindingResolver.resolve(url.hostname); + if (!binding) { return { config: baseConfig, tenantAccountId: null, @@ -453,6 +328,14 @@ export async function resolveRequestRuntime( }; } + const tenantAccountId = binding.accountId; + if (binding.status === "suspended") { + throw new TenantRuntimeError("Tenant is suspended", 503); + } + if (binding.status === "pending_deletion") { + throw new TenantRuntimeError("Tenant has been deleted", 410); + } + const bosUrl = `bos://${tenantAccountId}/${gatewayId}`; const remoteConfig = await getRemoteConfigCached(bosUrl, "production"); const baseBosUrl = `bos://${baseConfig.account}/${gatewayId}`; @@ -483,17 +366,10 @@ export async function resolveRequestRuntime( baseConfig, tenantRuntimeConfig, tenantAccountId, - getAllowedOverrides(), + binding.allowUiOverrides, + binding.allowBackendOverrides, ); - const tenantStatus = getTenantStatus(remoteConfig); - if (tenantStatus === "suspended") { - throw new TenantRuntimeError("Tenant is suspended", 503); - } - if (tenantStatus === "pending_deletion") { - throw new TenantRuntimeError("Tenant has been deleted", 410); - } - if (effectiveConfig.ui.url !== baseConfig.ui.url) { await verifyUiIntegrity(effectiveConfig, verificationMode); } @@ -512,7 +388,7 @@ export async function resolveRequestRuntime( const ssrAllowed = Boolean(effectiveConfig.ui.ssrUrl) && Boolean(effectiveConfig.ui.ssrIntegrity) && - isSsrAllowed(tenantAccountId); + binding.allowSsr; return { config: ssrAllowed diff --git a/host/tests/integration/tenant-host-nested.test.ts b/host/tests/integration/tenant-host-nested.test.ts index e2891320..ccde7073 100644 --- a/host/tests/integration/tenant-host-nested.test.ts +++ b/host/tests/integration/tenant-host-nested.test.ts @@ -11,19 +11,6 @@ vi.mock("everything-dev/config", async () => { await vi.importActual("everything-dev/config"); return { ...actual, - parseRuntimeOverrideTargets: (value?: string | null) => - value - ? [ - ...new Set( - value - .split(",") - .map((entry) => entry.trim()) - .filter(Boolean), - ), - ] - : [], - isRuntimeOverrideAllowed: (targets: string[], target: string) => - targets.includes(target) || (target.startsWith("plugins.") && targets.includes("plugins.*")), loadRemoteConfig: loadRemoteConfigMock, buildRuntimeConfig: buildRuntimeConfigMock, }; @@ -39,6 +26,29 @@ vi.mock("everything-dev/integrity", async () => { }; }); +vi.mock("../../src/services/binding-resolver", async () => { + const actual = await vi.importActual( + "../../src/services/binding-resolver", + ); + return { + ...actual, + createBindingResolver: () => ({ + resolve: async (hostname: string) => + hostname === "chicago.alice.linktree.com" + ? { + hostname: "chicago.alice.linktree.com", + accountId: "chicago.alice.linktree.near", + allowUiOverrides: true, + allowBackendOverrides: false, + allowSsr: false, + status: "active", + } + : null, + clear: () => {}, + }), + }; +}); + const { runServer } = await import("../../src/program"); function createBaseConfig() { @@ -117,9 +127,6 @@ describe("tenant host nested integration", () => { const previousNodeEnv = process.env.NODE_ENV; const previousHost = process.env.HOST; const previousPort = process.env.PORT; - const previousAllowOverride = process.env.ALLOW_OVERRIDE; - const previousTenantWhitelist = process.env.TENANT_WHITELIST; - const previousAllowUntrustedSsr = process.env.ALLOW_UNTRUSTED_SSR; beforeAll(async () => { assetServer = await startStaticServer({}); @@ -129,9 +136,6 @@ describe("tenant host nested integration", () => { process.env.NODE_ENV = "production"; process.env.HOST = "127.0.0.1"; process.env.PORT = String(port); - process.env.ALLOW_OVERRIDE = "ui,plugins.*"; - process.env.TENANT_WHITELIST = "chicago.alice.linktree.near"; - process.env.ALLOW_UNTRUSTED_SSR = "false"; process.argv.push("--proxy"); const config = createBaseConfig(); @@ -162,9 +166,6 @@ describe("tenant host nested integration", () => { process.env.NODE_ENV = previousNodeEnv; process.env.HOST = previousHost; process.env.PORT = previousPort; - process.env.ALLOW_OVERRIDE = previousAllowOverride; - process.env.TENANT_WHITELIST = previousTenantWhitelist; - process.env.ALLOW_UNTRUSTED_SSR = previousAllowUntrustedSsr; const proxyIdx = process.argv.indexOf("--proxy"); if (proxyIdx !== -1) { diff --git a/host/tests/integration/tenant-runtime.test.ts b/host/tests/integration/tenant-runtime.test.ts index 9927d32e..1cbf21ec 100644 --- a/host/tests/integration/tenant-runtime.test.ts +++ b/host/tests/integration/tenant-runtime.test.ts @@ -7,19 +7,6 @@ const verifySriForUrlMock = vi.fn(); vi.mock("everything-dev/config", async () => { return { - parseRuntimeOverrideTargets: (value?: string | null) => - value - ? [ - ...new Set( - value - .split(",") - .map((entry) => entry.trim()) - .filter(Boolean), - ), - ] - : [], - isRuntimeOverrideAllowed: (targets: string[], target: string) => - targets.includes(target) || (target.startsWith("plugins.") && targets.includes("plugins.*")), loadRemoteConfig: loadRemoteConfigMock, buildRuntimeConfig: buildRuntimeConfigMock, }; @@ -32,6 +19,7 @@ vi.mock("everything-dev/integrity", () => ({ const { clearTenantRuntimeCaches, resolveRequestRuntime } = await import( "../../src/services/tenant-runtime" ); +import type { BindingResolver } from "../../src/services/binding-resolver"; function createDeferred() { let resolve!: (value: T | PromiseLike) => void; @@ -41,6 +29,34 @@ function createDeferred() { return { promise, resolve }; } +function createMockBindingResolver( + ...hostnames: Array<{ + hostname: string; + allowUiOverrides?: boolean; + allowBackendOverrides?: boolean; + allowSsr?: boolean; + status?: "active" | "pending" | "suspended" | "pending_deletion"; + }> +): BindingResolver { + const map = new Map( + hostnames.map((entry) => [ + entry.hostname, + { + hostname: entry.hostname, + accountId: entry.hostname.replace(/\.com$/, ".near"), + allowUiOverrides: entry.allowUiOverrides ?? true, + allowBackendOverrides: entry.allowBackendOverrides ?? false, + allowSsr: entry.allowSsr ?? false, + status: entry.status ?? "active", + }, + ]), + ); + return { + resolve: async (hostname: string) => map.get(hostname) ?? null, + clear: () => {}, + }; +} + function createBaseRuntimeConfig(): RuntimeConfig { return { env: "production", @@ -96,35 +112,27 @@ function createBaseRuntimeConfig(): RuntimeConfig { } describe("resolveRequestRuntime", () => { - const envSnapshot = { ...process.env }; - beforeEach(() => { vi.clearAllMocks(); clearTenantRuntimeCaches(); verifySriForUrlMock.mockResolvedValue(undefined); - process.env = { - ...envSnapshot, - ALLOW_OVERRIDE: "ui", - TENANT_WHITELIST: "alice.linktree.near", - ALLOW_UNTRUSTED_SSR: "false", - }; - }); - - afterEach(() => { - process.env = { ...envSnapshot }; }); it("returns the base runtime on the bare domain", async () => { const baseConfig = createBaseRuntimeConfig(); - const result = await resolveRequestRuntime(baseConfig, new Request("https://linktree.com/")); + const result = await resolveRequestRuntime( + baseConfig, + new Request("https://linktree.com/"), + { bindingResolver: createMockBindingResolver() }, + ); expect(result.config).toBe(baseConfig); expect(result.tenantAccountId).toBeNull(); expect(loadRemoteConfigMock).not.toHaveBeenCalled(); }); - it("derives tenant accounts relative to the active runtime account", async () => { + it("resolves the tenant account for a hostname via the binding resolver", async () => { loadRemoteConfigMock.mockResolvedValue({ source: "bos://alice.linktree.near/linktree.com", rawConfig: { @@ -155,6 +163,12 @@ describe("resolveRequestRuntime", () => { const result = await resolveRequestRuntime( createBaseRuntimeConfig(), new Request("https://alice.linktree.com/"), + { + bindingResolver: createMockBindingResolver({ + hostname: "alice.linktree.com", + allowUiOverrides: true, + }), + }, ); expect(result.tenantAccountId).toBe("alice.linktree.near"); @@ -164,7 +178,7 @@ describe("resolveRequestRuntime", () => { ); }); - it("supports nested tenant labels within the active runtime namespace", async () => { + it("resolves nested tenant hostnames via the binding resolver", async () => { loadRemoteConfigMock.mockResolvedValue({ source: "bos://chicago.alice.linktree.near/linktree.com", rawConfig: { @@ -198,6 +212,12 @@ describe("resolveRequestRuntime", () => { const result = await resolveRequestRuntime( createBaseRuntimeConfig(), new Request("https://chicago.alice.linktree.com/"), + { + bindingResolver: createMockBindingResolver({ + hostname: "chicago.alice.linktree.com", + allowUiOverrides: true, + }), + }, ); expect(result.tenantAccountId).toBe("chicago.alice.linktree.near"); @@ -230,61 +250,37 @@ describe("resolveRequestRuntime", () => { buildRuntimeConfigMock.mockResolvedValue(createBaseRuntimeConfig()); await expect( - resolveRequestRuntime(createBaseRuntimeConfig(), new Request("https://alice.linktree.com/")), + resolveRequestRuntime(createBaseRuntimeConfig(), new Request("https://alice.linktree.com/"), { + bindingResolver: createMockBindingResolver({ hostname: "alice.linktree.com" }), + }), ).rejects.toThrow("must extend bos://linktree.near/linktree.com"); }); - it("rejects a suspended tenant with 503 based on published config status", async () => { - loadRemoteConfigMock.mockResolvedValue({ - source: "bos://alice.linktree.near/linktree.com", - rawConfig: { - extends: "bos://linktree.near/linktree.com", - status: "suspended", - }, - config: { - account: "alice.linktree.near", - app: { - host: { development: "local:host", production: "https://host.example.com" }, - ui: { name: "ui", production: "https://cdn.example.com/alice-ui" }, - api: { name: "api", production: "https://api.example.com" }, - }, - }, - extendsChain: ["bos://alice.linktree.near/linktree.com", "bos://linktree.near/linktree.com"], - }); - - buildRuntimeConfigMock.mockResolvedValue(createBaseRuntimeConfig()); - + it("rejects a suspended tenant with 503 based on the binding status", async () => { await expect( - resolveRequestRuntime(createBaseRuntimeConfig(), new Request("https://alice.linktree.com/")), + resolveRequestRuntime(createBaseRuntimeConfig(), new Request("https://alice.linktree.com/"), { + bindingResolver: createMockBindingResolver({ + hostname: "alice.linktree.com", + status: "suspended", + }), + }), ).rejects.toMatchObject({ status: 503, message: "Tenant is suspended" }); + expect(loadRemoteConfigMock).not.toHaveBeenCalled(); }); - it("rejects a pending_deletion tenant with 410 based on published config status", async () => { - loadRemoteConfigMock.mockResolvedValue({ - source: "bos://alice.linktree.near/linktree.com", - rawConfig: { - extends: "bos://linktree.near/linktree.com", - status: "pending_deletion", - }, - config: { - account: "alice.linktree.near", - app: { - host: { development: "local:host", production: "https://host.example.com" }, - ui: { name: "ui", production: "https://cdn.example.com/alice-ui" }, - api: { name: "api", production: "https://api.example.com" }, - }, - }, - extendsChain: ["bos://alice.linktree.near/linktree.com", "bos://linktree.near/linktree.com"], - }); - - buildRuntimeConfigMock.mockResolvedValue(createBaseRuntimeConfig()); - + it("rejects a pending_deletion tenant with 410 based on the binding status", async () => { await expect( - resolveRequestRuntime(createBaseRuntimeConfig(), new Request("https://alice.linktree.com/")), + resolveRequestRuntime(createBaseRuntimeConfig(), new Request("https://alice.linktree.com/"), { + bindingResolver: createMockBindingResolver({ + hostname: "alice.linktree.com", + status: "pending_deletion", + }), + }), ).rejects.toMatchObject({ status: 410, message: "Tenant has been deleted" }); + expect(loadRemoteConfigMock).not.toHaveBeenCalled(); }); - it("applies a tenant UI override and allows SSR for whitelisted tenants", async () => { + it("applies a tenant UI override and allows SSR when the binding enables both", async () => { const baseConfig = createBaseRuntimeConfig(); loadRemoteConfigMock.mockResolvedValue({ @@ -325,6 +321,13 @@ describe("resolveRequestRuntime", () => { const result = await resolveRequestRuntime( baseConfig, new Request("https://alice.linktree.com/"), + { + bindingResolver: createMockBindingResolver({ + hostname: "alice.linktree.com", + allowUiOverrides: true, + allowSsr: true, + }), + }, ); expect(result.tenantAccountId).toBe("alice.linktree.near"); @@ -338,7 +341,7 @@ describe("resolveRequestRuntime", () => { ); }); - it("disables SSR for non-whitelisted tenants when untrusted SSR is off", async () => { + it("disables SSR when the binding does not allow it", async () => { const baseConfig = createBaseRuntimeConfig(); loadRemoteConfigMock.mockResolvedValue({ @@ -373,6 +376,13 @@ describe("resolveRequestRuntime", () => { const result = await resolveRequestRuntime( baseConfig, new Request("https://bob.linktree.com/"), + { + bindingResolver: createMockBindingResolver({ + hostname: "bob.linktree.com", + allowUiOverrides: true, + allowSsr: false, + }), + }, ); expect(result.ssrAllowed).toBe(false); @@ -380,7 +390,7 @@ describe("resolveRequestRuntime", () => { expect(result.config.ui.ssrIntegrity).toBeUndefined(); }); - it("disables SSR for whitelisted tenants when ssrIntegrity is missing", async () => { + it("disables SSR for SSR-enabled bindings when ssrIntegrity is missing", async () => { const baseConfig = createBaseRuntimeConfig(); loadRemoteConfigMock.mockResolvedValue({ @@ -419,6 +429,13 @@ describe("resolveRequestRuntime", () => { const result = await resolveRequestRuntime( baseConfig, new Request("https://alice.linktree.com/"), + { + bindingResolver: createMockBindingResolver({ + hostname: "alice.linktree.com", + allowUiOverrides: true, + allowSsr: true, + }), + }, ); expect(result.ssrAllowed).toBe(false); @@ -426,7 +443,7 @@ describe("resolveRequestRuntime", () => { expect(result.config.ui.ssrIntegrity).toBeUndefined(); }); - it("disables SSR for whitelisted tenants when ssrUrl is missing", async () => { + it("disables SSR for SSR-enabled bindings when ssrUrl is missing", async () => { const baseConfig = createBaseRuntimeConfig(); loadRemoteConfigMock.mockResolvedValue({ @@ -460,6 +477,13 @@ describe("resolveRequestRuntime", () => { const result = await resolveRequestRuntime( baseConfig, new Request("https://alice.linktree.com/"), + { + bindingResolver: createMockBindingResolver({ + hostname: "alice.linktree.com", + allowUiOverrides: true, + allowSsr: true, + }), + }, ); expect(result.ssrAllowed).toBe(false); @@ -467,7 +491,7 @@ describe("resolveRequestRuntime", () => { expect(result.config.ui.ssrIntegrity).toBeUndefined(); }); - it("allows SSR for whitelisted tenants when both ssrUrl and ssrIntegrity are present", async () => { + it("allows SSR when the binding enables SSR and both ssrUrl and ssrIntegrity are present", async () => { const baseConfig = createBaseRuntimeConfig(); loadRemoteConfigMock.mockResolvedValue({ @@ -508,6 +532,13 @@ describe("resolveRequestRuntime", () => { const result = await resolveRequestRuntime( baseConfig, new Request("https://alice.linktree.com/"), + { + bindingResolver: createMockBindingResolver({ + hostname: "alice.linktree.com", + allowUiOverrides: true, + allowSsr: true, + }), + }, ); expect(result.ssrAllowed).toBe(true); @@ -515,9 +546,8 @@ describe("resolveRequestRuntime", () => { expect(result.config.ui.ssrIntegrity).toBe("sha384-alice-ssr"); }); - it("applies existing plugin UI overrides when plugins are allowed", async () => { + it("applies existing plugin UI overrides when backend overrides are allowed", async () => { const baseConfig = createBaseRuntimeConfig(); - process.env.ALLOW_OVERRIDE = "ui,plugins.*"; loadRemoteConfigMock.mockResolvedValue({ source: "bos://alice.linktree.near/linktree.com", @@ -591,6 +621,14 @@ describe("resolveRequestRuntime", () => { const result = await resolveRequestRuntime( baseConfig, new Request("https://alice.linktree.com/"), + { + bindingResolver: createMockBindingResolver({ + hostname: "alice.linktree.com", + allowUiOverrides: true, + allowSsr: true, + allowBackendOverrides: true, + }), + }, ); expect(result.config.plugins?.apps.ui?.url).toBe("https://plugins.example.com/alice-apps-ui"); @@ -639,7 +677,17 @@ describe("resolveRequestRuntime", () => { }, }); - await resolveRequestRuntime(baseConfig, new Request("https://alice.linktree.com/")); + await resolveRequestRuntime( + baseConfig, + new Request("https://alice.linktree.com/"), + { + bindingResolver: createMockBindingResolver({ + hostname: "alice.linktree.com", + allowUiOverrides: true, + allowSsr: true, + }), + }, + ); const refresh = createDeferred(); verifySriForUrlMock.mockImplementationOnce(() => refresh.promise); @@ -648,6 +696,11 @@ describe("resolveRequestRuntime", () => { await expect( resolveRequestRuntime(baseConfig, new Request("https://alice.linktree.com/asset.js"), { verification: "stale-while-revalidate", + bindingResolver: createMockBindingResolver({ + hostname: "alice.linktree.com", + allowUiOverrides: true, + allowSsr: true, + }), }), ).resolves.toMatchObject({ tenantAccountId: "alice.linktree.near" }); expect(verifySriForUrlMock).toHaveBeenCalledTimes(2); @@ -655,6 +708,11 @@ describe("resolveRequestRuntime", () => { await expect( resolveRequestRuntime(baseConfig, new Request("https://alice.linktree.com/asset-2.js"), { verification: "stale-while-revalidate", + bindingResolver: createMockBindingResolver({ + hostname: "alice.linktree.com", + allowUiOverrides: true, + allowSsr: true, + }), }), ).resolves.toMatchObject({ tenantAccountId: "alice.linktree.near" }); expect(verifySriForUrlMock).toHaveBeenCalledTimes(2); @@ -704,7 +762,17 @@ describe("resolveRequestRuntime", () => { }, }); - await resolveRequestRuntime(baseConfig, new Request("https://alice.linktree.com/")); + await resolveRequestRuntime( + baseConfig, + new Request("https://alice.linktree.com/"), + { + bindingResolver: createMockBindingResolver({ + hostname: "alice.linktree.com", + allowUiOverrides: true, + allowSsr: true, + }), + }, + ); const refresh = createDeferred(); verifySriForUrlMock.mockImplementationOnce(() => refresh.promise); @@ -716,6 +784,11 @@ describe("resolveRequestRuntime", () => { new Request("https://alice.linktree.com/"), { verification: "blocking", + bindingResolver: createMockBindingResolver({ + hostname: "alice.linktree.com", + allowUiOverrides: true, + allowSsr: true, + }), }, ).then(() => { settled = true; @@ -732,50 +805,109 @@ describe("resolveRequestRuntime", () => { } }); - it("recomputes the tenant whitelist when the env value changes", async () => { + it("gates SSR per tenant based on the binding allowSsr flag", async () => { const baseConfig = createBaseRuntimeConfig(); loadRemoteConfigMock.mockResolvedValue({ - source: "bos://bob.linktree.near/linktree.com", + source: "bos://alice.linktree.near/linktree.com", rawConfig: { extends: "bos://linktree.near/linktree.com", }, config: { - account: "bob.linktree.near", + account: "alice.linktree.near", app: { host: { development: "local:host", production: "https://host.example.com" }, - ui: { name: "ui", production: "https://cdn.example.com/bob-ui" }, + ui: { name: "ui", production: "https://cdn.example.com/alice-ui" }, api: { name: "api", production: "https://api.example.com" }, }, }, - extendsChain: ["bos://bob.linktree.near/linktree.com", "bos://linktree.near/linktree.com"], + extendsChain: ["bos://alice.linktree.near/linktree.com", "bos://linktree.near/linktree.com"], }); buildRuntimeConfigMock.mockResolvedValue({ ...baseConfig, - account: "bob.linktree.near", + account: "alice.linktree.near", ui: { ...baseConfig.ui, - url: "https://cdn.example.com/bob-ui", - entry: "https://cdn.example.com/bob-ui/mf-manifest.json", - integrity: "sha384-bob", - ssrUrl: "https://cdn.example.com/bob-ui-ssr", - ssrIntegrity: "sha384-bob-ssr", + url: "https://cdn.example.com/alice-ui", + entry: "https://cdn.example.com/alice-ui/mf-manifest.json", + integrity: "sha384-alice", + ssrUrl: "https://cdn.example.com/alice-ui-ssr", + ssrIntegrity: "sha384-alice-ssr", }, }); const blocked = await resolveRequestRuntime( baseConfig, - new Request("https://bob.linktree.com/"), + new Request("https://alice.linktree.com/"), + { + bindingResolver: createMockBindingResolver({ + hostname: "alice.linktree.com", + allowUiOverrides: true, + allowSsr: false, + }), + }, ); expect(blocked.ssrAllowed).toBe(false); - - process.env.TENANT_WHITELIST = "bob.linktree.near"; + expect(blocked.config.ui.ssrUrl).toBeUndefined(); const allowed = await resolveRequestRuntime( baseConfig, - new Request("https://bob.linktree.com/"), + new Request("https://alice.linktree.com/"), + { + bindingResolver: createMockBindingResolver({ + hostname: "alice.linktree.com", + allowUiOverrides: true, + allowSsr: true, + }), + }, ); expect(allowed.ssrAllowed).toBe(true); + expect(allowed.config.ui.ssrUrl).toBe("https://cdn.example.com/alice-ui-ssr"); + }); + + it("does not apply tenant UI overrides when the binding disallows them", async () => { + const baseConfig = createBaseRuntimeConfig(); + + loadRemoteConfigMock.mockResolvedValue({ + source: "bos://alice.linktree.near/linktree.com", + rawConfig: { + extends: "bos://linktree.near/linktree.com", + }, + config: { + account: "alice.linktree.near", + app: { + host: { development: "local:host", production: "https://host.example.com" }, + ui: { name: "ui", production: "https://cdn.example.com/alice-ui" }, + api: { name: "api", production: "https://api.example.com" }, + }, + }, + extendsChain: ["bos://alice.linktree.near/linktree.com", "bos://linktree.near/linktree.com"], + }); + + buildRuntimeConfigMock.mockResolvedValue({ + ...baseConfig, + account: "alice.linktree.near", + ui: { + ...baseConfig.ui, + url: "https://cdn.example.com/alice-ui", + entry: "https://cdn.example.com/alice-ui/mf-manifest.json", + integrity: "sha384-alice", + }, + }); + + const result = await resolveRequestRuntime( + baseConfig, + new Request("https://alice.linktree.com/"), + { + bindingResolver: createMockBindingResolver({ + hostname: "alice.linktree.com", + allowUiOverrides: false, + }), + }, + ); + + expect(result.config.ui.url).toBe(baseConfig.ui.url); + expect(verifySriForUrlMock).not.toHaveBeenCalled(); }); }); diff --git a/tests/regression/http/tenant_bindings_test.go b/tests/regression/http/tenant_bindings_test.go new file mode 100644 index 00000000..7ca4c801 --- /dev/null +++ b/tests/regression/http/tenant_bindings_test.go @@ -0,0 +1,184 @@ +package regression + +import ( + "encoding/json" + "fmt" + "os" + "strings" + "testing" + + "everything.dev/regression/http/internal/regtest" +) + +func TestTenantBindingsPublicEndpoint(t *testing.T) { + client := regtest.NewCookieClient() + + t.Run("bindings_return_200_without_auth", func(t *testing.T) { + status, _, body := regtest.GetRaw(t, client, baseURL+"/api/tenants/bindings") + regtest.MustStatus(t, status, 200, body) + }) + + var bindings []struct { + Hostname string `json:"hostname"` + AccountID string `json:"accountId"` + AllowUiOverrides bool `json:"allowUiOverrides"` + AllowBackendOverrides bool `json:"allowBackendOverrides"` + AllowSsr bool `json:"allowSsr"` + Status string `json:"status"` + } + t.Run("bindings_decode_as_array", func(t *testing.T) { + status, _, body := regtest.GetRaw(t, client, baseURL+"/api/tenants/bindings") + regtest.MustStatus(t, status, 200, body) + if err := json.Unmarshal([]byte(body), &bindings); err != nil { + t.Fatalf("decoding bindings response: %v\nBody: %s", err, body) + } + }) + + t.Run("seeded_tenant_binding_present", func(t *testing.T) { + var seeded *struct { + Hostname string `json:"hostname"` + AccountID string `json:"accountId"` + AllowUiOverrides bool `json:"allowUiOverrides"` + AllowBackendOverrides bool `json:"allowBackendOverrides"` + AllowSsr bool `json:"allowSsr"` + Status string `json:"status"` + } + for i := range bindings { + if strings.HasPrefix(bindings[i].Hostname, "regression-tenant-") { + seeded = &bindings[i] + break + } + } + if seeded == nil { + t.Fatal("expected seeded tenant binding to be present") + } + if seeded.Status != "active" { + t.Fatalf("expected seeded tenant status 'active', got %q", seeded.Status) + } + if seeded.AccountID != fmt.Sprintf("%s.testnet", seeded.Hostname) { + t.Fatalf("expected account id derived from hostname, got %q", seeded.AccountID) + } + + // The seeded tenant uses the default permission columns. + if !seeded.AllowUiOverrides { + t.Fatal("expected allowUiOverrides to default to true") + } + if seeded.AllowBackendOverrides { + t.Fatal("expected allowBackendOverrides to default to false") + } + if seeded.AllowSsr { + t.Fatal("expected allowSsr to default to false") + } + }) +} + +func TestTenantBindingsReflectAllowFlags(t *testing.T) { + client := regtest.NewCookieClient() + + t.Run("sign_in", func(t *testing.T) { + status, _, body := regtest.PostEmpty(t, client, baseURL+"/api/auth/sign-in/anonymous") + regtest.MustStatus(t, status, 200, body) + }) + + orgName := fmt.Sprintf("regression-flags-org-%d", os.Getpid()) + t.Run("create_org", func(t *testing.T) { + status, _, body := regtest.PostJSON(t, client, baseURL+"/api/auth/organization/create", map[string]string{ + "name": orgName, + "slug": orgName, + }, map[string]string{ + "Origin": "http://localhost:4100", + }) + regtest.MustStatus(t, status, 200, body) + var result struct { + ID string `json:"id"` + } + if err := json.Unmarshal([]byte(body), &result); err != nil { + t.Fatalf("decoding org response: %v\nBody: %s", err, body) + } + if result.ID == "" { + t.Fatal("expected non-empty org id") + } + + status, _, body = regtest.PostJSON(t, client, baseURL+"/api/auth/organization/set-active", map[string]string{ + "organizationId": result.ID, + }, map[string]string{ + "Origin": "http://localhost:4100", + }) + regtest.MustStatus(t, status, 200, body) + }) + + subdomain := fmt.Sprintf("regression-flags-%d", os.Getpid()) + t.Run("create_tenant_with_explicit_flags", func(t *testing.T) { + status, _, body := regtest.PostJSON(t, client, baseURL+"/api/tenants", map[string]any{ + "subdomain": subdomain, + "name": "Flags Tenant", + "accountId": fmt.Sprintf("%s.testnet", subdomain), + "allowUiOverrides": false, + "allowBackendOverrides": true, + "allowSsr": true, + }, nil) + regtest.MustStatus(t, status, 200, body) + + var result struct { + ID string `json:"id"` + AllowUiOverrides bool `json:"allowUiOverrides"` + AllowBackendOverrides bool `json:"allowBackendOverrides"` + AllowSsr bool `json:"allowSsr"` + } + if err := json.Unmarshal([]byte(body), &result); err != nil { + t.Fatalf("decoding tenant response: %v\nBody: %s", err, body) + } + if result.ID == "" { + t.Fatal("expected non-empty tenant id") + } + if result.AllowUiOverrides { + t.Fatal("expected allowUiOverrides to be false") + } + if !result.AllowBackendOverrides { + t.Fatal("expected allowBackendOverrides to be true") + } + if !result.AllowSsr { + t.Fatal("expected allowSsr to be true") + } + }) + + t.Run("bindings_reflect_flags", func(t *testing.T) { + status, _, body := regtest.GetRaw(t, client, baseURL+"/api/tenants/bindings") + regtest.MustStatus(t, status, 200, body) + + var bindings []struct { + Hostname string `json:"hostname"` + AllowUiOverrides bool `json:"allowUiOverrides"` + AllowBackendOverrides bool `json:"allowBackendOverrides"` + AllowSsr bool `json:"allowSsr"` + } + if err := json.Unmarshal([]byte(body), &bindings); err != nil { + t.Fatalf("decoding bindings response: %v\nBody: %s", err, body) + } + + var found *struct { + Hostname string `json:"hostname"` + AllowUiOverrides bool `json:"allowUiOverrides"` + AllowBackendOverrides bool `json:"allowBackendOverrides"` + AllowSsr bool `json:"allowSsr"` + } + for i := range bindings { + if bindings[i].Hostname == subdomain { + found = &bindings[i] + break + } + } + if found == nil { + t.Fatal("expected created tenant to appear in bindings") + } + if found.AllowUiOverrides { + t.Fatal("expected binding allowUiOverrides to be false") + } + if !found.AllowBackendOverrides { + t.Fatal("expected binding allowBackendOverrides to be true") + } + if !found.AllowSsr { + t.Fatal("expected binding allowSsr to be true") + } + }) +} diff --git a/ui/src/routes/_layout.tsx b/ui/src/routes/_layout.tsx index db4a8ec0..c0a44b0a 100644 --- a/ui/src/routes/_layout.tsx +++ b/ui/src/routes/_layout.tsx @@ -1,97 +1,14 @@ -import { useQuery } from "@tanstack/react-query"; -import { createFileRoute, Link, Outlet, useRouterState } from "@tanstack/react-router"; -import { ClipboardList, Compass, Globe, Home, Menu, Shield, X } from "lucide-react"; +import { createFileRoute, Outlet, useRouterState } from "@tanstack/react-router"; +import { X } from "lucide-react"; import { useEffect, useState } from "react"; -import { - getAccount, - getActiveRuntime, - getAppName, - sessionQueryOptions, - useAuthClient, -} from "@/app"; -import builtOn from "@/assets/built_on.png"; -import builtOnRev from "@/assets/built_on_rev.png"; -import { ThemeToggle } from "@/components/theme-toggle"; -import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; -import { UserNav } from "@/components/user-nav"; -import { cn } from "@/lib/utils"; - -type SidebarRole = "anon" | "member" | "admin"; - -interface SidebarItem { - icon: React.ComponentType<{ className?: string }>; - label: string; - to: string; - roleRequired: SidebarRole; -} - -function filterSidebarByRole(items: SidebarItem[], userRole: SidebarRole): SidebarItem[] { - return items.filter((item) => { - if (item.roleRequired === "anon") return true; - if (item.roleRequired === "member" && userRole !== "anon") return true; - if (item.roleRequired === "admin" && userRole === "admin") return true; - return false; - }); -} - -function getUserRole(isAuthenticated: boolean, isAdmin: boolean): SidebarRole { - if (isAdmin) return "admin"; - if (isAuthenticated) return "member"; - return "anon"; -} +import { TooltipProvider } from "@/components/ui/tooltip"; export const Route = createFileRoute("/_layout")({ - beforeLoad: async ({ context }) => { - const { queryClient, authClient, apiClient } = context; - const session = await queryClient.ensureQueryData( - sessionQueryOptions(authClient, context.session), - ); - - const accountId = getAccount(context.runtimeConfig); - const tenant = await apiClient.resolveTenant({ accountId }); - - return { - runtimeConfig: context.runtimeConfig, - session, - tenant, - }; - }, component: Layout, }); function Layout() { - const pathname = useRouterState({ select: (s) => s.location.pathname }); const isNavigating = useRouterState({ select: (s) => s.status === "pending" }); - const { runtimeConfig, session, tenant } = Route.useRouteContext(); - const appName = getAppName(runtimeConfig); - const runtime = getActiveRuntime(runtimeConfig); - const account = getAccount(runtimeConfig); - const auth = useAuthClient(); - const isAuthenticated = !!session?.user; - const userRole = getUserRole(isAuthenticated, session?.user?.role === "admin"); - - const { data: liveSession } = useQuery({ - ...sessionQueryOptions(auth, session), - initialData: session, - }); - const activeOrgId = liveSession?.session?.activeOrganizationId ?? null; - const liveIsTenantMember = !!tenant && !!activeOrgId && activeOrgId === tenant.orgId; - - const hideSidebar = pathname === "/login"; - - const sidebarItems: SidebarItem[] = [ - { icon: Home, label: "home", to: "/home", roleRequired: "anon" }, - { icon: Globe, label: "apps", to: "/apps", roleRequired: "anon" }, - ...(liveIsTenantMember - ? ([{ icon: Shield, label: "admin", to: "/admin", roleRequired: "member" }] as SidebarItem[]) - : []), - ]; - const visibleItems = filterSidebarByRole(sidebarItems, userRole); - - const isActive = (item: SidebarItem) => { - return pathname === item.to || (item.to !== "/" && pathname.startsWith(`${item.to}/`)); - }; const [betaBannerDismissed, setBetaBannerDismissed] = useState(() => { if (typeof window === "undefined") return false; @@ -134,297 +51,8 @@ function Layout() { )} -
-
- {isAuthenticated ? ( -
- - - {appName} - - - - -
- {tenant && ( - <> - {tenant.name} - / - - )} - {runtime?.accountId ?? account} - / - - {pathname === "/" ? "home" : pathname.slice(1).split("/").join(" / ")} - -
-
- ) : ( - - - {appName} - - - - )} - -
- -
-
-
- {hideSidebar && } - -
- - -
-
- -
-
-
- -
- -
- - {!isAuthenticated && !hideSidebar && ( -
- -
- )} - -
- -
+ ); } - -function MinimalHeader() { - return ( -
- -
- ); -} - -function NearBranding() { - return ( - - Built on NEAR - Built on NEAR - - ); -} - -function MobileTabBar({ - isAuthenticated, - visibleItems, - isActive, -}: { - isAuthenticated: boolean; - visibleItems: SidebarItem[]; - isActive: (item: SidebarItem) => boolean; -}) { - const [drawerOpen, setDrawerOpen] = useState(false); - const pathname = useRouterState({ select: (s) => s.location.pathname }); - const tabActive = (to: string) => (to === "/" ? pathname === "/" : pathname.startsWith(to)); - - return ( - - ); -} - -function TabItem({ - to, - icon: Icon, - label, - active, -}: { - to: string; - icon: React.ComponentType<{ className?: string }>; - label: string; - active: boolean; -}) { - return ( - - - {label} - - ); -} diff --git a/ui/src/routes/_layout/_authenticated.tsx b/ui/src/routes/_layout/_authenticated.tsx index f6c8d6de..869f23e3 100644 --- a/ui/src/routes/_layout/_authenticated.tsx +++ b/ui/src/routes/_layout/_authenticated.tsx @@ -1,5 +1,14 @@ -import { createFileRoute, Outlet, redirect } from "@tanstack/react-router"; -import { type SessionData, sessionQueryOptions } from "@/app"; +import { createFileRoute, Link, Outlet, redirect, useRouterState } from "@tanstack/react-router"; +import { ClipboardList, Compass, Globe, Home, Menu, MessageSquare, Shield } from "lucide-react"; +import { useState } from "react"; +import type { SessionData } from "@/app"; +import { getAccount, getActiveRuntime, getAppName, sessionQueryOptions } from "@/app"; +import { NearBranding } from "@/components/near-branding"; +import { ThemeToggle } from "@/components/theme-toggle"; +import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; +import { UserNav } from "@/components/user-nav"; +import { cn } from "@/lib/utils"; interface AuthContext { isAuthenticated: boolean; @@ -11,6 +20,30 @@ interface AuthContext { isBanned: boolean; } +type SidebarRole = "anon" | "member" | "admin"; + +interface SidebarItem { + icon: React.ComponentType<{ className?: string }>; + label: string; + to: string; + roleRequired: SidebarRole; +} + +function filterSidebarByRole(items: SidebarItem[], userRole: SidebarRole): SidebarItem[] { + return items.filter((item) => { + if (item.roleRequired === "anon") return true; + if (item.roleRequired === "member" && userRole !== "anon") return true; + if (item.roleRequired === "admin" && userRole === "admin") return true; + return false; + }); +} + +function getUserRole(isAuthenticated: boolean, isAdmin: boolean): SidebarRole { + if (isAdmin) return "admin"; + if (isAuthenticated) return "member"; + return "anon"; +} + export const Route = createFileRoute("/_layout/_authenticated")({ beforeLoad: async ({ context, location }) => { const { queryClient, authClient } = context; @@ -53,9 +86,219 @@ export const Route = createFileRoute("/_layout/_authenticated")({ }); function AuthenticatedLayout() { + const pathname = useRouterState({ select: (s) => s.location.pathname }); + const { runtimeConfig, session } = Route.useRouteContext(); + const appName = getAppName(runtimeConfig); + const runtime = getActiveRuntime(runtimeConfig); + const account = getAccount(runtimeConfig); + const isAdmin = session?.user?.role === "admin"; + + const sidebarItems: SidebarItem[] = [ + { icon: Home, label: "home", to: "/home", roleRequired: "anon" }, + { icon: Shield, label: "admin", to: "/admin", roleRequired: "admin" }, + { icon: MessageSquare, label: "nostr", to: "/nostr", roleRequired: "admin" }, + ]; + const visibleItems = filterSidebarByRole(sidebarItems, getUserRole(true, isAdmin)); + + const isActive = (item: SidebarItem) => { + return pathname === item.to || (item.to !== "/" && pathname.startsWith(`${item.to}/`)); + }; + return ( -
- +
+ + +
+
+
+
+ + + {appName} + + + + +
+ {runtime?.accountId ?? account} + / + + {pathname === "/" ? "home" : pathname.slice(1).split("/").join(" / ")} + +
+
+ +
+ +
+
+
+ +
+
+ +
+
+
+ +
); } + +function MobileTabBar({ + visibleItems, + isActive, +}: { + visibleItems: SidebarItem[]; + isActive: (item: SidebarItem) => boolean; +}) { + const [drawerOpen, setDrawerOpen] = useState(false); + const pathname = useRouterState({ select: (s) => s.location.pathname }); + const tabActive = (to: string) => (to === "/" ? pathname === "/" : pathname.startsWith(to)); + + return ( + + ); +} + +function TabItem({ + to, + icon: Icon, + label, + active, +}: { + to: string; + icon: React.ComponentType<{ className?: string }>; + label: string; + active: boolean; +}) { + return ( + + + {label} + + ); +} diff --git a/ui/src/routes/_layout/_authenticated/admin/index.tsx b/ui/src/routes/_layout/_authenticated/admin/index.tsx new file mode 100644 index 00000000..65c3c2d3 --- /dev/null +++ b/ui/src/routes/_layout/_authenticated/admin/index.tsx @@ -0,0 +1,104 @@ +import { createFileRoute, Link } from "@tanstack/react-router"; +import { Building2, Settings, Users } from "lucide-react"; +import { getAccount } from "@/app"; +import { Button, Card } from "@/components"; +import { PageContainer } from "@/components/layout/page-container"; + +export const Route = createFileRoute("/_layout/_authenticated/admin/")({ + head: () => ({ + meta: [{ title: "Admin Dashboard | app" }], + }), + component: AdminDashboard, +}); + +function AdminDashboard() { + const { auth } = Route.useRouteContext(); + const account = getAccount(); + const user = auth?.user ?? null; + + return ( + +
+
+
+
+

+ Dashboard +

+

+ Signed in as {account} +

+
+
+
+ +
+ + + + +
+ +
+

Manage

+
+ +
+ +
+

Organizations

+

+ Manage organizations, members, roles, and invitations. +

+ +
+ + +
+ +
+

Settings

+

+ Update your profile, auth methods, and security preferences. +

+ +
+
+
+
+
+ ); +} + +function StatCard({ + label, + value, + mono, +}: { + label: string; + value: React.ReactNode; + mono?: boolean; +}) { + return ( +
+
+ {label} +
+
+ {value} +
+
+ ); +} diff --git a/ui/src/routes/_layout/_authenticated/admin/system.tsx b/ui/src/routes/_layout/_authenticated/admin/system.tsx new file mode 100644 index 00000000..a98296af --- /dev/null +++ b/ui/src/routes/_layout/_authenticated/admin/system.tsx @@ -0,0 +1,73 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { getAccount, getActiveRuntime, getAppName, getRepository } from "@/app"; +import { Card } from "@/components"; +import { PageContainer } from "@/components/layout/page-container"; +import { InfoRow } from "@/components/ui/info-row"; + +export const Route = createFileRoute("/_layout/_authenticated/admin/system")({ + loader: async ({ context }) => ({ + runtimeConfig: context.runtimeConfig, + }), + head: () => ({ + meta: [{ title: "Admin System | app" }], + }), + component: AdminSystem, +}); + +function AdminSystem() { + const { runtimeConfig } = Route.useLoaderData(); + const account = getAccount(runtimeConfig); + const appName = getAppName(runtimeConfig); + const repository = getRepository(runtimeConfig); + const runtime = getActiveRuntime(runtimeConfig); + + const env = runtimeConfig?.env; + const networkId = runtimeConfig?.networkId; + const hostUrl = runtimeConfig?.hostUrl; + const apiBase = runtimeConfig?.apiBase; + const rpcBase = runtimeConfig?.rpcBase; + const assetsUrl = runtimeConfig?.assetsUrl; + const runtimeBasePath = runtime?.runtimeBasePath; + + return ( + +
+
+
+

+ System +

+

+ Runtime configuration for this deployment. +

+
+
+ +
+ +

Runtime

+ + + + +
+ + +

Deployment

+ + + + +
+ + +

Endpoints

+ + + +
+
+
+
+ ); +} diff --git a/ui/src/routes/_layout/_public.tsx b/ui/src/routes/_layout/_public.tsx new file mode 100644 index 00000000..1fd9a871 --- /dev/null +++ b/ui/src/routes/_layout/_public.tsx @@ -0,0 +1,53 @@ +import { createFileRoute, Link, Outlet, useRouterState } from "@tanstack/react-router"; +import { getAppName } from "@/app"; +import { NearBranding } from "@/components/near-branding"; +import { ThemeToggle } from "@/components/theme-toggle"; +import { UserNav } from "@/components/user-nav"; + +export const Route = createFileRoute("/_layout/_public")({ + component: PublicLayout, +}); + +function PublicLayout() { + const { runtimeConfig } = Route.useRouteContext(); + const appName = getAppName(runtimeConfig); + const pathname = useRouterState({ select: (s) => s.location.pathname }); + + return ( +
+
+
+ + + {appName} + + + + +
+ + +
+
+
+ +
+
+ +
+
+ +
+
+
+ ); +} diff --git a/ui/src/routes/_layout/about.tsx b/ui/src/routes/_layout/_public/about.tsx similarity index 99% rename from ui/src/routes/_layout/about.tsx rename to ui/src/routes/_layout/_public/about.tsx index ea4d4b4e..b960d03e 100644 --- a/ui/src/routes/_layout/about.tsx +++ b/ui/src/routes/_layout/_public/about.tsx @@ -41,7 +41,7 @@ async function fetchRepositoryReadme(repositoryUrl: string): Promise { const repository = getRepository(context.runtimeConfig); const description = diff --git a/ui/src/routes/_layout/_public/index.tsx b/ui/src/routes/_layout/_public/index.tsx new file mode 100644 index 00000000..8cbb4f06 --- /dev/null +++ b/ui/src/routes/_layout/_public/index.tsx @@ -0,0 +1,111 @@ +import { createFileRoute, Link } from "@tanstack/react-router"; +import { ArrowRight, Building2, FileCode2, Lock, Sparkles } from "lucide-react"; +import { getAccount, getActiveRuntime, getAppName, getRepository } from "@/app"; +import { Button, Card, PageContainer } from "@/components"; + +export const Route = createFileRoute("/_layout/_public/")({ + loader: async ({ context }) => ({ + runtimeConfig: context.runtimeConfig, + }), + head: () => ({ + meta: [ + { title: "Welcome | app" }, + { + name: "description", + content: "A modern starter app built with TanStack Router and Better Auth on NEAR.", + }, + ], + }), + component: LandingPage, +}); + +function LandingPage() { + const { runtimeConfig } = Route.useLoaderData(); + const appName = getAppName(runtimeConfig); + const account = getAccount(runtimeConfig); + const runtime = getActiveRuntime(runtimeConfig); + const repository = getRepository(runtimeConfig); + + const accountId = runtime?.accountId ?? account; + + return ( + +
+
+
+ + {accountId} +
+ +
+

+ {appName} +

+

+ A production-ready starter built on TanStack Router, Better Auth, and Effect — with + organization management, a guarded dashboard, and a fully typed API. +

+
+ +
+ + +
+
+ +
+ +
+ +
+

Secure authentication

+

+ NEAR wallet sign-in, email, and passkeys via Better Auth — with an authenticated + layout guard and session-aware routing. +

+
+ + +
+ +
+

Organizations

+

+ Create and manage organizations with members, roles, invitations, and API keys — all + backed by typed oRPC endpoints. +

+
+ + +
+ +
+

Typed end to end

+

+ One contract drives the API and the client — schemas, validation, and types stay in + sync across the whole stack. +

+
+
+ + {repository && ( +
+

Fork the template and make it yours.

+ +
+ )} +
+
+ ); +} diff --git a/ui/src/routes/_layout/login.tsx b/ui/src/routes/_layout/_public/login.tsx similarity index 86% rename from ui/src/routes/_layout/login.tsx rename to ui/src/routes/_layout/_public/login.tsx index 96284716..fdc771d9 100644 --- a/ui/src/routes/_layout/login.tsx +++ b/ui/src/routes/_layout/_public/login.tsx @@ -3,18 +3,15 @@ import { createFileRoute, Navigate, redirect, useNavigate } from "@tanstack/reac import { useEffect, useState } from "react"; import { toast } from "sonner"; import { getAppName, sessionQueryOptions, useAuthClient } from "@/app"; -import builtOn from "@/assets/built_on.png"; -import builtOnRev from "@/assets/built_on_rev.png"; import { BrandElement } from "@/components/brand-element"; import { Button } from "@/components/ui/button"; -import { NetworkToggle } from "@/components/ui/network-toggle"; import { UnderConstruction } from "@/components/under-construction"; type SearchParams = { redirect?: string; }; -export const Route = createFileRoute("/_layout/login")({ +export const Route = createFileRoute("/_layout/_public/login")({ ssr: false, validateSearch: (search: Record): SearchParams => ({ redirect: typeof search.redirect === "string" ? search.redirect : undefined, @@ -120,8 +117,7 @@ function LoginPage() { const isPending = nearPending || anonPending; return ( -
- +
@@ -142,7 +138,25 @@ function LoginPage() {
- -
); } diff --git a/ui/src/routes/_layout/index.tsx b/ui/src/routes/_layout/index.tsx deleted file mode 100644 index f998f7f7..00000000 --- a/ui/src/routes/_layout/index.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import { useQuery } from "@tanstack/react-query"; -import { createFileRoute, Link, redirect } from "@tanstack/react-router"; -import { Building2, Plus, Shield } from "lucide-react"; -import { sessionQueryOptions, useApiClient, useAuthClient } from "@/app"; -import { Button, Card } from "@/components"; -import { PageContainer } from "@/components/layout/page-container"; - -export const Route = createFileRoute("/_layout/")({ - beforeLoad: async ({ context }) => { - const { authClient } = context; - const session = await context.queryClient.ensureQueryData( - sessionQueryOptions(authClient, context.session), - ); - if (!session?.user) { - throw redirect({ to: "/login" }); - } - }, - component: TenantListPage, -}); - -function TenantListPage() { - const apiClient = useApiClient(); - const auth = useAuthClient(); - const { data: session } = useQuery(sessionQueryOptions(auth, undefined)); - - const { data: tenants = [], isLoading } = useQuery({ - queryKey: ["tenants"], - queryFn: () => apiClient.listTenants(), - staleTime: 30_000, - }); - - return ( - -
-
-
- - Tenants -
-
-
-

- {session?.user?.name || session?.user?.email || "Your"} Tenants -

-
- -
-
- - {isLoading ? ( -
- {[1, 2, 3].map((n) => ( - -
-
-
- - ))} -
- ) : tenants.length === 0 ? ( - - -

No tenants yet.

-

- Create a tenant to deploy your own app with custom UI and API. -

- -
- ) : ( -
- {tenants.map((tenant) => ( - -
-
{tenant.name}
-
- {tenant.subdomain} -
-
-
- - -
-
- -
-
- ))} -
- )} -
- - ); -} - -function TenantMeta({ label, value, mono }: { label: string; value: string; mono?: boolean }) { - return ( -
- {label} - - {value} - -
- ); -} diff --git a/ui/src/routes/_layout/skill.tsx b/ui/src/routes/_layout/skill.tsx deleted file mode 100644 index db047a71..00000000 --- a/ui/src/routes/_layout/skill.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import { createFileRoute } from "@tanstack/react-router"; -import { Check, Copy, ExternalLink, FileText } from "lucide-react"; -import { useState } from "react"; -import { toast } from "sonner"; -import { getAccount, getActiveRuntime, getAppName } from "@/app"; -import { PageContainer } from "@/components/layout/page-container"; -import { Button } from "@/components/ui/button"; -import { Markdown } from "@/components/ui/markdown"; - -const INTENT_REGISTRY_URL = "https://tanstack.com/intent/registry/everything-dev"; - -export const Route = createFileRoute("/_layout/skill")({ - loader: async ({ context }) => { - const runtimeConfig = context.runtimeConfig; - - const skill = await fetch("/skill.md") - .then(async (response) => { - if (!response.ok) { - throw new Error(`Failed to load skill: ${response.status}`); - } - - return response.text(); - }) - .catch(() => null); - - return { - runtimeConfig, - skill, - intentRegistryUrl: INTENT_REGISTRY_URL, - }; - }, - head: () => ({ - meta: [ - { title: "Skill | app" }, - { - name: "description", - content: "Agent-oriented instructions for running, editing, and publishing this runtime.", - }, - ], - }), - component: SkillPage, -}); - -function SkillPage() { - const { skill, runtimeConfig, intentRegistryUrl } = Route.useLoaderData(); - const runtime = getActiveRuntime(runtimeConfig); - const account = getAccount(runtimeConfig); - const appName = getAppName(runtimeConfig); - const [copied, setCopied] = useState(false); - - const accountId = runtime?.accountId ?? account; - - const handleCopy = async () => { - if (!skill) { - toast.error("Skill prompt unavailable"); - return; - } - - await navigator.clipboard.writeText(skill); - setCopied(true); - toast.success("Skill prompt copied"); - setTimeout(() => setCopied(false), 2000); - }; - - return ( - -
-
-
-
-
- -
-
-
- {accountId} - / - {appName} -
-

- Agent-ready prompt for TanStack Intent, local development, UI changes, and publish - flow. -

-
-
- -
- - - -
-
- -
- Best entry points: `npx @tanstack/intent@latest load everything-dev`, `/skill.md`, and - the registry page above. -
-
- - {skill ? ( -
- -
- ) : ( -
- -

Skill prompt unavailable.

-
- )} -
-
- ); -} From 4f8f6e907d497bfecbef26649fc00554288635b5 Mon Sep 17 00:00:00 2001 From: Elliot Braem Date: Thu, 13 Aug 2026 14:20:22 -0500 Subject: [PATCH 02/17] upgrade bindings --- AGENTS.md | 2 +- LLM.txt | 2 +- README.md | 2 +- host/README.md | 16 +++++----------- host/src/services/tenant-runtime.ts | 3 ++- host/tests/integration/tenant-runtime.test.ts | 2 +- .../skills/plugin-client/SKILL.md | 2 +- .../skills/extends-config/SKILL.md | 13 +++---------- .../skills/init-upgrade/SKILL.md | 10 ++-------- .../everything-dev/skills/super-app/SKILL.md | 19 ++++++------------- 10 files changed, 23 insertions(+), 48 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 8453be7f..ddca75f8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -168,7 +168,7 @@ Current fixed-core host rules: - the shared host still boots once from one base runtime snapshot - child runtime config must extend the active BOS runtime - supported request-scoped overrides are `ui` and existing `plugins..ui` -- tenant SSR is gated by `TENANT_WHITELIST` and `ALLOW_UNTRUSTED_SSR` +- tenant SSR is gated per-tenant by the `allowSsr` column on the tenant record; the host's BindingResolver reads permissions from the API's `GET /tenants/bindings` endpoint (cached for 30s) - nested label routing and account-relative tenant derivation are the intended architecture direction, but not the complete resolver behavior today For full per-request host/plugin/auth/api swapping, start from `plans/runtime-config-hot-swap.md`. diff --git a/LLM.txt b/LLM.txt index 3b5f2820..5eb2fff3 100644 --- a/LLM.txt +++ b/LLM.txt @@ -48,7 +48,7 @@ Practical consequence: - Path-based runtime metadata works today. - Shared-host tenant UI mode now works today: the host can resolve a tenant config per request, enforce that it extends the base BOS runtime, and apply request-scoped UI overrides while keeping auth/API/plugin routers fixed. - Supported tenant overrides today are `app.ui`, existing `plugins..ui`, and existing `plugins..sidebar`. -- Tenant SSR is gated by `TENANT_WHITELIST` and `ALLOW_UNTRUSTED_SSR`. +- Tenant SSR is gated per-tenant by the `allow_ssr` column on the tenant record; the host's BindingResolver reads these permissions from the API's `GET /tenants/bindings` endpoint (cached for 30s). - True wildcard-domain full runtime swapping for host/plugin/auth/api still needs dynamic scoped app rebuilding in the host. - The active design doc for that work is `plans/runtime-config-hot-swap.md`. diff --git a/README.md b/README.md index 25b62e71..cf47c93f 100644 --- a/README.md +++ b/README.md @@ -204,7 +204,7 @@ What works today: - `app.ui` - existing `plugins..ui` - existing `plugins..sidebar` -- Tenant SSR is gated by `TENANT_WHITELIST` and `ALLOW_UNTRUSTED_SSR`. +- Tenant SSR is gated per-tenant by the `allow_ssr` column on the tenant record; the host's BindingResolver reads these permissions from the API's `GET /tenants/bindings` endpoint (cached for 30s). Design direction: - nested labels compose onto the active runtime account, such as `chicago.pizza.com -> bos://chicago.pizza.pingpayio.near/pizza.com` diff --git a/host/README.md b/host/README.md index 41c8db50..2f47fe65 100644 --- a/host/README.md +++ b/host/README.md @@ -96,9 +96,6 @@ For the temporary publish registry, use `bos publish` or `bos publish --deploy`. | `API_SOURCE` | `local` or `remote` | Based on NODE_ENV | | `API_PROXY` | Proxy API requests to another host URL | - | | `NETWORK_ID` | Tenant account suffix resolution: `mainnet` or `testnet` | `mainnet` | -| `ALLOW_OVERRIDE` | Comma-separated tenant override targets like `ui`, `plugins.*`, `plugins.apps` | - | -| `TENANT_WHITELIST` | Comma-separated tenant account IDs allowed to SSR | - | -| `ALLOW_UNTRUSTED_SSR` | Allow tenant SSR without whitelist | `false` | | `HOST_DATABASE_URL` | SQLite database URL for auth | `file:./database.db` | | `HOST_DATABASE_AUTH_TOKEN` | Auth token for remote database | - | | `BETTER_AUTH_SECRET` | Secret for session encryption | - | @@ -112,7 +109,7 @@ For the temporary publish registry, use `bos publish` or `bos publish --deploy`. - Current: tenant config must extend the base BOS runtime - Current: tenant accounts derive relative to the active runtime account namespace - Current: supported tenant overrides are `app.ui`, existing `plugins..ui`, and existing `plugins..sidebar` -- Current: tenant SSR is opt-in via `TENANT_WHITELIST` or `ALLOW_UNTRUSTED_SSR=true` +- Current: tenant SSR is gated per-tenant by the `allowSsr` column on the tenant record; the host's BindingResolver reads these permissions from the API's `GET /tenants/bindings` endpoint (cached for 30s) - Not yet implemented: tenant API/auth overrides in fixed-core mode - Not yet implemented: dynamic new plugin IDs per tenant @@ -123,9 +120,6 @@ Example deployment: ```bash BOS_ACCOUNT=linktree.near BOS_GATEWAY=linktree.com -ALLOW_OVERRIDE=ui,plugins.* -TENANT_WHITELIST=alice.linktree.near,bob.linktree.near -ALLOW_UNTRUSTED_SSR=false bos start --no-interactive ``` @@ -139,7 +133,8 @@ Example tenant behavior: Tenant config rules: - must extend the base BOS runtime -- may only override targets allowed by `ALLOW_OVERRIDE` +- may only override the tenant `ui` when `allow_ui_overrides` is true on the tenant record +- plugin UI overrides require `allow_backend_overrides` on the tenant record - in fixed-core mode, only UI-facing overrides are applied - custom UI remotes must provide integrity - custom plugin UI remotes must provide integrity @@ -147,9 +142,8 @@ Tenant config rules: Tenant SSR rules: -- if `ALLOW_UNTRUSTED_SSR=true`, any valid tenant UI with SSR config may SSR -- otherwise the tenant account must appear in `TENANT_WHITELIST` -- non-whitelisted tenants fall back to client rendering +- tenant SSR is allowed only when the tenant record has `allow_ssr` set +- tenants without `allow_ssr` fall back to client rendering ### Proxy Mode diff --git a/host/src/services/tenant-runtime.ts b/host/src/services/tenant-runtime.ts index 4f3b0cae..ca3adcf7 100644 --- a/host/src/services/tenant-runtime.ts +++ b/host/src/services/tenant-runtime.ts @@ -7,7 +7,7 @@ import { verifySriForUrl } from "everything-dev/integrity"; import type { RuntimePlugin } from "../types"; import { logger } from "../utils/logger"; import { resolveDomain } from "../utils/normalize"; -import { createBindingResolver, type BindingResolver } from "./binding-resolver"; +import { clearBindingResolverCache, createBindingResolver, type BindingResolver } from "./binding-resolver"; const REMOTE_CONFIG_TTL_MS = 30_000; const VERIFICATION_TTL_MS = 5 * 60_000; @@ -90,6 +90,7 @@ export function getTenantRuntimeErrorResponse(error: unknown): { status: number; export function clearTenantRuntimeCaches() { remoteConfigCache.clear(); verifiedUiCache.clear(); + clearBindingResolverCache(); } function getRemoteConfigCached(bosUrl: string, env: BosEnv) { diff --git a/host/tests/integration/tenant-runtime.test.ts b/host/tests/integration/tenant-runtime.test.ts index 1cbf21ec..96c536bb 100644 --- a/host/tests/integration/tenant-runtime.test.ts +++ b/host/tests/integration/tenant-runtime.test.ts @@ -1,4 +1,4 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; import type { RuntimeConfig } from "../../src/services/config"; const loadRemoteConfigMock = vi.fn(); diff --git a/packages/every-plugin/skills/plugin-client/SKILL.md b/packages/every-plugin/skills/plugin-client/SKILL.md index 7c8125b8..5a9f9d30 100644 --- a/packages/every-plugin/skills/plugin-client/SKILL.md +++ b/packages/every-plugin/skills/plugin-client/SKILL.md @@ -437,7 +437,7 @@ This writes `bos.config.json` with `extends`, scaffolds the project, and generat ### What Can Be Overridden -Child configs can override `app.ui` (custom UI CDN) and `plugins.` (custom plugin URLs). The host's `ALLOW_OVERRIDE` env var controls which sections tenants can customize (`ui`, `plugins`, `plugins.`). +Child configs can override `app.ui` (custom UI CDN) and `plugins.` (custom plugin URLs). Which sections a tenant can customize is controlled per-tenant by the `allow_ui_overrides` and `allow_backend_overrides` flags on the tenant record, resolved by the host from the API's `GET /tenants/bindings` endpoint (cached for 30s). See the `extends-config` skill for deep merge semantics, per-environment extends, and canonical field ordering. diff --git a/packages/everything-dev/skills/extends-config/SKILL.md b/packages/everything-dev/skills/extends-config/SKILL.md index 65fe9041..c8fc8d0a 100644 --- a/packages/everything-dev/skills/extends-config/SKILL.md +++ b/packages/everything-dev/skills/extends-config/SKILL.md @@ -73,13 +73,7 @@ Use this mental model: - `domain` is the public ingress for this runtime - a runtime can be a child in lineage and still become a new tenant root on its own domain -With host env like: - -```bash -ALLOW_OVERRIDE=ui,plugins.* -TENANT_WHITELIST=pizza.pingpayio.near -ALLOW_UNTRUSTED_SSR=false -``` +Tenant permissions come from the DB, not env vars. The host fetches the tenant bindings map from the API's `GET /tenants/bindings` endpoint (cached for 30s), and the tenant record's allow flags gate what may be overridden and whether SSR is permitted. Design target for request mapping: - `pizza.com` -> base runtime `bos://pizza.pingpayio.near/pizza.com` @@ -101,9 +95,8 @@ The tenant config must extend the base BOS runtime. Tenant API/auth overrides an ### SSR behavior Tenant SSR is gated separately from inheritance: -- if `ALLOW_UNTRUSTED_SSR=true`, any valid tenant with SSR config may SSR -- otherwise the tenant account must be listed in `TENANT_WHITELIST` -- non-whitelisted tenants fall back to client rendering +- a tenant may SSR only when its record has `allow_ssr` set +- tenants without `allow_ssr` fall back to client rendering ## Resolved Config: `.bos/bos.resolved-config.json` diff --git a/packages/everything-dev/skills/init-upgrade/SKILL.md b/packages/everything-dev/skills/init-upgrade/SKILL.md index df40b85e..dad27afa 100644 --- a/packages/everything-dev/skills/init-upgrade/SKILL.md +++ b/packages/everything-dev/skills/init-upgrade/SKILL.md @@ -81,15 +81,9 @@ Child apps can either run as their own base runtime on their own domain, or as r Shared-host children must extend the base runtime and do not introduce new server-side plugin IDs dynamically. -### 4. Host deployment env for shared-host mode +### 4. Host resolution for shared-host mode -The shared host uses these env vars to resolve descendant requests: - -```bash -ALLOW_OVERRIDE=ui,plugins.* -TENANT_WHITELIST=pizza.pingpayio.near,chicago.pizza.pingpayio.near -ALLOW_UNTRUSTED_SSR=false -``` +The shared host resolves descendant requests via a DB-backed binding map. It fetches tenant permissions from the API's `GET /tenants/bindings` endpoint (cached for 30s) — no env vars are needed: Design target, for example: - `pingpay.io` -> base runtime `bos://pingpayio.near/pingpay.io` diff --git a/packages/everything-dev/skills/super-app/SKILL.md b/packages/everything-dev/skills/super-app/SKILL.md index 4adae2f3..bc32b536 100644 --- a/packages/everything-dev/skills/super-app/SKILL.md +++ b/packages/everything-dev/skills/super-app/SKILL.md @@ -87,20 +87,13 @@ This runtime is still a lineage child because it extends the parent, but it is a You can also override existing plugin UIs and sidebar entries for tenant-specific navigation. -## Host Env For Tenant Mode +## Host Resolution For Tenant Mode -The shared host uses these env vars to resolve tenants: +The shared host resolves tenant permissions from the API, not env vars. It fetches the tenant bindings map from the API's `GET /tenants/bindings` endpoint (cached for 30s) and applies DB-backed per-tenant permissions: -```bash -ALLOW_OVERRIDE=ui,plugins.* -TENANT_WHITELIST=pizza.pingpayio.near,chicago.pizza.pingpayio.near -ALLOW_UNTRUSTED_SSR=false -``` - -Meaning: -- `ALLOW_OVERRIDE` controls which tenant config sections can affect request-scoped composition. Format: comma-separated list (e.g., `ui,plugins.*`) where `plugins.*` is a glob matching all plugin overrides -- `TENANT_WHITELIST` controls which tenants may use SSR -- `ALLOW_UNTRUSTED_SSR=true` allows SSR for any valid tenant with SSR config +- `allow_ui_overrides` controls whether the tenant `ui` override can affect request-scoped composition +- `allow_backend_overrides` controls whether tenant plugin UI overrides (`plugins..ui`, `plugins..sidebar`) are applied +- `allow_ssr` controls whether the tenant may use SSR ## Resolution Rules @@ -141,7 +134,7 @@ If tenant overrides are not applying as expected: 1. **Verify the tenant config exists in FastKV**: The config must be published to `{tenantAccount}/bos/gateways/{gateway}/bos.config.json` 2. **Check extends chain**: The tenant config must extend the base runtime via its `extends` field 3. **Check host logs**: Run `cat .bos/logs/host.log` and look for tenant resolution messages — the host logs which runtime config it resolved for each request -4. **Check env vars**: `ALLOW_OVERRIDE` must include the sections you're overriding (e.g., `ui` or `plugins.*`) +4. **Check tenant permissions**: the tenant record must have the relevant allow flag set in the database (e.g., `allow_ui_overrides` for a `ui` override, `allow_backend_overrides` for plugin UI overrides) 5. **Verify integrity**: If tenant remote URLs have integrity hashes, the host validates them — mismatches cause rejection 6. **Missing tenant config**: If the tenant config is not found in FastKV, the host silently serves the base runtime config without tenant overrides — no error is surfaced to the browser From d944f55df0415055187c0c785c16da6cfc8c8231 Mon Sep 17 00:00:00 2001 From: Elliot Braem Date: Thu, 13 Aug 2026 14:24:09 -0500 Subject: [PATCH 03/17] add branding --- .env.example | 6 +- ui/src/components/near-branding.tsx | 24 +++ ui/src/routeTree.gen.ts | 223 ++++++++++++++++++---------- 3 files changed, 168 insertions(+), 85 deletions(-) create mode 100644 ui/src/components/near-branding.tsx diff --git a/.env.example b/.env.example index 452789fd..dca67750 100644 --- a/.env.example +++ b/.env.example @@ -2,13 +2,15 @@ # Update values as needed for your local environment # app.host -CORS_ORIGIN=http://localhost:4100 +CORS_ORIGIN=http://localhost:3000 CSP_STRICT= # app.api API_DATABASE_URL=postgres://everythingdev:everythingdev@localhost:5432/api_db # app.auth +NEAR_SUB_ACCOUNT_PARENT_KEY_MAINNET= +NEAR_SUB_ACCOUNT_PARENT_KEY_TESTNET= AUTH_DATABASE_URL=postgres://everythingdev:everythingdev@localhost:5433/auth_db BETTER_AUTH_SECRET= GITHUB_CLIENT_SECRET= @@ -19,8 +21,6 @@ TWILIO_AUTH_TOKEN= TWILIO_PHONE_NUMBER= RESEND_API_KEY= NEAR_RELAYER_PRIVATE_KEY= -NEAR_SUB_ACCOUNT_PARENT_KEY_MAINNET= -NEAR_SUB_ACCOUNT_PARENT_KEY_TESTNET= # plugins.template TEMPLATE_DATABASE_URL=postgres://everythingdev:everythingdev@localhost:5434/template_db diff --git a/ui/src/components/near-branding.tsx b/ui/src/components/near-branding.tsx new file mode 100644 index 00000000..e3454dcf --- /dev/null +++ b/ui/src/components/near-branding.tsx @@ -0,0 +1,24 @@ +import builtOn from "@/assets/built_on.png"; +import builtOnRev from "@/assets/built_on_rev.png"; + +export function NearBranding() { + return ( + + Built on NEAR + Built on NEAR + + ); +} diff --git a/ui/src/routeTree.gen.ts b/ui/src/routeTree.gen.ts index 8ee2490c..c7659df3 100644 --- a/ui/src/routeTree.gen.ts +++ b/ui/src/routeTree.gen.ts @@ -10,21 +10,22 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as LayoutRouteImport } from './routes/_layout' -import { Route as LayoutIndexRouteImport } from './routes/_layout/index' -import { Route as LayoutSkillRouteImport } from './routes/_layout/skill' -import { Route as LayoutLoginRouteImport } from './routes/_layout/login' -import { Route as LayoutAboutRouteImport } from './routes/_layout/about' +import { Route as LayoutPublicRouteImport } from './routes/_layout/_public' import { Route as LayoutAuthenticatedRouteImport } from './routes/_layout/_authenticated' import { Route as LayoutThingsIndexRouteImport } from './routes/_layout/things/index' import { Route as LayoutAppsIndexRouteImport } from './routes/_layout/apps/index' +import { Route as LayoutPublicIndexRouteImport } from './routes/_layout/_public/index' import { Route as LayoutThingsLiveRouteImport } from './routes/_layout/things/live' import { Route as LayoutThingsThingIdRouteImport } from './routes/_layout/things/$thingId' +import { Route as LayoutPublicLoginRouteImport } from './routes/_layout/_public/login' +import { Route as LayoutPublicAboutRouteImport } from './routes/_layout/_public/about' import { Route as LayoutAuthenticatedSettingsRouteImport } from './routes/_layout/_authenticated/settings' import { Route as LayoutAuthenticatedHomeRouteImport } from './routes/_layout/_authenticated/home' import { Route as LayoutAuthenticatedAdminRouteImport } from './routes/_layout/_authenticated/admin' import { Route as LayoutAppsAccountIdIndexRouteImport } from './routes/_layout/apps/$accountId/index' import { Route as LayoutAuthenticatedSettingsIndexRouteImport } from './routes/_layout/_authenticated/settings/index' import { Route as LayoutAuthenticatedOrganizationsIndexRouteImport } from './routes/_layout/_authenticated/organizations/index' +import { Route as LayoutAuthenticatedAdminIndexRouteImport } from './routes/_layout/_authenticated/admin/index' import { Route as LayoutAppsAccountIdGatewayIdRouteImport } from './routes/_layout/apps/$accountId/$gatewayId' import { Route as LayoutAuthenticatedThingsNewRouteImport } from './routes/_layout/_authenticated/things/new' import { Route as LayoutAuthenticatedTenantNewRouteImport } from './routes/_layout/_authenticated/tenant/new' @@ -34,30 +35,15 @@ import { Route as LayoutAuthenticatedSettingsProfileRouteImport } from './routes import { Route as LayoutAuthenticatedSettingsAuthMethodsRouteImport } from './routes/_layout/_authenticated/settings/auth-methods' import { Route as LayoutAuthenticatedOrganizationsNewRouteImport } from './routes/_layout/_authenticated/organizations/new' import { Route as LayoutAuthenticatedOrganizationsSlugRouteImport } from './routes/_layout/_authenticated/organizations/$slug' +import { Route as LayoutAuthenticatedAdminSystemRouteImport } from './routes/_layout/_authenticated/admin/system' import { Route as LayoutAuthenticatedAcceptInvitationIdRouteImport } from './routes/_layout/_authenticated/accept-invitation.$id' const LayoutRoute = LayoutRouteImport.update({ id: '/_layout', getParentRoute: () => rootRouteImport, } as any) -const LayoutIndexRoute = LayoutIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => LayoutRoute, -} as any) -const LayoutSkillRoute = LayoutSkillRouteImport.update({ - id: '/skill', - path: '/skill', - getParentRoute: () => LayoutRoute, -} as any) -const LayoutLoginRoute = LayoutLoginRouteImport.update({ - id: '/login', - path: '/login', - getParentRoute: () => LayoutRoute, -} as any) -const LayoutAboutRoute = LayoutAboutRouteImport.update({ - id: '/about', - path: '/about', +const LayoutPublicRoute = LayoutPublicRouteImport.update({ + id: '/_public', getParentRoute: () => LayoutRoute, } as any) const LayoutAuthenticatedRoute = LayoutAuthenticatedRouteImport.update({ @@ -74,6 +60,11 @@ const LayoutAppsIndexRoute = LayoutAppsIndexRouteImport.update({ path: '/apps/', getParentRoute: () => LayoutRoute, } as any) +const LayoutPublicIndexRoute = LayoutPublicIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => LayoutPublicRoute, +} as any) const LayoutThingsLiveRoute = LayoutThingsLiveRouteImport.update({ id: '/things/live', path: '/things/live', @@ -84,6 +75,16 @@ const LayoutThingsThingIdRoute = LayoutThingsThingIdRouteImport.update({ path: '/things/$thingId', getParentRoute: () => LayoutRoute, } as any) +const LayoutPublicLoginRoute = LayoutPublicLoginRouteImport.update({ + id: '/login', + path: '/login', + getParentRoute: () => LayoutPublicRoute, +} as any) +const LayoutPublicAboutRoute = LayoutPublicAboutRouteImport.update({ + id: '/about', + path: '/about', + getParentRoute: () => LayoutPublicRoute, +} as any) const LayoutAuthenticatedSettingsRoute = LayoutAuthenticatedSettingsRouteImport.update({ id: '/settings', @@ -119,6 +120,12 @@ const LayoutAuthenticatedOrganizationsIndexRoute = path: '/organizations/', getParentRoute: () => LayoutAuthenticatedRoute, } as any) +const LayoutAuthenticatedAdminIndexRoute = + LayoutAuthenticatedAdminIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => LayoutAuthenticatedAdminRoute, + } as any) const LayoutAppsAccountIdGatewayIdRoute = LayoutAppsAccountIdGatewayIdRouteImport.update({ id: '/apps/$accountId/$gatewayId', @@ -173,6 +180,12 @@ const LayoutAuthenticatedOrganizationsSlugRoute = path: '/organizations/$slug', getParentRoute: () => LayoutAuthenticatedRoute, } as any) +const LayoutAuthenticatedAdminSystemRoute = + LayoutAuthenticatedAdminSystemRouteImport.update({ + id: '/system', + path: '/system', + getParentRoute: () => LayoutAuthenticatedAdminRoute, + } as any) const LayoutAuthenticatedAcceptInvitationIdRoute = LayoutAuthenticatedAcceptInvitationIdRouteImport.update({ id: '/accept-invitation/$id', @@ -181,18 +194,18 @@ const LayoutAuthenticatedAcceptInvitationIdRoute = } as any) export interface FileRoutesByFullPath { - '/': typeof LayoutIndexRoute - '/about': typeof LayoutAboutRoute - '/login': typeof LayoutLoginRoute - '/skill': typeof LayoutSkillRoute - '/admin': typeof LayoutAuthenticatedAdminRoute + '/': typeof LayoutPublicIndexRoute + '/admin': typeof LayoutAuthenticatedAdminRouteWithChildren '/home': typeof LayoutAuthenticatedHomeRoute '/settings': typeof LayoutAuthenticatedSettingsRouteWithChildren + '/about': typeof LayoutPublicAboutRoute + '/login': typeof LayoutPublicLoginRoute '/things/$thingId': typeof LayoutThingsThingIdRoute '/things/live': typeof LayoutThingsLiveRoute '/apps/': typeof LayoutAppsIndexRoute '/things/': typeof LayoutThingsIndexRoute '/accept-invitation/$id': typeof LayoutAuthenticatedAcceptInvitationIdRoute + '/admin/system': typeof LayoutAuthenticatedAdminSystemRoute '/organizations/$slug': typeof LayoutAuthenticatedOrganizationsSlugRoute '/organizations/new': typeof LayoutAuthenticatedOrganizationsNewRoute '/settings/auth-methods': typeof LayoutAuthenticatedSettingsAuthMethodsRoute @@ -202,22 +215,22 @@ export interface FileRoutesByFullPath { '/tenant/new': typeof LayoutAuthenticatedTenantNewRoute '/things/new': typeof LayoutAuthenticatedThingsNewRoute '/apps/$accountId/$gatewayId': typeof LayoutAppsAccountIdGatewayIdRoute + '/admin/': typeof LayoutAuthenticatedAdminIndexRoute '/organizations/': typeof LayoutAuthenticatedOrganizationsIndexRoute '/settings/': typeof LayoutAuthenticatedSettingsIndexRoute '/apps/$accountId/': typeof LayoutAppsAccountIdIndexRoute } export interface FileRoutesByTo { - '/': typeof LayoutIndexRoute - '/about': typeof LayoutAboutRoute - '/login': typeof LayoutLoginRoute - '/skill': typeof LayoutSkillRoute - '/admin': typeof LayoutAuthenticatedAdminRoute + '/': typeof LayoutPublicIndexRoute '/home': typeof LayoutAuthenticatedHomeRoute + '/about': typeof LayoutPublicAboutRoute + '/login': typeof LayoutPublicLoginRoute '/things/$thingId': typeof LayoutThingsThingIdRoute '/things/live': typeof LayoutThingsLiveRoute '/apps': typeof LayoutAppsIndexRoute '/things': typeof LayoutThingsIndexRoute '/accept-invitation/$id': typeof LayoutAuthenticatedAcceptInvitationIdRoute + '/admin/system': typeof LayoutAuthenticatedAdminSystemRoute '/organizations/$slug': typeof LayoutAuthenticatedOrganizationsSlugRoute '/organizations/new': typeof LayoutAuthenticatedOrganizationsNewRoute '/settings/auth-methods': typeof LayoutAuthenticatedSettingsAuthMethodsRoute @@ -227,6 +240,7 @@ export interface FileRoutesByTo { '/tenant/new': typeof LayoutAuthenticatedTenantNewRoute '/things/new': typeof LayoutAuthenticatedThingsNewRoute '/apps/$accountId/$gatewayId': typeof LayoutAppsAccountIdGatewayIdRoute + '/admin': typeof LayoutAuthenticatedAdminIndexRoute '/organizations': typeof LayoutAuthenticatedOrganizationsIndexRoute '/settings': typeof LayoutAuthenticatedSettingsIndexRoute '/apps/$accountId': typeof LayoutAppsAccountIdIndexRoute @@ -235,18 +249,19 @@ export interface FileRoutesById { __root__: typeof rootRouteImport '/_layout': typeof LayoutRouteWithChildren '/_layout/_authenticated': typeof LayoutAuthenticatedRouteWithChildren - '/_layout/about': typeof LayoutAboutRoute - '/_layout/login': typeof LayoutLoginRoute - '/_layout/skill': typeof LayoutSkillRoute - '/_layout/': typeof LayoutIndexRoute - '/_layout/_authenticated/admin': typeof LayoutAuthenticatedAdminRoute + '/_layout/_public': typeof LayoutPublicRouteWithChildren + '/_layout/_authenticated/admin': typeof LayoutAuthenticatedAdminRouteWithChildren '/_layout/_authenticated/home': typeof LayoutAuthenticatedHomeRoute '/_layout/_authenticated/settings': typeof LayoutAuthenticatedSettingsRouteWithChildren + '/_layout/_public/about': typeof LayoutPublicAboutRoute + '/_layout/_public/login': typeof LayoutPublicLoginRoute '/_layout/things/$thingId': typeof LayoutThingsThingIdRoute '/_layout/things/live': typeof LayoutThingsLiveRoute + '/_layout/_public/': typeof LayoutPublicIndexRoute '/_layout/apps/': typeof LayoutAppsIndexRoute '/_layout/things/': typeof LayoutThingsIndexRoute '/_layout/_authenticated/accept-invitation/$id': typeof LayoutAuthenticatedAcceptInvitationIdRoute + '/_layout/_authenticated/admin/system': typeof LayoutAuthenticatedAdminSystemRoute '/_layout/_authenticated/organizations/$slug': typeof LayoutAuthenticatedOrganizationsSlugRoute '/_layout/_authenticated/organizations/new': typeof LayoutAuthenticatedOrganizationsNewRoute '/_layout/_authenticated/settings/auth-methods': typeof LayoutAuthenticatedSettingsAuthMethodsRoute @@ -256,6 +271,7 @@ export interface FileRoutesById { '/_layout/_authenticated/tenant/new': typeof LayoutAuthenticatedTenantNewRoute '/_layout/_authenticated/things/new': typeof LayoutAuthenticatedThingsNewRoute '/_layout/apps/$accountId/$gatewayId': typeof LayoutAppsAccountIdGatewayIdRoute + '/_layout/_authenticated/admin/': typeof LayoutAuthenticatedAdminIndexRoute '/_layout/_authenticated/organizations/': typeof LayoutAuthenticatedOrganizationsIndexRoute '/_layout/_authenticated/settings/': typeof LayoutAuthenticatedSettingsIndexRoute '/_layout/apps/$accountId/': typeof LayoutAppsAccountIdIndexRoute @@ -264,17 +280,17 @@ export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath fullPaths: | '/' - | '/about' - | '/login' - | '/skill' | '/admin' | '/home' | '/settings' + | '/about' + | '/login' | '/things/$thingId' | '/things/live' | '/apps/' | '/things/' | '/accept-invitation/$id' + | '/admin/system' | '/organizations/$slug' | '/organizations/new' | '/settings/auth-methods' @@ -284,22 +300,22 @@ export interface FileRouteTypes { | '/tenant/new' | '/things/new' | '/apps/$accountId/$gatewayId' + | '/admin/' | '/organizations/' | '/settings/' | '/apps/$accountId/' fileRoutesByTo: FileRoutesByTo to: | '/' + | '/home' | '/about' | '/login' - | '/skill' - | '/admin' - | '/home' | '/things/$thingId' | '/things/live' | '/apps' | '/things' | '/accept-invitation/$id' + | '/admin/system' | '/organizations/$slug' | '/organizations/new' | '/settings/auth-methods' @@ -309,6 +325,7 @@ export interface FileRouteTypes { | '/tenant/new' | '/things/new' | '/apps/$accountId/$gatewayId' + | '/admin' | '/organizations' | '/settings' | '/apps/$accountId' @@ -316,18 +333,19 @@ export interface FileRouteTypes { | '__root__' | '/_layout' | '/_layout/_authenticated' - | '/_layout/about' - | '/_layout/login' - | '/_layout/skill' - | '/_layout/' + | '/_layout/_public' | '/_layout/_authenticated/admin' | '/_layout/_authenticated/home' | '/_layout/_authenticated/settings' + | '/_layout/_public/about' + | '/_layout/_public/login' | '/_layout/things/$thingId' | '/_layout/things/live' + | '/_layout/_public/' | '/_layout/apps/' | '/_layout/things/' | '/_layout/_authenticated/accept-invitation/$id' + | '/_layout/_authenticated/admin/system' | '/_layout/_authenticated/organizations/$slug' | '/_layout/_authenticated/organizations/new' | '/_layout/_authenticated/settings/auth-methods' @@ -337,6 +355,7 @@ export interface FileRouteTypes { | '/_layout/_authenticated/tenant/new' | '/_layout/_authenticated/things/new' | '/_layout/apps/$accountId/$gatewayId' + | '/_layout/_authenticated/admin/' | '/_layout/_authenticated/organizations/' | '/_layout/_authenticated/settings/' | '/_layout/apps/$accountId/' @@ -355,32 +374,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutRouteImport parentRoute: typeof rootRouteImport } - '/_layout/': { - id: '/_layout/' - path: '/' + '/_layout/_public': { + id: '/_layout/_public' + path: '' fullPath: '/' - preLoaderRoute: typeof LayoutIndexRouteImport - parentRoute: typeof LayoutRoute - } - '/_layout/skill': { - id: '/_layout/skill' - path: '/skill' - fullPath: '/skill' - preLoaderRoute: typeof LayoutSkillRouteImport - parentRoute: typeof LayoutRoute - } - '/_layout/login': { - id: '/_layout/login' - path: '/login' - fullPath: '/login' - preLoaderRoute: typeof LayoutLoginRouteImport - parentRoute: typeof LayoutRoute - } - '/_layout/about': { - id: '/_layout/about' - path: '/about' - fullPath: '/about' - preLoaderRoute: typeof LayoutAboutRouteImport + preLoaderRoute: typeof LayoutPublicRouteImport parentRoute: typeof LayoutRoute } '/_layout/_authenticated': { @@ -404,6 +402,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutAppsIndexRouteImport parentRoute: typeof LayoutRoute } + '/_layout/_public/': { + id: '/_layout/_public/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof LayoutPublicIndexRouteImport + parentRoute: typeof LayoutPublicRoute + } '/_layout/things/live': { id: '/_layout/things/live' path: '/things/live' @@ -418,6 +423,20 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutThingsThingIdRouteImport parentRoute: typeof LayoutRoute } + '/_layout/_public/login': { + id: '/_layout/_public/login' + path: '/login' + fullPath: '/login' + preLoaderRoute: typeof LayoutPublicLoginRouteImport + parentRoute: typeof LayoutPublicRoute + } + '/_layout/_public/about': { + id: '/_layout/_public/about' + path: '/about' + fullPath: '/about' + preLoaderRoute: typeof LayoutPublicAboutRouteImport + parentRoute: typeof LayoutPublicRoute + } '/_layout/_authenticated/settings': { id: '/_layout/_authenticated/settings' path: '/settings' @@ -460,6 +479,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutAuthenticatedOrganizationsIndexRouteImport parentRoute: typeof LayoutAuthenticatedRoute } + '/_layout/_authenticated/admin/': { + id: '/_layout/_authenticated/admin/' + path: '/' + fullPath: '/admin/' + preLoaderRoute: typeof LayoutAuthenticatedAdminIndexRouteImport + parentRoute: typeof LayoutAuthenticatedAdminRoute + } '/_layout/apps/$accountId/$gatewayId': { id: '/_layout/apps/$accountId/$gatewayId' path: '/apps/$accountId/$gatewayId' @@ -523,6 +549,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutAuthenticatedOrganizationsSlugRouteImport parentRoute: typeof LayoutAuthenticatedRoute } + '/_layout/_authenticated/admin/system': { + id: '/_layout/_authenticated/admin/system' + path: '/system' + fullPath: '/admin/system' + preLoaderRoute: typeof LayoutAuthenticatedAdminSystemRouteImport + parentRoute: typeof LayoutAuthenticatedAdminRoute + } '/_layout/_authenticated/accept-invitation/$id': { id: '/_layout/_authenticated/accept-invitation/$id' path: '/accept-invitation/$id' @@ -533,6 +566,22 @@ declare module '@tanstack/react-router' { } } +interface LayoutAuthenticatedAdminRouteChildren { + LayoutAuthenticatedAdminSystemRoute: typeof LayoutAuthenticatedAdminSystemRoute + LayoutAuthenticatedAdminIndexRoute: typeof LayoutAuthenticatedAdminIndexRoute +} + +const LayoutAuthenticatedAdminRouteChildren: LayoutAuthenticatedAdminRouteChildren = + { + LayoutAuthenticatedAdminSystemRoute: LayoutAuthenticatedAdminSystemRoute, + LayoutAuthenticatedAdminIndexRoute: LayoutAuthenticatedAdminIndexRoute, + } + +const LayoutAuthenticatedAdminRouteWithChildren = + LayoutAuthenticatedAdminRoute._addFileChildren( + LayoutAuthenticatedAdminRouteChildren, + ) + interface LayoutAuthenticatedSettingsRouteChildren { LayoutAuthenticatedSettingsAuthMethodsRoute: typeof LayoutAuthenticatedSettingsAuthMethodsRoute LayoutAuthenticatedSettingsProfileRoute: typeof LayoutAuthenticatedSettingsProfileRoute @@ -558,7 +607,7 @@ const LayoutAuthenticatedSettingsRouteWithChildren = ) interface LayoutAuthenticatedRouteChildren { - LayoutAuthenticatedAdminRoute: typeof LayoutAuthenticatedAdminRoute + LayoutAuthenticatedAdminRoute: typeof LayoutAuthenticatedAdminRouteWithChildren LayoutAuthenticatedHomeRoute: typeof LayoutAuthenticatedHomeRoute LayoutAuthenticatedSettingsRoute: typeof LayoutAuthenticatedSettingsRouteWithChildren LayoutAuthenticatedAcceptInvitationIdRoute: typeof LayoutAuthenticatedAcceptInvitationIdRoute @@ -571,7 +620,7 @@ interface LayoutAuthenticatedRouteChildren { } const LayoutAuthenticatedRouteChildren: LayoutAuthenticatedRouteChildren = { - LayoutAuthenticatedAdminRoute: LayoutAuthenticatedAdminRoute, + LayoutAuthenticatedAdminRoute: LayoutAuthenticatedAdminRouteWithChildren, LayoutAuthenticatedHomeRoute: LayoutAuthenticatedHomeRoute, LayoutAuthenticatedSettingsRoute: LayoutAuthenticatedSettingsRouteWithChildren, @@ -592,12 +641,25 @@ const LayoutAuthenticatedRouteChildren: LayoutAuthenticatedRouteChildren = { const LayoutAuthenticatedRouteWithChildren = LayoutAuthenticatedRoute._addFileChildren(LayoutAuthenticatedRouteChildren) +interface LayoutPublicRouteChildren { + LayoutPublicAboutRoute: typeof LayoutPublicAboutRoute + LayoutPublicLoginRoute: typeof LayoutPublicLoginRoute + LayoutPublicIndexRoute: typeof LayoutPublicIndexRoute +} + +const LayoutPublicRouteChildren: LayoutPublicRouteChildren = { + LayoutPublicAboutRoute: LayoutPublicAboutRoute, + LayoutPublicLoginRoute: LayoutPublicLoginRoute, + LayoutPublicIndexRoute: LayoutPublicIndexRoute, +} + +const LayoutPublicRouteWithChildren = LayoutPublicRoute._addFileChildren( + LayoutPublicRouteChildren, +) + interface LayoutRouteChildren { LayoutAuthenticatedRoute: typeof LayoutAuthenticatedRouteWithChildren - LayoutAboutRoute: typeof LayoutAboutRoute - LayoutLoginRoute: typeof LayoutLoginRoute - LayoutSkillRoute: typeof LayoutSkillRoute - LayoutIndexRoute: typeof LayoutIndexRoute + LayoutPublicRoute: typeof LayoutPublicRouteWithChildren LayoutThingsThingIdRoute: typeof LayoutThingsThingIdRoute LayoutThingsLiveRoute: typeof LayoutThingsLiveRoute LayoutAppsIndexRoute: typeof LayoutAppsIndexRoute @@ -608,10 +670,7 @@ interface LayoutRouteChildren { const LayoutRouteChildren: LayoutRouteChildren = { LayoutAuthenticatedRoute: LayoutAuthenticatedRouteWithChildren, - LayoutAboutRoute: LayoutAboutRoute, - LayoutLoginRoute: LayoutLoginRoute, - LayoutSkillRoute: LayoutSkillRoute, - LayoutIndexRoute: LayoutIndexRoute, + LayoutPublicRoute: LayoutPublicRouteWithChildren, LayoutThingsThingIdRoute: LayoutThingsThingIdRoute, LayoutThingsLiveRoute: LayoutThingsLiveRoute, LayoutAppsIndexRoute: LayoutAppsIndexRoute, From e1a050495936b14d91109fed6591c375a21f067f Mon Sep 17 00:00:00 2001 From: Elliot Braem Date: Thu, 13 Aug 2026 17:23:21 -0500 Subject: [PATCH 04/17] working --- .env.example | 2 +- api/src/contract.ts | 2 +- api/src/db/migrations/0003_brief_freak.sql | 1 + api/src/db/migrations/meta/0002_snapshot.json | 21 +- api/src/db/migrations/meta/0003_snapshot.json | 165 +++++++++++++++ api/src/db/migrations/meta/_journal.json | 9 +- api/src/db/schema.ts | 12 +- api/src/index.ts | 15 +- api/src/services/tenants.ts | 16 +- host/src/services/binding-resolver.ts | 13 +- host/src/services/tenant-runtime.ts | 12 +- .../integration/ssr-bundled-runtime.test.ts | 2 +- host/tests/integration/tenant-runtime.test.ts | 45 ++-- package.json | 2 +- .../skills/api-and-auth/SKILL.md | 48 +++++ packages/everything-dev/src/infra/planner.ts | 3 + packages/everything-dev/src/ui/router.ts | 51 ++--- plugins/_template/src/db/schema.ts | 18 ++ .../regression/browser/helpers/page-ready.ts | 18 ++ tests/regression/browser/helpers/seeded.ts | 2 +- .../browser/specs/auth-redirect.spec.ts | 4 +- tests/regression/browser/specs/logout.spec.ts | 12 +- tests/regression/browser/specs/theme.spec.ts | 15 +- tests/regression/http/metadata_test.go | 28 +++ ui/rsbuild.config.ts | 2 +- ui/src/components/index.ts | 1 + ui/src/components/user-nav.tsx | 82 ++++++-- ui/src/lib/near-profile.ts | 17 ++ ui/src/routeTree.gen.ts | 42 ++++ ui/src/routes/__root.tsx | 3 +- ui/src/routes/_layout.tsx | 5 +- ui/src/routes/_layout/_authenticated/home.tsx | 19 +- ui/src/routes/_layout/_public/$accountId.tsx | 197 ++++++++++++++++++ ui/src/routes/_layout/_public/skill.tsx | 126 +++++++++++ 34 files changed, 857 insertions(+), 153 deletions(-) create mode 100644 api/src/db/migrations/0003_brief_freak.sql create mode 100644 api/src/db/migrations/meta/0003_snapshot.json create mode 100644 ui/src/lib/near-profile.ts create mode 100644 ui/src/routes/_layout/_public/$accountId.tsx create mode 100644 ui/src/routes/_layout/_public/skill.tsx diff --git a/.env.example b/.env.example index dca67750..e9e9b5e3 100644 --- a/.env.example +++ b/.env.example @@ -2,7 +2,7 @@ # Update values as needed for your local environment # app.host -CORS_ORIGIN=http://localhost:3000 +CORS_ORIGIN=http://localhost:4100 CSP_STRICT= # app.api diff --git a/api/src/contract.ts b/api/src/contract.ts index 92bc79b2..3c9cd6e8 100644 --- a/api/src/contract.ts +++ b/api/src/contract.ts @@ -17,7 +17,7 @@ export const TenantSchema = z.object({ id: z.string(), subdomain: z.string(), accountId: z.string(), - orgId: z.string(), + orgId: z.string().nullable(), name: z.string(), status: TenantStatusSchema, allowUiOverrides: z.boolean(), diff --git a/api/src/db/migrations/0003_brief_freak.sql b/api/src/db/migrations/0003_brief_freak.sql new file mode 100644 index 00000000..1a294453 --- /dev/null +++ b/api/src/db/migrations/0003_brief_freak.sql @@ -0,0 +1 @@ +ALTER TABLE "tenants" ALTER COLUMN "org_id" DROP NOT NULL; \ No newline at end of file diff --git a/api/src/db/migrations/meta/0002_snapshot.json b/api/src/db/migrations/meta/0002_snapshot.json index fda7cde0..4351ee45 100644 --- a/api/src/db/migrations/meta/0002_snapshot.json +++ b/api/src/db/migrations/meta/0002_snapshot.json @@ -127,23 +127,17 @@ "tenants_subdomain_unique": { "name": "tenants_subdomain_unique", "nullsNotDistinct": false, - "columns": [ - "subdomain" - ] + "columns": ["subdomain"] }, "tenants_account_id_unique": { "name": "tenants_account_id_unique", "nullsNotDistinct": false, - "columns": [ - "account_id" - ] + "columns": ["account_id"] }, "tenants_org_id_unique": { "name": "tenants_org_id_unique", "nullsNotDistinct": false, - "columns": [ - "org_id" - ] + "columns": ["org_id"] } }, "policies": {}, @@ -155,12 +149,7 @@ "public.tenant_status": { "name": "tenant_status", "schema": "public", - "values": [ - "active", - "pending", - "suspended", - "pending_deletion" - ] + "values": ["active", "pending", "suspended", "pending_deletion"] } }, "schemas": {}, @@ -173,4 +162,4 @@ "schemas": {}, "tables": {} } -} \ No newline at end of file +} diff --git a/api/src/db/migrations/meta/0003_snapshot.json b/api/src/db/migrations/meta/0003_snapshot.json new file mode 100644 index 00000000..b013ab2b --- /dev/null +++ b/api/src/db/migrations/meta/0003_snapshot.json @@ -0,0 +1,165 @@ +{ + "id": "85c9d130-a4b6-4026-a2df-890fddfa7518", + "prevId": "7b8a421a-d0c7-414a-96e8-0c259935178b", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.tenants": { + "name": "tenants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "subdomain": { + "name": "subdomain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "tenant_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "allow_ui_overrides": { + "name": "allow_ui_overrides", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "allow_backend_overrides": { + "name": "allow_backend_overrides", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allow_ssr": { + "name": "allow_ssr", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "tenants_subdomain_idx": { + "name": "tenants_subdomain_idx", + "columns": [ + { + "expression": "subdomain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tenants_account_id_idx": { + "name": "tenants_account_id_idx", + "columns": [ + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tenants_subdomain_unique": { + "name": "tenants_subdomain_unique", + "nullsNotDistinct": false, + "columns": ["subdomain"] + }, + "tenants_account_id_unique": { + "name": "tenants_account_id_unique", + "nullsNotDistinct": false, + "columns": ["account_id"] + }, + "tenants_org_id_unique": { + "name": "tenants_org_id_unique", + "nullsNotDistinct": false, + "columns": ["org_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.tenant_status": { + "name": "tenant_status", + "schema": "public", + "values": ["active", "pending", "suspended", "pending_deletion"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/api/src/db/migrations/meta/_journal.json b/api/src/db/migrations/meta/_journal.json index 9e5e42bb..74597e55 100644 --- a/api/src/db/migrations/meta/_journal.json +++ b/api/src/db/migrations/meta/_journal.json @@ -22,6 +22,13 @@ "when": 1786645659094, "tag": "0002_awesome_madame_web", "breakpoints": true + }, + { + "idx": 3, + "version": "7", + "when": 1786653436589, + "tag": "0003_brief_freak", + "breakpoints": true } ] -} \ No newline at end of file +} diff --git a/api/src/db/schema.ts b/api/src/db/schema.ts index c689380d..24aedf51 100644 --- a/api/src/db/schema.ts +++ b/api/src/db/schema.ts @@ -1,12 +1,4 @@ -import { - boolean, - pgEnum, - pgTable, - text, - timestamp, - uniqueIndex, - uuid, -} from "drizzle-orm/pg-core"; +import { boolean, pgEnum, pgTable, text, timestamp, uniqueIndex, uuid } from "drizzle-orm/pg-core"; export const tenantStatus = pgEnum("tenant_status", [ "active", @@ -21,7 +13,7 @@ export const tenants = pgTable( id: uuid("id").defaultRandom().primaryKey(), subdomain: text("subdomain").notNull().unique(), accountId: text("account_id").notNull().unique(), - orgId: text("org_id").notNull().unique(), + orgId: text("org_id").unique(), name: text("name").notNull(), status: tenantStatus("status").default("active").notNull(), allowUiOverrides: boolean("allow_ui_overrides").default(true).notNull(), diff --git a/api/src/index.ts b/api/src/index.ts index 9ebbaed8..cf2d04d0 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -89,7 +89,10 @@ export default createPlugin.withPlugins()({ const authorizedTenant = async ( input: { tenantId: string }, - context: { organization: { activeOrganizationId: string } }, + context: { + organization: { activeOrganizationId: string }; + near?: { primaryAccountId: string | null }; + }, ) => { const activeOrgId = context.organization.activeOrganizationId; const tenant = await services.tenants.resolveTenantById(input.tenantId); @@ -99,6 +102,16 @@ export default createPlugin.withPlugins()({ data: { resource: "tenant", resourceId: input.tenantId }, }); } + if (tenant.orgId === null) { + const isOwner = + !!context.near?.primaryAccountId && context.near.primaryAccountId === tenant.accountId; + if (!isOwner) { + throw new ORPCError("FORBIDDEN", { + message: "You do not own this personal tenant", + }); + } + return tenant; + } if (tenant.orgId !== activeOrgId) { throw new ORPCError("FORBIDDEN", { message: "You are not a member of this tenant's organization", diff --git a/api/src/services/tenants.ts b/api/src/services/tenants.ts index 4cdb1aa5..34f6231b 100644 --- a/api/src/services/tenants.ts +++ b/api/src/services/tenants.ts @@ -10,7 +10,7 @@ export interface TenantRecord { id: string; subdomain: string; accountId: string; - orgId: string; + orgId: string | null; name: string; status: TenantStatus; allowUiOverrides: boolean; @@ -34,7 +34,7 @@ export interface TenantInput { subdomain: string; name: string; accountId: string; - orgId: string; + orgId: string | null; status?: TenantStatus; allowUiOverrides?: boolean; allowBackendOverrides?: boolean; @@ -50,7 +50,13 @@ export interface TenantsService { input: Partial< Pick< TenantInput, - "name" | "subdomain" | "accountId" | "status" | "allowUiOverrides" | "allowBackendOverrides" | "allowSsr" + | "name" + | "subdomain" + | "accountId" + | "status" + | "allowUiOverrides" + | "allowBackendOverrides" + | "allowSsr" > >, ): Promise; @@ -143,7 +149,9 @@ export const TenantsLive = Layer.effect( accountId: input.accountId, orgId: input.orgId, ...(input.status !== undefined && { status: input.status }), - ...(input.allowUiOverrides !== undefined && { allowUiOverrides: input.allowUiOverrides }), + ...(input.allowUiOverrides !== undefined && { + allowUiOverrides: input.allowUiOverrides, + }), ...(input.allowBackendOverrides !== undefined && { allowBackendOverrides: input.allowBackendOverrides, }), diff --git a/host/src/services/binding-resolver.ts b/host/src/services/binding-resolver.ts index 30ed5ee4..0a97472d 100644 --- a/host/src/services/binding-resolver.ts +++ b/host/src/services/binding-resolver.ts @@ -30,11 +30,7 @@ export function clearBindingResolverCache() { function isBaseHost(hostname: string, gatewayId: string): boolean { const normalized = hostname.toLowerCase(); - return ( - normalized === gatewayId || - normalized === "localhost" || - normalized === "127.0.0.1" - ); + return normalized === gatewayId || normalized === "localhost" || normalized === "127.0.0.1"; } async function fetchBindingsFromApi(apiUrl: string): Promise { @@ -69,9 +65,7 @@ function mapBindingsByHostname( return entries; } -function ensureBindingsLoaded( - config: RuntimeConfig, -): Promise> { +function ensureBindingsLoaded(config: RuntimeConfig): Promise> { const apiUrl = config.api?.url; if (!apiUrl) { return Promise.resolve(new Map()); @@ -91,8 +85,7 @@ function ensureBindingsLoaded( return bindingsCache.refetching; } - const staleEntries = - bindingsCache?.apiUrl === apiUrl ? bindingsCache.entries : null; + const staleEntries = bindingsCache?.apiUrl === apiUrl ? bindingsCache.entries : null; const fetchPromise = fetchBindingsFromApi(apiUrl) .then((bindings) => { diff --git a/host/src/services/tenant-runtime.ts b/host/src/services/tenant-runtime.ts index ca3adcf7..69c1aa2a 100644 --- a/host/src/services/tenant-runtime.ts +++ b/host/src/services/tenant-runtime.ts @@ -1,13 +1,13 @@ -import { - buildRuntimeConfig, - loadRemoteConfig, - type RuntimeConfig, -} from "everything-dev/config"; +import { buildRuntimeConfig, loadRemoteConfig, type RuntimeConfig } from "everything-dev/config"; import { verifySriForUrl } from "everything-dev/integrity"; import type { RuntimePlugin } from "../types"; import { logger } from "../utils/logger"; import { resolveDomain } from "../utils/normalize"; -import { clearBindingResolverCache, createBindingResolver, type BindingResolver } from "./binding-resolver"; +import { + type BindingResolver, + clearBindingResolverCache, + createBindingResolver, +} from "./binding-resolver"; const REMOTE_CONFIG_TTL_MS = 30_000; const VERIFICATION_TTL_MS = 5 * 60_000; diff --git a/host/tests/integration/ssr-bundled-runtime.test.ts b/host/tests/integration/ssr-bundled-runtime.test.ts index d26b3177..c880184a 100644 --- a/host/tests/integration/ssr-bundled-runtime.test.ts +++ b/host/tests/integration/ssr-bundled-runtime.test.ts @@ -57,7 +57,7 @@ describe("bundled host SSR runtime", () => { const html = await response.text(); expect(response.status).toBe(200); - expect(html).toContain("connect to everything"); + expect(html).toContain("everything.dev"); expect(html).not.toContain("SSR unavailable, showing client app."); expect(html).not.toContain("

Loading...

"); diff --git a/host/tests/integration/tenant-runtime.test.ts b/host/tests/integration/tenant-runtime.test.ts index 96c536bb..1831a817 100644 --- a/host/tests/integration/tenant-runtime.test.ts +++ b/host/tests/integration/tenant-runtime.test.ts @@ -19,6 +19,7 @@ vi.mock("everything-dev/integrity", () => ({ const { clearTenantRuntimeCaches, resolveRequestRuntime } = await import( "../../src/services/tenant-runtime" ); + import type { BindingResolver } from "../../src/services/binding-resolver"; function createDeferred() { @@ -121,11 +122,9 @@ describe("resolveRequestRuntime", () => { it("returns the base runtime on the bare domain", async () => { const baseConfig = createBaseRuntimeConfig(); - const result = await resolveRequestRuntime( - baseConfig, - new Request("https://linktree.com/"), - { bindingResolver: createMockBindingResolver() }, - ); + const result = await resolveRequestRuntime(baseConfig, new Request("https://linktree.com/"), { + bindingResolver: createMockBindingResolver(), + }); expect(result.config).toBe(baseConfig); expect(result.tenantAccountId).toBeNull(); @@ -677,17 +676,13 @@ describe("resolveRequestRuntime", () => { }, }); - await resolveRequestRuntime( - baseConfig, - new Request("https://alice.linktree.com/"), - { - bindingResolver: createMockBindingResolver({ - hostname: "alice.linktree.com", - allowUiOverrides: true, - allowSsr: true, - }), - }, - ); + await resolveRequestRuntime(baseConfig, new Request("https://alice.linktree.com/"), { + bindingResolver: createMockBindingResolver({ + hostname: "alice.linktree.com", + allowUiOverrides: true, + allowSsr: true, + }), + }); const refresh = createDeferred(); verifySriForUrlMock.mockImplementationOnce(() => refresh.promise); @@ -762,17 +757,13 @@ describe("resolveRequestRuntime", () => { }, }); - await resolveRequestRuntime( - baseConfig, - new Request("https://alice.linktree.com/"), - { - bindingResolver: createMockBindingResolver({ - hostname: "alice.linktree.com", - allowUiOverrides: true, - allowSsr: true, - }), - }, - ); + await resolveRequestRuntime(baseConfig, new Request("https://alice.linktree.com/"), { + bindingResolver: createMockBindingResolver({ + hostname: "alice.linktree.com", + allowUiOverrides: true, + allowSsr: true, + }), + }); const refresh = createDeferred(); verifySriForUrlMock.mockImplementationOnce(() => refresh.promise); diff --git a/package.json b/package.json index 073c58bc..1f52d071 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "test:api": "cd api && bun run test tests/integration/ tests/unit/", "test:integration": "cd api && bun run test tests/integration/", "test:e2e": "bun run --cwd host test:e2e", - "regression:start:dev": "BOS_NO_PERSIST_PORTS=1 bun packages/everything-dev/src/cli.ts dev --no-interactive --port 4100 --api-port 4101 --auth-port 4102 --ui-port 4103 --plugin-port-start 4110", + "regression:start:dev": "BOS_NO_PERSIST_PORTS=1 bun packages/everything-dev/src/cli.ts dev --no-interactive --ssr --port 4100 --api-port 4101 --auth-port 4102 --ui-port 4103 --plugin-port-start 4110", "regression:start:prod": "BOS_NO_PERSIST_PORTS=1 PORT=4100 bun ./node_modules/everything-dev/dist/cli.mjs start --no-interactive", "test:regression:http:dev": "REGRESSION_MODE=dev sh -lc 'cd tests/regression/http && go test ./... -count=1 -v -timeout 10m'", "test:regression:http:prod": "REGRESSION_MODE=prod sh -lc 'cd tests/regression/http && go test ./... -count=1 -v -timeout 10m'", diff --git a/packages/everything-dev/skills/api-and-auth/SKILL.md b/packages/everything-dev/skills/api-and-auth/SKILL.md index 06416252..e302ff66 100644 --- a/packages/everything-dev/skills/api-and-auth/SKILL.md +++ b/packages/everything-dev/skills/api-and-auth/SKILL.md @@ -356,6 +356,54 @@ export function createPluginsClient(result, context) { } ``` +### Tenant-Scoped Data + +A **tenant** is a deployment record (subdomain + NEAR account + UI/backend/SSR override +permissions) — distinct from an organization (a group of users) and from a user's own data. +If your plugin stores data that belongs to a specific tenant deployment (not just a user or +org), resolve the tenant and scope every query to it. + +The `api` plugin owns the `tenants` table and exposes public lookup routes (no auth required — +tenant lookups are read-only and safe to expose): + +```ts +// From any plugin, via pluginsClient.api (injected through withPlugins()) +const tenant = context.organization?.activeOrganizationId + ? await plugins.api().resolveTenantByOrgId({ orgId: context.organization.activeOrganizationId }) + : await plugins.api().resolveTenant({ accountId: context.near?.primaryAccountId ?? "" }); +``` + +Build a local `requireTenant` middleware in your own plugin (mirrors `requireOrganization`): + +```ts +const requireTenant = builder.middleware(async ({ context, next }) => { + const activeOrgId = context.organization?.activeOrganizationId; + const tenant = activeOrgId + ? await plugins.api().resolveTenantByOrgId({ orgId: activeOrgId }).catch(() => null) + : null; + if (!tenant) { + throw new ORPCError("FORBIDDEN", { message: "No tenant found for this organization" }); + } + return next({ context: { ...context, tenant } }); +}); + +builder.listReports.use(requireTenant).handler(async ({ context }) => { + return await services.reports.listByTenant(context.tenant.id); // always filtered +}); +``` + +**Row-level convention (interim isolation)**: add a `tenantId` column to any table holding +tenant-specific application data, resolved server-side and never trusted from client input — +same discipline the `tenants` table itself uses for `orgId` scoping. See +`plugins/_template/src/db/schema.ts` for a commented example table. + +**Forward path**: the target architecture (see `plans/beta-v2-tenants.md`) is per-tenant-per-plugin +Postgres schema isolation (`tenant__plugin_`, `search_path` injected per request). That +requires request-scoped DB access instead of the current initialize-time singleton pattern — a +larger change, only worth it once there's a real multi-tenant plugin ecosystem to isolate. The +`tenantId` column convention above is forward-compatible: when schema isolation lands, the column +becomes redundant and can be dropped without reworking query logic. + ## Generated Types See `references/generated-types.md` for the full table — files, contents, and regeneration triggers. diff --git a/packages/everything-dev/src/infra/planner.ts b/packages/everything-dev/src/infra/planner.ts index 2efb3939..c0dacc98 100644 --- a/packages/everything-dev/src/infra/planner.ts +++ b/packages/everything-dev/src/infra/planner.ts @@ -453,6 +453,9 @@ export function planInfra(input: InfraInput): Effect.Effect { const scriptMap = new Map(); for (const match of router.state.matches) { - const route = - ( - router as AnyRouter & { - routesById?: Record unknown } }>; - } - ).routesById?.[(match as { routeId: string }).routeId] ?? - (match as { route?: { options?: { head?: (...args: unknown[]) => unknown } } }).route; - const headFn = route?.options?.head; - if (!headFn) continue; + const matchData = match as { + meta?: HeadMeta[]; + links?: HeadLink[]; + headScripts?: HeadScript[]; + }; - try { - const headResult = (await headFn({ - loaderData: match.loaderData, - matches: router.state.matches, - match, - params: match.params, - })) as { - meta?: HeadMeta[]; - links?: HeadLink[]; - scripts?: HeadScript[]; - }; - - if (headResult?.meta) { - for (const meta of headResult.meta) { - metaMap.set(getMetaKey(meta), meta); - } + if (matchData.meta) { + for (const meta of matchData.meta) { + metaMap.set(getMetaKey(meta), meta); } - if (headResult?.links) { - for (const link of headResult.links) { - linkMap.set(getLinkKey(link), link); - } + } + if (matchData.links) { + for (const link of matchData.links) { + linkMap.set(getLinkKey(link), link); } - if (headResult?.scripts) { - for (const script of headResult.scripts) { - scriptMap.set(getScriptKey(script), script); - } + } + if (matchData.headScripts) { + for (const script of matchData.headScripts) { + scriptMap.set(getScriptKey(script), script); } - } catch (error) { - console.warn(`[collectHeadData] head() failed for ${match.routeId}:`, error); } } diff --git a/plugins/_template/src/db/schema.ts b/plugins/_template/src/db/schema.ts index b355bb35..51574d0f 100644 --- a/plugins/_template/src/db/schema.ts +++ b/plugins/_template/src/db/schema.ts @@ -14,3 +14,21 @@ export const things = pgTable( }, (table) => [index("things_type_idx").on(table.type)], ); + +// Tenant-scoped table convention (interim, row-level isolation): +// +// If your plugin stores data that belongs to a specific tenant deployment +// (not just a user or org), add a `tenantId` column and filter every query +// by it. Resolve the tenant via the API plugin's public lookup routes +// (`pluginsClient.api.resolveTenantByOrgId` / `resolveTenant`) and never +// trust a tenantId passed directly from client input. +// +// export const reports = pgTable("reports", { +// id: uuid("id").defaultRandom().primaryKey(), +// tenantId: text("tenant_id").notNull(), // resolved server-side, always filtered +// title: text("title").notNull(), +// createdAt: timestamp("created_at", { mode: "date", withTimezone: true }).defaultNow().notNull(), +// }, (table) => [index("reports_tenant_id_idx").on(table.tenantId)]); +// +// See the api-and-auth skill's "Tenant-Scoped Data" section for the full +// middleware pattern and the forward path to per-tenant schema isolation. diff --git a/tests/regression/browser/helpers/page-ready.ts b/tests/regression/browser/helpers/page-ready.ts index f508dec3..dab7f790 100644 --- a/tests/regression/browser/helpers/page-ready.ts +++ b/tests/regression/browser/helpers/page-ready.ts @@ -33,6 +33,24 @@ export async function waitForApp(page: Page): Promise { return typeof window.__RUNTIME_CONFIG__ !== "undefined"; }); expect(hasRuntimeConfig).toBeTruthy(); + + try { + await page.waitForFunction( + () => { + const p = (window as { __EVERYTHING_DEV_HYDRATE_PROMISE__?: Promise }) + .__EVERYTHING_DEV_HYDRATE_PROMISE__; + return p !== undefined; + }, + { timeout: 5000 }, + ); + await page.evaluate(async () => { + const p = (window as { __EVERYTHING_DEV_HYDRATE_PROMISE__?: Promise }) + .__EVERYTHING_DEV_HYDRATE_PROMISE__; + if (p) await p; + }); + } catch { + // Hydration promise never surfaced (e.g. client-shell fallback path) — proceed. + } } export function expectNoHydrationFailure(errors: PageErrors) { diff --git a/tests/regression/browser/helpers/seeded.ts b/tests/regression/browser/helpers/seeded.ts index 26c1efce..560e0aad 100644 --- a/tests/regression/browser/helpers/seeded.ts +++ b/tests/regression/browser/helpers/seeded.ts @@ -45,5 +45,5 @@ export async function verifyAuthenticated(page: Page) { await page.goto("/home", { waitUntil: "domcontentloaded" }); await page.waitForTimeout(500); await page.waitForLoadState("networkidle"); - await expect(page.locator("button[title='menu']")).toBeVisible({ timeout: 10000 }); + await expect(page.locator("button[title='account menu']")).toBeVisible({ timeout: 10000 }); } diff --git a/tests/regression/browser/specs/auth-redirect.spec.ts b/tests/regression/browser/specs/auth-redirect.spec.ts index 94a79c21..f0b3aede 100644 --- a/tests/regression/browser/specs/auth-redirect.spec.ts +++ b/tests/regression/browser/specs/auth-redirect.spec.ts @@ -26,8 +26,8 @@ test.describe("Auth redirect", () => { expectNoHydrationFailure(pageErrors); }); - test("unauthenticated / redirects to /login", async ({ page }) => { - await page.goto("/", { waitUntil: "domcontentloaded" }); + test("unauthenticated /home redirects to /login", async ({ page }) => { + await page.goto("/home", { waitUntil: "domcontentloaded" }); await waitForApp(page); await page.waitForURL(/\/login/, { timeout: 15000 }); diff --git a/tests/regression/browser/specs/logout.spec.ts b/tests/regression/browser/specs/logout.spec.ts index c207bb95..cef410e9 100644 --- a/tests/regression/browser/specs/logout.spec.ts +++ b/tests/regression/browser/specs/logout.spec.ts @@ -8,7 +8,7 @@ test.describe("logout", () => { pageErrors = collectErrors(page); }); - test("sign out redirects to login and session is cleared", async ({ page }) => { + test("sign out lands on public page and session is cleared", async ({ page }) => { await page.goto("/login", { waitUntil: "domcontentloaded" }); await waitForApp(page); @@ -28,18 +28,20 @@ test.describe("logout", () => { await page.waitForURL(/\/home$/, { timeout: 15000 }); await page.waitForLoadState("networkidle"); await waitForApp(page); - await page.locator("button[title='menu']").click(); + await page.locator("button[title='account menu']").click(); const signOutItem = page.getByRole("menuitem", { name: "sign out" }); await expect(signOutItem).toBeVisible({ timeout: 5000 }); await signOutItem.click(); - await page.waitForURL(/\/login/, { timeout: 15000 }); - await expect(page.getByText("continue anonymously")).toBeVisible({ timeout: 10000 }); + await page.waitForURL(/\/$/, { timeout: 15000 }); + await expect(page.getByText("Get started")).toBeVisible({ timeout: 10000 }); + await expect(page.locator("button[title='account menu']")).toHaveCount(0); await page.reload({ waitUntil: "domcontentloaded" }); await waitForApp(page); - await expect(page.getByText("continue anonymously")).toBeVisible({ timeout: 10000 }); + await expect(page.getByText("Get started")).toBeVisible({ timeout: 10000 }); + await expect(page.locator("button[title='account menu']")).toHaveCount(0); expectNoHydrationFailure(pageErrors); }); diff --git a/tests/regression/browser/specs/theme.spec.ts b/tests/regression/browser/specs/theme.spec.ts index c8bd569a..0b9a3db8 100644 --- a/tests/regression/browser/specs/theme.spec.ts +++ b/tests/regression/browser/specs/theme.spec.ts @@ -49,19 +49,8 @@ test.describe("theme", () => { await expect(html).toHaveClass(/dark/); - const darkToggle = page.locator("button[aria-label='Switch to light theme']").first(); - await expect(darkToggle).toBeVisible({ timeout: 10000 }); - await darkToggle.click(); - - await expect(html).not.toHaveClass(/dark/); - - const storedAfterLight = await page.evaluate(() => localStorage.getItem("theme")); - expect(storedAfterLight).toBe("light"); - - await page.reload({ waitUntil: "domcontentloaded" }); - await waitForApp(page); - - await expect(html).not.toHaveClass(/dark/); + const storedAfterReload = await page.evaluate(() => localStorage.getItem("theme")); + expect(storedAfterReload).toBe("dark"); expectNoHydrationFailure(pageErrors); }); diff --git a/tests/regression/http/metadata_test.go b/tests/regression/http/metadata_test.go index 856cea48..7382a275 100644 --- a/tests/regression/http/metadata_test.go +++ b/tests/regression/http/metadata_test.go @@ -1,6 +1,7 @@ package regression import ( + "strings" "testing" "everything.dev/regression/http/internal/regtest" @@ -33,3 +34,30 @@ func TestRouteMetadata(t *testing.T) { t.Fatal("route HTML missing tag") } } + +func TestProfileMetadata(t *testing.T) { + const accountID = "root.near" + + client := regtest.NewCookieClient() + status, _, body := regtest.GetRaw(t, client, baseURL+"/"+accountID) + regtest.MustStatus(t, status, 200, body) + + if !regtest.HTMLContainsTitle(body) { + t.Fatal("profile HTML missing <title> tag") + } + if !strings.Contains(body, accountID) { + t.Fatalf("profile HTML missing account id %q", accountID) + } + + for _, property := range []string{"og:title", "og:description", "og:type"} { + if !regtest.HTMLContainsMetaProperty(body, property) { + t.Fatalf("profile HTML missing %s meta property", property) + } + } + + for _, name := range []string{"twitter:card", "twitter:title", "twitter:description"} { + if !regtest.HTMLContainsMetaName(body, name) { + t.Fatalf("profile HTML missing %s meta name", name) + } + } +} diff --git a/ui/rsbuild.config.ts b/ui/rsbuild.config.ts index 8a37905c..b829ffe5 100644 --- a/ui/rsbuild.config.ts +++ b/ui/rsbuild.config.ts @@ -260,7 +260,7 @@ function createServerConfig() { }, }, server: { - port: 3004, + port: Number(process.env.PORT) || 3004, printUrls: ({ urls }) => urls.filter((url) => url.includes("localhost")), headers: { "Access-Control-Allow-Origin": "*", diff --git a/ui/src/components/index.ts b/ui/src/components/index.ts index 476a1331..f7bcfd5c 100644 --- a/ui/src/components/index.ts +++ b/ui/src/components/index.ts @@ -9,6 +9,7 @@ export { ConfirmDialog } from "./confirm-dialog"; export { EmptyState } from "./empty-state"; export { PageContainer } from "./layout/page-container"; export { OrgSwitcher } from "./org-switcher"; +export { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar"; export { Badge } from "./ui/badge"; export { Button } from "./ui/button"; export { diff --git a/ui/src/components/user-nav.tsx b/ui/src/components/user-nav.tsx index a77e1c08..5929d4c4 100644 --- a/ui/src/components/user-nav.tsx +++ b/ui/src/components/user-nav.tsx @@ -1,17 +1,18 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { Link, useNavigate, useRouter } from "@tanstack/react-router"; +import { Building2, Home, LogOut, Settings, User } from "lucide-react"; import { useMemo } from "react"; import type { Organization } from "@/app"; import { sessionQueryOptions, useAuthClient } from "@/app"; -import { OrgSwitcher } from "@/components"; +import { Avatar, AvatarFallback, AvatarImage, OrgSwitcher } from "@/components"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, - DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { getNearInitials, resolveNearImageUrl } from "@/lib/near-profile"; export function UserNav() { const auth = useAuthClient(); @@ -20,6 +21,8 @@ export function UserNav() { const router = useRouter(); const { data: session } = useQuery(sessionQueryOptions(auth)); const user = session?.user; + const nearAccountId = auth.near.getAccountId(); + const { data: organizations } = useQuery({ queryKey: ["organizations"], queryFn: async () => { @@ -35,6 +38,16 @@ export function UserNav() { return organizations?.find((org) => org.id === activeOrgId); }, [organizations, activeOrgId]); + const { data: nearProfile } = useQuery({ + queryKey: ["near-profile", nearAccountId], + queryFn: async () => { + const { data } = await auth.near.getProfile(nearAccountId ?? undefined); + return data ?? null; + }, + enabled: !!nearAccountId, + staleTime: 5 * 60 * 1000, + }); + const signOutMutation = useMutation({ mutationFn: async () => { const { error } = await auth.signOut(); @@ -71,6 +84,28 @@ export function UserNav() { await queryClient.invalidateQueries({ queryKey: ["organizations"] }); }; + const avatarSrc = resolveNearImageUrl(nearProfile?.image) ?? user.image ?? undefined; + const validEmail = !user.isAnonymous && user.email ? user.email : null; + const displayName = nearProfile?.name || user.name || nearAccountId || validEmail || "guest"; + const handle = nearAccountId || validEmail || "anonymous session"; + const showHandle = handle !== displayName; + const initials = getNearInitials(nearProfile?.name || user.name || nearAccountId); + + const identityContent = ( + <> + <Avatar className="size-9 shrink-0 ring-1 ring-border"> + {avatarSrc ? <AvatarImage src={avatarSrc} alt="" /> : null} + <AvatarFallback className="text-xs font-semibold"> + {initials || <User className="size-4" />} + </AvatarFallback> + </Avatar> + <div className="min-w-0 flex-1"> + <p className="truncate text-sm font-medium text-foreground">{displayName}</p> + {showHandle && <p className="truncate text-xs text-muted-foreground">{handle}</p>} + </div> + </> + ); + return ( <div className="flex items-center gap-2"> {organizations && organizations.length > 0 && ( @@ -85,30 +120,48 @@ export function UserNav() { <DropdownMenuTrigger asChild> <button type="button" - className="w-6 h-6 rounded-full! bg-foreground transition-all duration-200 ease-out hover:shadow-lg hover:scale-110 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2" - title="menu" - /> + aria-label={displayName} + className="rounded-full! ring-1 ring-border transition-transform duration-200 ease-out focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 hover:scale-105" + title="account menu" + > + <Avatar className="size-8"> + {avatarSrc ? <AvatarImage src={avatarSrc} alt="" /> : null} + <AvatarFallback className="text-xs font-semibold"> + {initials || <User className="size-4" />} + </AvatarFallback> + </Avatar> + </button> </DropdownMenuTrigger> - <DropdownMenuContent align="end" className="w-56"> - <DropdownMenuLabel> - <div className="space-y-1"> - <p className="text-xs text-muted-foreground">signed in as</p> - <p className="truncate text-sm font-normal">{user.email || user.id}</p> - </div> - </DropdownMenuLabel> + <DropdownMenuContent align="end" className="w-64"> + <DropdownMenuItem asChild> + {nearAccountId ? ( + <Link to="/$accountId" params={{ accountId: nearAccountId }}> + {identityContent} + </Link> + ) : ( + <Link to="/settings/profile">{identityContent}</Link> + )} + </DropdownMenuItem> <DropdownMenuSeparator /> <DropdownMenuItem asChild> - <Link to="/home">workspace</Link> + <Link to="/home"> + <Home /> + workspace + </Link> </DropdownMenuItem> {activeOrg && ( <DropdownMenuItem asChild> <Link to="/organizations/$slug" params={{ slug: activeOrg.slug }}> + <Building2 /> {activeOrg.name} </Link> </DropdownMenuItem> )} <DropdownMenuItem asChild> - <Link to="/settings">settings</Link> + <Link to="/settings"> + <Settings /> + settings + </Link> </DropdownMenuItem> <DropdownMenuSeparator /> <DropdownMenuItem @@ -119,6 +172,7 @@ export function UserNav() { }} disabled={signOutMutation.isPending} > + <LogOut /> {signOutMutation.isPending ? "signing out..." : "sign out"} </DropdownMenuItem> </DropdownMenuContent> diff --git a/ui/src/lib/near-profile.ts b/ui/src/lib/near-profile.ts new file mode 100644 index 00000000..deb01fa8 --- /dev/null +++ b/ui/src/lib/near-profile.ts @@ -0,0 +1,17 @@ +export interface NearProfileImage { + url?: string; + ipfs_cid?: string; +} + +export function resolveNearImageUrl(image?: NearProfileImage | null): string | undefined { + if (image?.url) return image.url; + if (image?.ipfs_cid) return `https://ipfs.near.social/ipfs/${image.ipfs_cid}`; + return undefined; +} + +export function getNearInitials(name?: string | null): string { + if (!name) return ""; + const parts = name.trim().split(/\s+/).filter(Boolean); + if (parts.length >= 2) return `${parts[0][0]}${parts[1][0]}`.toUpperCase(); + return name.trim().slice(0, 2).toUpperCase(); +} diff --git a/ui/src/routeTree.gen.ts b/ui/src/routeTree.gen.ts index c7659df3..7bb4ab92 100644 --- a/ui/src/routeTree.gen.ts +++ b/ui/src/routeTree.gen.ts @@ -17,8 +17,10 @@ import { Route as LayoutAppsIndexRouteImport } from './routes/_layout/apps/index import { Route as LayoutPublicIndexRouteImport } from './routes/_layout/_public/index' import { Route as LayoutThingsLiveRouteImport } from './routes/_layout/things/live' import { Route as LayoutThingsThingIdRouteImport } from './routes/_layout/things/$thingId' +import { Route as LayoutPublicSkillRouteImport } from './routes/_layout/_public/skill' import { Route as LayoutPublicLoginRouteImport } from './routes/_layout/_public/login' import { Route as LayoutPublicAboutRouteImport } from './routes/_layout/_public/about' +import { Route as LayoutPublicAccountIdRouteImport } from './routes/_layout/_public/$accountId' import { Route as LayoutAuthenticatedSettingsRouteImport } from './routes/_layout/_authenticated/settings' import { Route as LayoutAuthenticatedHomeRouteImport } from './routes/_layout/_authenticated/home' import { Route as LayoutAuthenticatedAdminRouteImport } from './routes/_layout/_authenticated/admin' @@ -75,6 +77,11 @@ const LayoutThingsThingIdRoute = LayoutThingsThingIdRouteImport.update({ path: '/things/$thingId', getParentRoute: () => LayoutRoute, } as any) +const LayoutPublicSkillRoute = LayoutPublicSkillRouteImport.update({ + id: '/skill', + path: '/skill', + getParentRoute: () => LayoutPublicRoute, +} as any) const LayoutPublicLoginRoute = LayoutPublicLoginRouteImport.update({ id: '/login', path: '/login', @@ -85,6 +92,11 @@ const LayoutPublicAboutRoute = LayoutPublicAboutRouteImport.update({ path: '/about', getParentRoute: () => LayoutPublicRoute, } as any) +const LayoutPublicAccountIdRoute = LayoutPublicAccountIdRouteImport.update({ + id: '/$accountId', + path: '/$accountId', + getParentRoute: () => LayoutPublicRoute, +} as any) const LayoutAuthenticatedSettingsRoute = LayoutAuthenticatedSettingsRouteImport.update({ id: '/settings', @@ -198,8 +210,10 @@ export interface FileRoutesByFullPath { '/admin': typeof LayoutAuthenticatedAdminRouteWithChildren '/home': typeof LayoutAuthenticatedHomeRoute '/settings': typeof LayoutAuthenticatedSettingsRouteWithChildren + '/$accountId': typeof LayoutPublicAccountIdRoute '/about': typeof LayoutPublicAboutRoute '/login': typeof LayoutPublicLoginRoute + '/skill': typeof LayoutPublicSkillRoute '/things/$thingId': typeof LayoutThingsThingIdRoute '/things/live': typeof LayoutThingsLiveRoute '/apps/': typeof LayoutAppsIndexRoute @@ -223,8 +237,10 @@ export interface FileRoutesByFullPath { export interface FileRoutesByTo { '/': typeof LayoutPublicIndexRoute '/home': typeof LayoutAuthenticatedHomeRoute + '/$accountId': typeof LayoutPublicAccountIdRoute '/about': typeof LayoutPublicAboutRoute '/login': typeof LayoutPublicLoginRoute + '/skill': typeof LayoutPublicSkillRoute '/things/$thingId': typeof LayoutThingsThingIdRoute '/things/live': typeof LayoutThingsLiveRoute '/apps': typeof LayoutAppsIndexRoute @@ -253,8 +269,10 @@ export interface FileRoutesById { '/_layout/_authenticated/admin': typeof LayoutAuthenticatedAdminRouteWithChildren '/_layout/_authenticated/home': typeof LayoutAuthenticatedHomeRoute '/_layout/_authenticated/settings': typeof LayoutAuthenticatedSettingsRouteWithChildren + '/_layout/_public/$accountId': typeof LayoutPublicAccountIdRoute '/_layout/_public/about': typeof LayoutPublicAboutRoute '/_layout/_public/login': typeof LayoutPublicLoginRoute + '/_layout/_public/skill': typeof LayoutPublicSkillRoute '/_layout/things/$thingId': typeof LayoutThingsThingIdRoute '/_layout/things/live': typeof LayoutThingsLiveRoute '/_layout/_public/': typeof LayoutPublicIndexRoute @@ -283,8 +301,10 @@ export interface FileRouteTypes { | '/admin' | '/home' | '/settings' + | '/$accountId' | '/about' | '/login' + | '/skill' | '/things/$thingId' | '/things/live' | '/apps/' @@ -308,8 +328,10 @@ export interface FileRouteTypes { to: | '/' | '/home' + | '/$accountId' | '/about' | '/login' + | '/skill' | '/things/$thingId' | '/things/live' | '/apps' @@ -337,8 +359,10 @@ export interface FileRouteTypes { | '/_layout/_authenticated/admin' | '/_layout/_authenticated/home' | '/_layout/_authenticated/settings' + | '/_layout/_public/$accountId' | '/_layout/_public/about' | '/_layout/_public/login' + | '/_layout/_public/skill' | '/_layout/things/$thingId' | '/_layout/things/live' | '/_layout/_public/' @@ -423,6 +447,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutThingsThingIdRouteImport parentRoute: typeof LayoutRoute } + '/_layout/_public/skill': { + id: '/_layout/_public/skill' + path: '/skill' + fullPath: '/skill' + preLoaderRoute: typeof LayoutPublicSkillRouteImport + parentRoute: typeof LayoutPublicRoute + } '/_layout/_public/login': { id: '/_layout/_public/login' path: '/login' @@ -437,6 +468,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutPublicAboutRouteImport parentRoute: typeof LayoutPublicRoute } + '/_layout/_public/$accountId': { + id: '/_layout/_public/$accountId' + path: '/$accountId' + fullPath: '/$accountId' + preLoaderRoute: typeof LayoutPublicAccountIdRouteImport + parentRoute: typeof LayoutPublicRoute + } '/_layout/_authenticated/settings': { id: '/_layout/_authenticated/settings' path: '/settings' @@ -642,14 +680,18 @@ const LayoutAuthenticatedRouteWithChildren = LayoutAuthenticatedRoute._addFileChildren(LayoutAuthenticatedRouteChildren) interface LayoutPublicRouteChildren { + LayoutPublicAccountIdRoute: typeof LayoutPublicAccountIdRoute LayoutPublicAboutRoute: typeof LayoutPublicAboutRoute LayoutPublicLoginRoute: typeof LayoutPublicLoginRoute + LayoutPublicSkillRoute: typeof LayoutPublicSkillRoute LayoutPublicIndexRoute: typeof LayoutPublicIndexRoute } const LayoutPublicRouteChildren: LayoutPublicRouteChildren = { + LayoutPublicAccountIdRoute: LayoutPublicAccountIdRoute, LayoutPublicAboutRoute: LayoutPublicAboutRoute, LayoutPublicLoginRoute: LayoutPublicLoginRoute, + LayoutPublicSkillRoute: LayoutPublicSkillRoute, LayoutPublicIndexRoute: LayoutPublicIndexRoute, } diff --git a/ui/src/routes/__root.tsx b/ui/src/routes/__root.tsx index 20446243..ea983de9 100644 --- a/ui/src/routes/__root.tsx +++ b/ui/src/routes/__root.tsx @@ -16,7 +16,7 @@ import { Scripts, } from "@tanstack/react-router"; import { TanStackRouterDevtoolsPanel } from "@tanstack/react-router-devtools"; -import { getRemoteScripts } from "everything-dev/ui/head"; +import { getRemoteScripts, getThemeInitScript } from "everything-dev/ui/head"; import { getSocialImageMeta } from "everything-dev/ui/metadata"; import { ThemeProvider } from "next-themes"; import type { RouterContext } from "@/app"; @@ -123,6 +123,7 @@ export const Route = createRootRouteWithContext<RouterContext>()({ ...(typeof window === "undefined" ? [{ children: "window.__EVERYTHING_DEV_SSR__=true" }] : []), + getThemeInitScript(), ...getRemoteScripts({ runtimeConfig: runtimeConfig ?? undefined, containerName: "ui", diff --git a/ui/src/routes/_layout.tsx b/ui/src/routes/_layout.tsx index c0a44b0a..3f68e524 100644 --- a/ui/src/routes/_layout.tsx +++ b/ui/src/routes/_layout.tsx @@ -10,6 +10,9 @@ export const Route = createFileRoute("/_layout")({ function Layout() { const isNavigating = useRouterState({ select: (s) => s.status === "pending" }); + const [mounted, setMounted] = useState(false); + useEffect(() => setMounted(true), []); + const [betaBannerDismissed, setBetaBannerDismissed] = useState(() => { if (typeof window === "undefined") return false; return localStorage.getItem("beta-banner-dismissed") === "true"; @@ -45,7 +48,7 @@ function Layout() { </div> )} - {isNavigating && ( + {mounted && isNavigating && ( <div className="fixed top-0 left-0 right-0 h-[2px] z-50 overflow-hidden pointer-events-none"> <div className="h-full bg-foreground animate-progress-bar" style={{ width: "100%" }} /> </div> diff --git a/ui/src/routes/_layout/_authenticated/home.tsx b/ui/src/routes/_layout/_authenticated/home.tsx index e28c6796..1fccff0e 100644 --- a/ui/src/routes/_layout/_authenticated/home.tsx +++ b/ui/src/routes/_layout/_authenticated/home.tsx @@ -2,12 +2,29 @@ import { useQuery } from "@tanstack/react-query"; import { createFileRoute, Link } from "@tanstack/react-router"; import { Home as HomeIcon, Settings } from "lucide-react"; import { useMemo } from "react"; -import { type Passkey, type SessionData, sessionQueryOptions, useAuthClient } from "@/app"; +import { + getAccount, + type Passkey, + type SessionData, + sessionQueryOptions, + useAuthClient, +} from "@/app"; import { Card } from "@/components"; import { PageContainer } from "@/components/layout/page-container"; import { InfoRow } from "@/components/ui/info-row"; export const Route = createFileRoute("/_layout/_authenticated/home")({ + beforeLoad: async ({ context }) => { + const { apiClient, runtimeConfig } = context; + const accountId = getAccount(runtimeConfig); + let tenant: Awaited<ReturnType<typeof apiClient.resolveTenant>> | null = null; + try { + tenant = await apiClient.resolveTenant({ accountId }); + } catch { + tenant = null; + } + return { tenant }; + }, head: () => ({ meta: [{ title: "Workspace | app" }, { name: "description", content: "Your workspace." }], }), diff --git a/ui/src/routes/_layout/_public/$accountId.tsx b/ui/src/routes/_layout/_public/$accountId.tsx new file mode 100644 index 00000000..ff1d96e1 --- /dev/null +++ b/ui/src/routes/_layout/_public/$accountId.tsx @@ -0,0 +1,197 @@ +import { useQuery } from "@tanstack/react-query"; +import { createFileRoute, Link } from "@tanstack/react-router"; +import { getSocialImageMeta } from "everything-dev/ui/metadata"; +import { ExternalLink, Globe, User } from "lucide-react"; +import { useApiClient, useAuthClient } from "@/app"; +import { Avatar, AvatarFallback, AvatarImage, Badge, PageContainer } from "@/components"; +import { getNearInitials, resolveNearImageUrl } from "@/lib/near-profile"; + +export const Route = createFileRoute("/_layout/_public/$accountId")({ + loader: async ({ params, context }) => { + const { queryClient, authClient, apiClient, runtimeConfig } = context; + const accountId = params.accountId; + + await Promise.all([ + queryClient.prefetchQuery({ + queryKey: ["near-profile", accountId], + queryFn: async () => { + const { data } = await authClient.near.getProfile(accountId); + return data ?? null; + }, + staleTime: 5 * 60 * 1000, + }), + queryClient.prefetchQuery({ + queryKey: ["apps-account", accountId], + queryFn: () => apiClient.apps.getRegistryAppsByAccount({ accountId }), + staleTime: 30_000, + }), + ]); + + return { accountId, hostUrl: runtimeConfig?.hostUrl ?? "" }; + }, + head: ({ loaderData, params }) => { + const accountId = params.accountId; + const hostUrl = (loaderData?.hostUrl ?? "").replace(/\/$/, ""); + const siteUrl = hostUrl ? `${hostUrl}/${accountId}` : ""; + const title = `${accountId} | everything.dev`; + const description = `${accountId}'s public profile on everything.dev.`; + + return { + meta: [ + { title }, + { name: "description", content: description }, + ...getSocialImageMeta({ + imageUrl: hostUrl ? `${hostUrl}/metadata.png` : "/metadata.png", + title, + description, + siteName: "everything.dev", + siteUrl, + type: "profile", + alt: description, + }), + ], + }; + }, + component: AccountProfilePage, +}); + +function AccountProfilePage() { + const { accountId } = Route.useLoaderData(); + const authClient = useAuthClient(); + const apiClient = useApiClient(); + + const { data: profile } = useQuery({ + queryKey: ["near-profile", accountId], + queryFn: async () => { + const { data } = await authClient.near.getProfile(accountId); + return data ?? null; + }, + staleTime: 5 * 60 * 1000, + }); + + const { data: appsData } = useQuery({ + queryKey: ["apps-account", accountId], + queryFn: () => apiClient.apps.getRegistryAppsByAccount({ accountId }), + staleTime: 30_000, + }); + + const apps = appsData?.data ?? []; + const backgroundUrl = resolveNearImageUrl(profile?.backgroundImage); + const avatarUrl = resolveNearImageUrl(profile?.image); + const displayName = profile?.name || accountId; + const initials = getNearInitials(profile?.name || accountId); + const linktree = profile?.linktree ? Object.entries(profile.linktree) : []; + + return ( + <PageContainer variant="default"> + <div className="space-y-6"> + <div className="overflow-hidden rounded-[12px] border border-border bg-card"> + <div + className="h-32 sm:h-44 w-full bg-muted" + style={ + backgroundUrl + ? { + backgroundImage: `url(${backgroundUrl})`, + backgroundSize: "cover", + backgroundPosition: "center", + } + : undefined + } + /> + <div className="px-6 pb-6"> + <Avatar className="-mt-10 size-20 border-4 border-card ring-1 ring-border bg-card"> + {avatarUrl ? <AvatarImage src={avatarUrl} alt="" /> : null} + <AvatarFallback className="text-xl font-semibold"> + {initials || <User className="size-8" />} + </AvatarFallback> + </Avatar> + + <div className="mt-3 space-y-1"> + <h1 className="text-xl font-bold text-foreground">{displayName}</h1> + <p className="font-mono text-sm text-muted-foreground">{accountId}</p> + </div> + + {profile?.description && ( + <p className="mt-3 max-w-2xl text-sm leading-relaxed text-muted-foreground"> + {profile.description} + </p> + )} + + {linktree.length > 0 && ( + <div className="mt-4 flex flex-wrap gap-2"> + {linktree.map(([label, url]) => ( + <a + key={label} + href={url} + target="_blank" + rel="noopener noreferrer" + className="inline-flex items-center gap-1.5 rounded-full border border-border bg-muted px-3 py-1 text-xs font-medium text-foreground transition-colors hover:bg-border" + > + <Globe className="size-3" /> + {label} + </a> + ))} + </div> + )} + </div> + </div> + + <div className="space-y-3"> + <div className="flex items-center justify-between"> + <h2 className="text-[11px] font-bold uppercase tracking-wider text-muted-foreground"> + Published Gateways + </h2> + {apps.length > 0 && ( + <Link + to="/apps/$accountId" + params={{ accountId }} + className="text-xs font-medium text-muted-foreground hover:text-foreground transition-colors" + > + view all + </Link> + )} + </div> + + {apps.length === 0 ? ( + <div className="rounded-[12px] border border-border bg-card px-6 py-10 text-center text-sm text-muted-foreground"> + No published gateways for{" "} + <span className="font-mono text-foreground">{accountId}</span>. + </div> + ) : ( + <div className="divide-y divide-border overflow-hidden rounded-[12px] border border-border bg-card"> + {apps.slice(0, 5).map((app) => ( + <Link + key={app.gatewayId} + to="/apps/$accountId/$gatewayId" + params={{ accountId, gatewayId: app.gatewayId }} + className="flex items-center justify-between gap-4 px-4 py-3 transition-colors hover:bg-muted/40" + > + <div className="min-w-0 space-y-0.5"> + <div className="flex items-center gap-1.5"> + <span + className={`inline-block h-1.5 w-1.5 shrink-0 rounded-full ${ + app.status === "ready" ? "bg-green-500" : "bg-destructive" + }`} + /> + <span className="truncate font-mono text-sm font-semibold text-foreground"> + {app.metadata?.title ?? app.gatewayId} + </span> + </div> + {app.domain && ( + <Badge variant="secondary" className="font-mono text-[10px]"> + {app.domain} + </Badge> + )} + </div> + {app.openUrl && ( + <ExternalLink className="size-3.5 shrink-0 text-muted-foreground" /> + )} + </Link> + ))} + </div> + )} + </div> + </div> + </PageContainer> + ); +} diff --git a/ui/src/routes/_layout/_public/skill.tsx b/ui/src/routes/_layout/_public/skill.tsx new file mode 100644 index 00000000..d2f83c68 --- /dev/null +++ b/ui/src/routes/_layout/_public/skill.tsx @@ -0,0 +1,126 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { Check, Copy, ExternalLink, FileText } from "lucide-react"; +import { useState } from "react"; +import { toast } from "sonner"; +import { getAccount, getActiveRuntime, getAppName } from "@/app"; +import { PageContainer } from "@/components/layout/page-container"; +import { Button } from "@/components/ui/button"; +import { Markdown } from "@/components/ui/markdown"; + +const INTENT_REGISTRY_URL = "https://tanstack.com/intent/registry/everything-dev"; + +export const Route = createFileRoute("/_layout/_public/skill")({ + loader: async ({ context }) => { + const runtimeConfig = context.runtimeConfig; + + const skill = await fetch("/skill.md") + .then(async (response) => { + if (!response.ok) { + throw new Error(`Failed to load skill: ${response.status}`); + } + + return response.text(); + }) + .catch(() => null); + + return { + runtimeConfig, + skill, + intentRegistryUrl: INTENT_REGISTRY_URL, + }; + }, + head: () => ({ + meta: [ + { title: "Skill | app" }, + { + name: "description", + content: "Agent-oriented instructions for running, editing, and publishing this runtime.", + }, + ], + }), + component: SkillPage, +}); + +function SkillPage() { + const { skill, runtimeConfig, intentRegistryUrl } = Route.useLoaderData(); + const runtime = getActiveRuntime(runtimeConfig); + const account = getAccount(runtimeConfig); + const appName = getAppName(runtimeConfig); + const [copied, setCopied] = useState(false); + + const accountId = runtime?.accountId ?? account; + + const handleCopy = async () => { + if (!skill) { + toast.error("Skill prompt unavailable"); + return; + } + + await navigator.clipboard.writeText(skill); + setCopied(true); + toast.success("Skill prompt copied"); + setTimeout(() => setCopied(false), 2000); + }; + + return ( + <PageContainer variant="default"> + <div className="space-y-4"> + <div className="rounded-[12px] border border-border bg-card p-6 space-y-4"> + <div className="flex items-start justify-between gap-4 flex-wrap"> + <div className="flex items-center gap-3 min-w-0"> + <div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-[10px] bg-foreground text-background"> + <FileText size={18} /> + </div> + <div className="min-w-0"> + <div className="flex items-center gap-2 flex-wrap"> + <span className="text-xs font-mono text-muted-foreground">{accountId}</span> + <span className="text-muted-foreground">/</span> + <span className="text-base font-semibold text-foreground">{appName}</span> + </div> + <p className="mt-1 text-sm text-muted-foreground"> + Agent-ready prompt for TanStack Intent, local development, UI changes, and publish + flow. + </p> + </div> + </div> + + <div className="flex flex-wrap items-center gap-2"> + <Button variant="outline" onClick={handleCopy} disabled={!skill}> + {copied ? <Check size={14} /> : <Copy size={14} />} + {copied ? "Copied" : "Copy prompt"} + </Button> + <Button variant="outline" asChild> + <a href="/skill.md" target="_blank" rel="noopener noreferrer"> + <ExternalLink size={14} /> + raw skill.md + </a> + </Button> + <Button asChild> + <a href={intentRegistryUrl} target="_blank" rel="noopener noreferrer"> + <ExternalLink size={14} /> + TanStack Intent + </a> + </Button> + </div> + </div> + + <div className="rounded-[8px] border border-border bg-muted px-3.5 py-3 text-sm text-muted-foreground"> + Best entry points: `npx @tanstack/intent@latest load everything-dev`, `/skill.md`, and + the registry page above. + </div> + </div> + + {skill ? ( + <div className="rounded-[12px] border border-border bg-card p-8"> + <Markdown content={skill} /> + </div> + ) : ( + <div className="flex flex-col items-center justify-center gap-3 rounded-[12px] border border-border bg-card px-8 py-16 text-muted-foreground"> + <FileText size={32} className="text-border" /> + <p className="text-sm text-muted-foreground">Skill prompt unavailable.</p> + </div> + )} + </div> + </PageContainer> + ); +} From daee1041afa7a4de51e7f9283018601fd3040ba8 Mon Sep 17 00:00:00 2001 From: Elliot Braem <elliot@ejlbraem.com> Date: Thu, 13 Aug 2026 17:41:36 -0500 Subject: [PATCH 05/17] refactor(ui): reorganize routes into mount-point layouts, rename home to dashboard - add _admin pathless layout gating on admin role; tenant admin dashboard and system pages render as children via Outlet - rename authenticated /home route to /dashboard; update sidebar, mobile tabs, user nav, and login redirect fallbacks - move apps and things routes under the public layout - changeset: ui-layout-mounts --- .changeset/ui-layout-mounts.md | 9 + ui/src/components/user-nav.tsx | 2 +- ui/src/routeTree.gen.ts | 421 +++++++++--------- ui/src/routes/_layout/_admin.tsx | 62 +++ .../{_authenticated => _admin}/admin.tsx | 54 +-- ui/src/routes/_layout/_admin/admin/index.tsx | 152 +++++++ ui/src/routes/_layout/_admin/admin/system.tsx | 70 +++ ui/src/routes/_layout/_authenticated.tsx | 4 +- .../_layout/_authenticated/admin/index.tsx | 104 ----- .../_layout/_authenticated/admin/system.tsx | 73 --- .../{home.tsx => dashboard.tsx} | 2 +- .../apps/$accountId/$gatewayId.tsx | 2 +- .../{ => _public}/apps/$accountId/index.tsx | 2 +- .../_layout/{ => _public}/apps/index.tsx | 2 +- ui/src/routes/_layout/_public/login.tsx | 6 +- .../_layout/{ => _public}/things/$thingId.tsx | 2 +- .../_layout/{ => _public}/things/index.tsx | 2 +- .../_layout/{ => _public}/things/live.tsx | 2 +- 18 files changed, 532 insertions(+), 439 deletions(-) create mode 100644 .changeset/ui-layout-mounts.md create mode 100644 ui/src/routes/_layout/_admin.tsx rename ui/src/routes/_layout/{_authenticated => _admin}/admin.tsx (65%) create mode 100644 ui/src/routes/_layout/_admin/admin/index.tsx create mode 100644 ui/src/routes/_layout/_admin/admin/system.tsx delete mode 100644 ui/src/routes/_layout/_authenticated/admin/index.tsx delete mode 100644 ui/src/routes/_layout/_authenticated/admin/system.tsx rename ui/src/routes/_layout/_authenticated/{home.tsx => dashboard.tsx} (98%) rename ui/src/routes/_layout/{ => _public}/apps/$accountId/$gatewayId.tsx (99%) rename ui/src/routes/_layout/{ => _public}/apps/$accountId/index.tsx (99%) rename ui/src/routes/_layout/{ => _public}/apps/index.tsx (99%) rename ui/src/routes/_layout/{ => _public}/things/$thingId.tsx (98%) rename ui/src/routes/_layout/{ => _public}/things/index.tsx (97%) rename ui/src/routes/_layout/{ => _public}/things/live.tsx (98%) diff --git a/.changeset/ui-layout-mounts.md b/.changeset/ui-layout-mounts.md new file mode 100644 index 00000000..b9e8dd19 --- /dev/null +++ b/.changeset/ui-layout-mounts.md @@ -0,0 +1,9 @@ +--- +"ui": minor +--- + +Reorganize UI routes into mount-point layouts and rename the authenticated workspace. + +- Move admin routes under a new `/_layout/_admin` pathless layout that gates on the admin role and redirects non-admins to `/dashboard`. The tenant admin dashboard (`admin/admin/index.tsx`) and system page (`admin/admin/system.tsx`) now render as children of the admin layout through an `Outlet`. +- Rename the authenticated `/home` route to `/dashboard`, updating the sidebar, mobile tab bar, user nav, and login redirect fallbacks. +- Move the apps and things routes under the public layout (`_layout/_public/apps`, `_layout/_public/things`) so they render inside the shared public shell instead of the top-level layout. diff --git a/ui/src/components/user-nav.tsx b/ui/src/components/user-nav.tsx index 5929d4c4..ba6a6107 100644 --- a/ui/src/components/user-nav.tsx +++ b/ui/src/components/user-nav.tsx @@ -144,7 +144,7 @@ export function UserNav() { </DropdownMenuItem> <DropdownMenuSeparator /> <DropdownMenuItem asChild> - <Link to="/home"> + <Link to="/dashboard"> <Home /> workspace </Link> diff --git a/ui/src/routeTree.gen.ts b/ui/src/routeTree.gen.ts index 7bb4ab92..359bd53d 100644 --- a/ui/src/routeTree.gen.ts +++ b/ui/src/routeTree.gen.ts @@ -12,23 +12,22 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as LayoutRouteImport } from './routes/_layout' import { Route as LayoutPublicRouteImport } from './routes/_layout/_public' import { Route as LayoutAuthenticatedRouteImport } from './routes/_layout/_authenticated' -import { Route as LayoutThingsIndexRouteImport } from './routes/_layout/things/index' -import { Route as LayoutAppsIndexRouteImport } from './routes/_layout/apps/index' +import { Route as LayoutAdminRouteImport } from './routes/_layout/_admin' import { Route as LayoutPublicIndexRouteImport } from './routes/_layout/_public/index' -import { Route as LayoutThingsLiveRouteImport } from './routes/_layout/things/live' -import { Route as LayoutThingsThingIdRouteImport } from './routes/_layout/things/$thingId' import { Route as LayoutPublicSkillRouteImport } from './routes/_layout/_public/skill' import { Route as LayoutPublicLoginRouteImport } from './routes/_layout/_public/login' import { Route as LayoutPublicAboutRouteImport } from './routes/_layout/_public/about' import { Route as LayoutPublicAccountIdRouteImport } from './routes/_layout/_public/$accountId' import { Route as LayoutAuthenticatedSettingsRouteImport } from './routes/_layout/_authenticated/settings' -import { Route as LayoutAuthenticatedHomeRouteImport } from './routes/_layout/_authenticated/home' -import { Route as LayoutAuthenticatedAdminRouteImport } from './routes/_layout/_authenticated/admin' -import { Route as LayoutAppsAccountIdIndexRouteImport } from './routes/_layout/apps/$accountId/index' +import { Route as LayoutAuthenticatedDashboardRouteImport } from './routes/_layout/_authenticated/dashboard' +import { Route as LayoutAdminAdminRouteImport } from './routes/_layout/_admin/admin' +import { Route as LayoutPublicThingsIndexRouteImport } from './routes/_layout/_public/things/index' +import { Route as LayoutPublicAppsIndexRouteImport } from './routes/_layout/_public/apps/index' import { Route as LayoutAuthenticatedSettingsIndexRouteImport } from './routes/_layout/_authenticated/settings/index' import { Route as LayoutAuthenticatedOrganizationsIndexRouteImport } from './routes/_layout/_authenticated/organizations/index' -import { Route as LayoutAuthenticatedAdminIndexRouteImport } from './routes/_layout/_authenticated/admin/index' -import { Route as LayoutAppsAccountIdGatewayIdRouteImport } from './routes/_layout/apps/$accountId/$gatewayId' +import { Route as LayoutAdminAdminIndexRouteImport } from './routes/_layout/_admin/admin/index' +import { Route as LayoutPublicThingsLiveRouteImport } from './routes/_layout/_public/things/live' +import { Route as LayoutPublicThingsThingIdRouteImport } from './routes/_layout/_public/things/$thingId' import { Route as LayoutAuthenticatedThingsNewRouteImport } from './routes/_layout/_authenticated/things/new' import { Route as LayoutAuthenticatedTenantNewRouteImport } from './routes/_layout/_authenticated/tenant/new' import { Route as LayoutAuthenticatedTenantTenantIdRouteImport } from './routes/_layout/_authenticated/tenant/$tenantId' @@ -37,8 +36,10 @@ import { Route as LayoutAuthenticatedSettingsProfileRouteImport } from './routes import { Route as LayoutAuthenticatedSettingsAuthMethodsRouteImport } from './routes/_layout/_authenticated/settings/auth-methods' import { Route as LayoutAuthenticatedOrganizationsNewRouteImport } from './routes/_layout/_authenticated/organizations/new' import { Route as LayoutAuthenticatedOrganizationsSlugRouteImport } from './routes/_layout/_authenticated/organizations/$slug' -import { Route as LayoutAuthenticatedAdminSystemRouteImport } from './routes/_layout/_authenticated/admin/system' import { Route as LayoutAuthenticatedAcceptInvitationIdRouteImport } from './routes/_layout/_authenticated/accept-invitation.$id' +import { Route as LayoutAdminAdminSystemRouteImport } from './routes/_layout/_admin/admin/system' +import { Route as LayoutPublicAppsAccountIdIndexRouteImport } from './routes/_layout/_public/apps/$accountId/index' +import { Route as LayoutPublicAppsAccountIdGatewayIdRouteImport } from './routes/_layout/_public/apps/$accountId/$gatewayId' const LayoutRoute = LayoutRouteImport.update({ id: '/_layout', @@ -52,14 +53,8 @@ const LayoutAuthenticatedRoute = LayoutAuthenticatedRouteImport.update({ id: '/_authenticated', getParentRoute: () => LayoutRoute, } as any) -const LayoutThingsIndexRoute = LayoutThingsIndexRouteImport.update({ - id: '/things/', - path: '/things/', - getParentRoute: () => LayoutRoute, -} as any) -const LayoutAppsIndexRoute = LayoutAppsIndexRouteImport.update({ - id: '/apps/', - path: '/apps/', +const LayoutAdminRoute = LayoutAdminRouteImport.update({ + id: '/_admin', getParentRoute: () => LayoutRoute, } as any) const LayoutPublicIndexRoute = LayoutPublicIndexRouteImport.update({ @@ -67,16 +62,6 @@ const LayoutPublicIndexRoute = LayoutPublicIndexRouteImport.update({ path: '/', getParentRoute: () => LayoutPublicRoute, } as any) -const LayoutThingsLiveRoute = LayoutThingsLiveRouteImport.update({ - id: '/things/live', - path: '/things/live', - getParentRoute: () => LayoutRoute, -} as any) -const LayoutThingsThingIdRoute = LayoutThingsThingIdRouteImport.update({ - id: '/things/$thingId', - path: '/things/$thingId', - getParentRoute: () => LayoutRoute, -} as any) const LayoutPublicSkillRoute = LayoutPublicSkillRouteImport.update({ id: '/skill', path: '/skill', @@ -103,23 +88,27 @@ const LayoutAuthenticatedSettingsRoute = path: '/settings', getParentRoute: () => LayoutAuthenticatedRoute, } as any) -const LayoutAuthenticatedHomeRoute = LayoutAuthenticatedHomeRouteImport.update({ - id: '/home', - path: '/home', - getParentRoute: () => LayoutAuthenticatedRoute, -} as any) -const LayoutAuthenticatedAdminRoute = - LayoutAuthenticatedAdminRouteImport.update({ - id: '/admin', - path: '/admin', +const LayoutAuthenticatedDashboardRoute = + LayoutAuthenticatedDashboardRouteImport.update({ + id: '/dashboard', + path: '/dashboard', getParentRoute: () => LayoutAuthenticatedRoute, } as any) -const LayoutAppsAccountIdIndexRoute = - LayoutAppsAccountIdIndexRouteImport.update({ - id: '/apps/$accountId/', - path: '/apps/$accountId/', - getParentRoute: () => LayoutRoute, - } as any) +const LayoutAdminAdminRoute = LayoutAdminAdminRouteImport.update({ + id: '/admin', + path: '/admin', + getParentRoute: () => LayoutAdminRoute, +} as any) +const LayoutPublicThingsIndexRoute = LayoutPublicThingsIndexRouteImport.update({ + id: '/things/', + path: '/things/', + getParentRoute: () => LayoutPublicRoute, +} as any) +const LayoutPublicAppsIndexRoute = LayoutPublicAppsIndexRouteImport.update({ + id: '/apps/', + path: '/apps/', + getParentRoute: () => LayoutPublicRoute, +} as any) const LayoutAuthenticatedSettingsIndexRoute = LayoutAuthenticatedSettingsIndexRouteImport.update({ id: '/', @@ -132,17 +121,21 @@ const LayoutAuthenticatedOrganizationsIndexRoute = path: '/organizations/', getParentRoute: () => LayoutAuthenticatedRoute, } as any) -const LayoutAuthenticatedAdminIndexRoute = - LayoutAuthenticatedAdminIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => LayoutAuthenticatedAdminRoute, - } as any) -const LayoutAppsAccountIdGatewayIdRoute = - LayoutAppsAccountIdGatewayIdRouteImport.update({ - id: '/apps/$accountId/$gatewayId', - path: '/apps/$accountId/$gatewayId', - getParentRoute: () => LayoutRoute, +const LayoutAdminAdminIndexRoute = LayoutAdminAdminIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => LayoutAdminAdminRoute, +} as any) +const LayoutPublicThingsLiveRoute = LayoutPublicThingsLiveRouteImport.update({ + id: '/things/live', + path: '/things/live', + getParentRoute: () => LayoutPublicRoute, +} as any) +const LayoutPublicThingsThingIdRoute = + LayoutPublicThingsThingIdRouteImport.update({ + id: '/things/$thingId', + path: '/things/$thingId', + getParentRoute: () => LayoutPublicRoute, } as any) const LayoutAuthenticatedThingsNewRoute = LayoutAuthenticatedThingsNewRouteImport.update({ @@ -192,34 +185,41 @@ const LayoutAuthenticatedOrganizationsSlugRoute = path: '/organizations/$slug', getParentRoute: () => LayoutAuthenticatedRoute, } as any) -const LayoutAuthenticatedAdminSystemRoute = - LayoutAuthenticatedAdminSystemRouteImport.update({ - id: '/system', - path: '/system', - getParentRoute: () => LayoutAuthenticatedAdminRoute, - } as any) const LayoutAuthenticatedAcceptInvitationIdRoute = LayoutAuthenticatedAcceptInvitationIdRouteImport.update({ id: '/accept-invitation/$id', path: '/accept-invitation/$id', getParentRoute: () => LayoutAuthenticatedRoute, } as any) +const LayoutAdminAdminSystemRoute = LayoutAdminAdminSystemRouteImport.update({ + id: '/system', + path: '/system', + getParentRoute: () => LayoutAdminAdminRoute, +} as any) +const LayoutPublicAppsAccountIdIndexRoute = + LayoutPublicAppsAccountIdIndexRouteImport.update({ + id: '/apps/$accountId/', + path: '/apps/$accountId/', + getParentRoute: () => LayoutPublicRoute, + } as any) +const LayoutPublicAppsAccountIdGatewayIdRoute = + LayoutPublicAppsAccountIdGatewayIdRouteImport.update({ + id: '/apps/$accountId/$gatewayId', + path: '/apps/$accountId/$gatewayId', + getParentRoute: () => LayoutPublicRoute, + } as any) export interface FileRoutesByFullPath { '/': typeof LayoutPublicIndexRoute - '/admin': typeof LayoutAuthenticatedAdminRouteWithChildren - '/home': typeof LayoutAuthenticatedHomeRoute + '/admin': typeof LayoutAdminAdminRouteWithChildren + '/dashboard': typeof LayoutAuthenticatedDashboardRoute '/settings': typeof LayoutAuthenticatedSettingsRouteWithChildren '/$accountId': typeof LayoutPublicAccountIdRoute '/about': typeof LayoutPublicAboutRoute '/login': typeof LayoutPublicLoginRoute '/skill': typeof LayoutPublicSkillRoute - '/things/$thingId': typeof LayoutThingsThingIdRoute - '/things/live': typeof LayoutThingsLiveRoute - '/apps/': typeof LayoutAppsIndexRoute - '/things/': typeof LayoutThingsIndexRoute + '/admin/system': typeof LayoutAdminAdminSystemRoute '/accept-invitation/$id': typeof LayoutAuthenticatedAcceptInvitationIdRoute - '/admin/system': typeof LayoutAuthenticatedAdminSystemRoute '/organizations/$slug': typeof LayoutAuthenticatedOrganizationsSlugRoute '/organizations/new': typeof LayoutAuthenticatedOrganizationsNewRoute '/settings/auth-methods': typeof LayoutAuthenticatedSettingsAuthMethodsRoute @@ -228,25 +228,25 @@ export interface FileRoutesByFullPath { '/tenant/$tenantId': typeof LayoutAuthenticatedTenantTenantIdRoute '/tenant/new': typeof LayoutAuthenticatedTenantNewRoute '/things/new': typeof LayoutAuthenticatedThingsNewRoute - '/apps/$accountId/$gatewayId': typeof LayoutAppsAccountIdGatewayIdRoute - '/admin/': typeof LayoutAuthenticatedAdminIndexRoute + '/things/$thingId': typeof LayoutPublicThingsThingIdRoute + '/things/live': typeof LayoutPublicThingsLiveRoute + '/admin/': typeof LayoutAdminAdminIndexRoute '/organizations/': typeof LayoutAuthenticatedOrganizationsIndexRoute '/settings/': typeof LayoutAuthenticatedSettingsIndexRoute - '/apps/$accountId/': typeof LayoutAppsAccountIdIndexRoute + '/apps/': typeof LayoutPublicAppsIndexRoute + '/things/': typeof LayoutPublicThingsIndexRoute + '/apps/$accountId/$gatewayId': typeof LayoutPublicAppsAccountIdGatewayIdRoute + '/apps/$accountId/': typeof LayoutPublicAppsAccountIdIndexRoute } export interface FileRoutesByTo { '/': typeof LayoutPublicIndexRoute - '/home': typeof LayoutAuthenticatedHomeRoute + '/dashboard': typeof LayoutAuthenticatedDashboardRoute '/$accountId': typeof LayoutPublicAccountIdRoute '/about': typeof LayoutPublicAboutRoute '/login': typeof LayoutPublicLoginRoute '/skill': typeof LayoutPublicSkillRoute - '/things/$thingId': typeof LayoutThingsThingIdRoute - '/things/live': typeof LayoutThingsLiveRoute - '/apps': typeof LayoutAppsIndexRoute - '/things': typeof LayoutThingsIndexRoute + '/admin/system': typeof LayoutAdminAdminSystemRoute '/accept-invitation/$id': typeof LayoutAuthenticatedAcceptInvitationIdRoute - '/admin/system': typeof LayoutAuthenticatedAdminSystemRoute '/organizations/$slug': typeof LayoutAuthenticatedOrganizationsSlugRoute '/organizations/new': typeof LayoutAuthenticatedOrganizationsNewRoute '/settings/auth-methods': typeof LayoutAuthenticatedSettingsAuthMethodsRoute @@ -255,31 +255,32 @@ export interface FileRoutesByTo { '/tenant/$tenantId': typeof LayoutAuthenticatedTenantTenantIdRoute '/tenant/new': typeof LayoutAuthenticatedTenantNewRoute '/things/new': typeof LayoutAuthenticatedThingsNewRoute - '/apps/$accountId/$gatewayId': typeof LayoutAppsAccountIdGatewayIdRoute - '/admin': typeof LayoutAuthenticatedAdminIndexRoute + '/things/$thingId': typeof LayoutPublicThingsThingIdRoute + '/things/live': typeof LayoutPublicThingsLiveRoute + '/admin': typeof LayoutAdminAdminIndexRoute '/organizations': typeof LayoutAuthenticatedOrganizationsIndexRoute '/settings': typeof LayoutAuthenticatedSettingsIndexRoute - '/apps/$accountId': typeof LayoutAppsAccountIdIndexRoute + '/apps': typeof LayoutPublicAppsIndexRoute + '/things': typeof LayoutPublicThingsIndexRoute + '/apps/$accountId/$gatewayId': typeof LayoutPublicAppsAccountIdGatewayIdRoute + '/apps/$accountId': typeof LayoutPublicAppsAccountIdIndexRoute } export interface FileRoutesById { __root__: typeof rootRouteImport '/_layout': typeof LayoutRouteWithChildren + '/_layout/_admin': typeof LayoutAdminRouteWithChildren '/_layout/_authenticated': typeof LayoutAuthenticatedRouteWithChildren '/_layout/_public': typeof LayoutPublicRouteWithChildren - '/_layout/_authenticated/admin': typeof LayoutAuthenticatedAdminRouteWithChildren - '/_layout/_authenticated/home': typeof LayoutAuthenticatedHomeRoute + '/_layout/_admin/admin': typeof LayoutAdminAdminRouteWithChildren + '/_layout/_authenticated/dashboard': typeof LayoutAuthenticatedDashboardRoute '/_layout/_authenticated/settings': typeof LayoutAuthenticatedSettingsRouteWithChildren '/_layout/_public/$accountId': typeof LayoutPublicAccountIdRoute '/_layout/_public/about': typeof LayoutPublicAboutRoute '/_layout/_public/login': typeof LayoutPublicLoginRoute '/_layout/_public/skill': typeof LayoutPublicSkillRoute - '/_layout/things/$thingId': typeof LayoutThingsThingIdRoute - '/_layout/things/live': typeof LayoutThingsLiveRoute '/_layout/_public/': typeof LayoutPublicIndexRoute - '/_layout/apps/': typeof LayoutAppsIndexRoute - '/_layout/things/': typeof LayoutThingsIndexRoute + '/_layout/_admin/admin/system': typeof LayoutAdminAdminSystemRoute '/_layout/_authenticated/accept-invitation/$id': typeof LayoutAuthenticatedAcceptInvitationIdRoute - '/_layout/_authenticated/admin/system': typeof LayoutAuthenticatedAdminSystemRoute '/_layout/_authenticated/organizations/$slug': typeof LayoutAuthenticatedOrganizationsSlugRoute '/_layout/_authenticated/organizations/new': typeof LayoutAuthenticatedOrganizationsNewRoute '/_layout/_authenticated/settings/auth-methods': typeof LayoutAuthenticatedSettingsAuthMethodsRoute @@ -288,29 +289,29 @@ export interface FileRoutesById { '/_layout/_authenticated/tenant/$tenantId': typeof LayoutAuthenticatedTenantTenantIdRoute '/_layout/_authenticated/tenant/new': typeof LayoutAuthenticatedTenantNewRoute '/_layout/_authenticated/things/new': typeof LayoutAuthenticatedThingsNewRoute - '/_layout/apps/$accountId/$gatewayId': typeof LayoutAppsAccountIdGatewayIdRoute - '/_layout/_authenticated/admin/': typeof LayoutAuthenticatedAdminIndexRoute + '/_layout/_public/things/$thingId': typeof LayoutPublicThingsThingIdRoute + '/_layout/_public/things/live': typeof LayoutPublicThingsLiveRoute + '/_layout/_admin/admin/': typeof LayoutAdminAdminIndexRoute '/_layout/_authenticated/organizations/': typeof LayoutAuthenticatedOrganizationsIndexRoute '/_layout/_authenticated/settings/': typeof LayoutAuthenticatedSettingsIndexRoute - '/_layout/apps/$accountId/': typeof LayoutAppsAccountIdIndexRoute + '/_layout/_public/apps/': typeof LayoutPublicAppsIndexRoute + '/_layout/_public/things/': typeof LayoutPublicThingsIndexRoute + '/_layout/_public/apps/$accountId/$gatewayId': typeof LayoutPublicAppsAccountIdGatewayIdRoute + '/_layout/_public/apps/$accountId/': typeof LayoutPublicAppsAccountIdIndexRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath fullPaths: | '/' | '/admin' - | '/home' + | '/dashboard' | '/settings' | '/$accountId' | '/about' | '/login' | '/skill' - | '/things/$thingId' - | '/things/live' - | '/apps/' - | '/things/' - | '/accept-invitation/$id' | '/admin/system' + | '/accept-invitation/$id' | '/organizations/$slug' | '/organizations/new' | '/settings/auth-methods' @@ -319,25 +320,25 @@ export interface FileRouteTypes { | '/tenant/$tenantId' | '/tenant/new' | '/things/new' - | '/apps/$accountId/$gatewayId' + | '/things/$thingId' + | '/things/live' | '/admin/' | '/organizations/' | '/settings/' + | '/apps/' + | '/things/' + | '/apps/$accountId/$gatewayId' | '/apps/$accountId/' fileRoutesByTo: FileRoutesByTo to: | '/' - | '/home' + | '/dashboard' | '/$accountId' | '/about' | '/login' | '/skill' - | '/things/$thingId' - | '/things/live' - | '/apps' - | '/things' - | '/accept-invitation/$id' | '/admin/system' + | '/accept-invitation/$id' | '/organizations/$slug' | '/organizations/new' | '/settings/auth-methods' @@ -346,30 +347,31 @@ export interface FileRouteTypes { | '/tenant/$tenantId' | '/tenant/new' | '/things/new' - | '/apps/$accountId/$gatewayId' + | '/things/$thingId' + | '/things/live' | '/admin' | '/organizations' | '/settings' + | '/apps' + | '/things' + | '/apps/$accountId/$gatewayId' | '/apps/$accountId' id: | '__root__' | '/_layout' + | '/_layout/_admin' | '/_layout/_authenticated' | '/_layout/_public' - | '/_layout/_authenticated/admin' - | '/_layout/_authenticated/home' + | '/_layout/_admin/admin' + | '/_layout/_authenticated/dashboard' | '/_layout/_authenticated/settings' | '/_layout/_public/$accountId' | '/_layout/_public/about' | '/_layout/_public/login' | '/_layout/_public/skill' - | '/_layout/things/$thingId' - | '/_layout/things/live' | '/_layout/_public/' - | '/_layout/apps/' - | '/_layout/things/' + | '/_layout/_admin/admin/system' | '/_layout/_authenticated/accept-invitation/$id' - | '/_layout/_authenticated/admin/system' | '/_layout/_authenticated/organizations/$slug' | '/_layout/_authenticated/organizations/new' | '/_layout/_authenticated/settings/auth-methods' @@ -378,11 +380,15 @@ export interface FileRouteTypes { | '/_layout/_authenticated/tenant/$tenantId' | '/_layout/_authenticated/tenant/new' | '/_layout/_authenticated/things/new' - | '/_layout/apps/$accountId/$gatewayId' - | '/_layout/_authenticated/admin/' + | '/_layout/_public/things/$thingId' + | '/_layout/_public/things/live' + | '/_layout/_admin/admin/' | '/_layout/_authenticated/organizations/' | '/_layout/_authenticated/settings/' - | '/_layout/apps/$accountId/' + | '/_layout/_public/apps/' + | '/_layout/_public/things/' + | '/_layout/_public/apps/$accountId/$gatewayId' + | '/_layout/_public/apps/$accountId/' fileRoutesById: FileRoutesById } export interface RootRouteChildren { @@ -412,18 +418,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutAuthenticatedRouteImport parentRoute: typeof LayoutRoute } - '/_layout/things/': { - id: '/_layout/things/' - path: '/things' - fullPath: '/things/' - preLoaderRoute: typeof LayoutThingsIndexRouteImport - parentRoute: typeof LayoutRoute - } - '/_layout/apps/': { - id: '/_layout/apps/' - path: '/apps' - fullPath: '/apps/' - preLoaderRoute: typeof LayoutAppsIndexRouteImport + '/_layout/_admin': { + id: '/_layout/_admin' + path: '' + fullPath: '/' + preLoaderRoute: typeof LayoutAdminRouteImport parentRoute: typeof LayoutRoute } '/_layout/_public/': { @@ -433,20 +432,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutPublicIndexRouteImport parentRoute: typeof LayoutPublicRoute } - '/_layout/things/live': { - id: '/_layout/things/live' - path: '/things/live' - fullPath: '/things/live' - preLoaderRoute: typeof LayoutThingsLiveRouteImport - parentRoute: typeof LayoutRoute - } - '/_layout/things/$thingId': { - id: '/_layout/things/$thingId' - path: '/things/$thingId' - fullPath: '/things/$thingId' - preLoaderRoute: typeof LayoutThingsThingIdRouteImport - parentRoute: typeof LayoutRoute - } '/_layout/_public/skill': { id: '/_layout/_public/skill' path: '/skill' @@ -482,26 +467,33 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutAuthenticatedSettingsRouteImport parentRoute: typeof LayoutAuthenticatedRoute } - '/_layout/_authenticated/home': { - id: '/_layout/_authenticated/home' - path: '/home' - fullPath: '/home' - preLoaderRoute: typeof LayoutAuthenticatedHomeRouteImport + '/_layout/_authenticated/dashboard': { + id: '/_layout/_authenticated/dashboard' + path: '/dashboard' + fullPath: '/dashboard' + preLoaderRoute: typeof LayoutAuthenticatedDashboardRouteImport parentRoute: typeof LayoutAuthenticatedRoute } - '/_layout/_authenticated/admin': { - id: '/_layout/_authenticated/admin' + '/_layout/_admin/admin': { + id: '/_layout/_admin/admin' path: '/admin' fullPath: '/admin' - preLoaderRoute: typeof LayoutAuthenticatedAdminRouteImport - parentRoute: typeof LayoutAuthenticatedRoute + preLoaderRoute: typeof LayoutAdminAdminRouteImport + parentRoute: typeof LayoutAdminRoute } - '/_layout/apps/$accountId/': { - id: '/_layout/apps/$accountId/' - path: '/apps/$accountId' - fullPath: '/apps/$accountId/' - preLoaderRoute: typeof LayoutAppsAccountIdIndexRouteImport - parentRoute: typeof LayoutRoute + '/_layout/_public/things/': { + id: '/_layout/_public/things/' + path: '/things' + fullPath: '/things/' + preLoaderRoute: typeof LayoutPublicThingsIndexRouteImport + parentRoute: typeof LayoutPublicRoute + } + '/_layout/_public/apps/': { + id: '/_layout/_public/apps/' + path: '/apps' + fullPath: '/apps/' + preLoaderRoute: typeof LayoutPublicAppsIndexRouteImport + parentRoute: typeof LayoutPublicRoute } '/_layout/_authenticated/settings/': { id: '/_layout/_authenticated/settings/' @@ -517,19 +509,26 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutAuthenticatedOrganizationsIndexRouteImport parentRoute: typeof LayoutAuthenticatedRoute } - '/_layout/_authenticated/admin/': { - id: '/_layout/_authenticated/admin/' + '/_layout/_admin/admin/': { + id: '/_layout/_admin/admin/' path: '/' fullPath: '/admin/' - preLoaderRoute: typeof LayoutAuthenticatedAdminIndexRouteImport - parentRoute: typeof LayoutAuthenticatedAdminRoute + preLoaderRoute: typeof LayoutAdminAdminIndexRouteImport + parentRoute: typeof LayoutAdminAdminRoute } - '/_layout/apps/$accountId/$gatewayId': { - id: '/_layout/apps/$accountId/$gatewayId' - path: '/apps/$accountId/$gatewayId' - fullPath: '/apps/$accountId/$gatewayId' - preLoaderRoute: typeof LayoutAppsAccountIdGatewayIdRouteImport - parentRoute: typeof LayoutRoute + '/_layout/_public/things/live': { + id: '/_layout/_public/things/live' + path: '/things/live' + fullPath: '/things/live' + preLoaderRoute: typeof LayoutPublicThingsLiveRouteImport + parentRoute: typeof LayoutPublicRoute + } + '/_layout/_public/things/$thingId': { + id: '/_layout/_public/things/$thingId' + path: '/things/$thingId' + fullPath: '/things/$thingId' + preLoaderRoute: typeof LayoutPublicThingsThingIdRouteImport + parentRoute: typeof LayoutPublicRoute } '/_layout/_authenticated/things/new': { id: '/_layout/_authenticated/things/new' @@ -587,13 +586,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutAuthenticatedOrganizationsSlugRouteImport parentRoute: typeof LayoutAuthenticatedRoute } - '/_layout/_authenticated/admin/system': { - id: '/_layout/_authenticated/admin/system' - path: '/system' - fullPath: '/admin/system' - preLoaderRoute: typeof LayoutAuthenticatedAdminSystemRouteImport - parentRoute: typeof LayoutAuthenticatedAdminRoute - } '/_layout/_authenticated/accept-invitation/$id': { id: '/_layout/_authenticated/accept-invitation/$id' path: '/accept-invitation/$id' @@ -601,24 +593,54 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutAuthenticatedAcceptInvitationIdRouteImport parentRoute: typeof LayoutAuthenticatedRoute } + '/_layout/_admin/admin/system': { + id: '/_layout/_admin/admin/system' + path: '/system' + fullPath: '/admin/system' + preLoaderRoute: typeof LayoutAdminAdminSystemRouteImport + parentRoute: typeof LayoutAdminAdminRoute + } + '/_layout/_public/apps/$accountId/': { + id: '/_layout/_public/apps/$accountId/' + path: '/apps/$accountId' + fullPath: '/apps/$accountId/' + preLoaderRoute: typeof LayoutPublicAppsAccountIdIndexRouteImport + parentRoute: typeof LayoutPublicRoute + } + '/_layout/_public/apps/$accountId/$gatewayId': { + id: '/_layout/_public/apps/$accountId/$gatewayId' + path: '/apps/$accountId/$gatewayId' + fullPath: '/apps/$accountId/$gatewayId' + preLoaderRoute: typeof LayoutPublicAppsAccountIdGatewayIdRouteImport + parentRoute: typeof LayoutPublicRoute + } } } -interface LayoutAuthenticatedAdminRouteChildren { - LayoutAuthenticatedAdminSystemRoute: typeof LayoutAuthenticatedAdminSystemRoute - LayoutAuthenticatedAdminIndexRoute: typeof LayoutAuthenticatedAdminIndexRoute +interface LayoutAdminAdminRouteChildren { + LayoutAdminAdminSystemRoute: typeof LayoutAdminAdminSystemRoute + LayoutAdminAdminIndexRoute: typeof LayoutAdminAdminIndexRoute } -const LayoutAuthenticatedAdminRouteChildren: LayoutAuthenticatedAdminRouteChildren = - { - LayoutAuthenticatedAdminSystemRoute: LayoutAuthenticatedAdminSystemRoute, - LayoutAuthenticatedAdminIndexRoute: LayoutAuthenticatedAdminIndexRoute, - } +const LayoutAdminAdminRouteChildren: LayoutAdminAdminRouteChildren = { + LayoutAdminAdminSystemRoute: LayoutAdminAdminSystemRoute, + LayoutAdminAdminIndexRoute: LayoutAdminAdminIndexRoute, +} -const LayoutAuthenticatedAdminRouteWithChildren = - LayoutAuthenticatedAdminRoute._addFileChildren( - LayoutAuthenticatedAdminRouteChildren, - ) +const LayoutAdminAdminRouteWithChildren = + LayoutAdminAdminRoute._addFileChildren(LayoutAdminAdminRouteChildren) + +interface LayoutAdminRouteChildren { + LayoutAdminAdminRoute: typeof LayoutAdminAdminRouteWithChildren +} + +const LayoutAdminRouteChildren: LayoutAdminRouteChildren = { + LayoutAdminAdminRoute: LayoutAdminAdminRouteWithChildren, +} + +const LayoutAdminRouteWithChildren = LayoutAdminRoute._addFileChildren( + LayoutAdminRouteChildren, +) interface LayoutAuthenticatedSettingsRouteChildren { LayoutAuthenticatedSettingsAuthMethodsRoute: typeof LayoutAuthenticatedSettingsAuthMethodsRoute @@ -645,8 +667,7 @@ const LayoutAuthenticatedSettingsRouteWithChildren = ) interface LayoutAuthenticatedRouteChildren { - LayoutAuthenticatedAdminRoute: typeof LayoutAuthenticatedAdminRouteWithChildren - LayoutAuthenticatedHomeRoute: typeof LayoutAuthenticatedHomeRoute + LayoutAuthenticatedDashboardRoute: typeof LayoutAuthenticatedDashboardRoute LayoutAuthenticatedSettingsRoute: typeof LayoutAuthenticatedSettingsRouteWithChildren LayoutAuthenticatedAcceptInvitationIdRoute: typeof LayoutAuthenticatedAcceptInvitationIdRoute LayoutAuthenticatedOrganizationsSlugRoute: typeof LayoutAuthenticatedOrganizationsSlugRoute @@ -658,8 +679,7 @@ interface LayoutAuthenticatedRouteChildren { } const LayoutAuthenticatedRouteChildren: LayoutAuthenticatedRouteChildren = { - LayoutAuthenticatedAdminRoute: LayoutAuthenticatedAdminRouteWithChildren, - LayoutAuthenticatedHomeRoute: LayoutAuthenticatedHomeRoute, + LayoutAuthenticatedDashboardRoute: LayoutAuthenticatedDashboardRoute, LayoutAuthenticatedSettingsRoute: LayoutAuthenticatedSettingsRouteWithChildren, LayoutAuthenticatedAcceptInvitationIdRoute: @@ -685,6 +705,12 @@ interface LayoutPublicRouteChildren { LayoutPublicLoginRoute: typeof LayoutPublicLoginRoute LayoutPublicSkillRoute: typeof LayoutPublicSkillRoute LayoutPublicIndexRoute: typeof LayoutPublicIndexRoute + LayoutPublicThingsThingIdRoute: typeof LayoutPublicThingsThingIdRoute + LayoutPublicThingsLiveRoute: typeof LayoutPublicThingsLiveRoute + LayoutPublicAppsIndexRoute: typeof LayoutPublicAppsIndexRoute + LayoutPublicThingsIndexRoute: typeof LayoutPublicThingsIndexRoute + LayoutPublicAppsAccountIdGatewayIdRoute: typeof LayoutPublicAppsAccountIdGatewayIdRoute + LayoutPublicAppsAccountIdIndexRoute: typeof LayoutPublicAppsAccountIdIndexRoute } const LayoutPublicRouteChildren: LayoutPublicRouteChildren = { @@ -693,6 +719,13 @@ const LayoutPublicRouteChildren: LayoutPublicRouteChildren = { LayoutPublicLoginRoute: LayoutPublicLoginRoute, LayoutPublicSkillRoute: LayoutPublicSkillRoute, LayoutPublicIndexRoute: LayoutPublicIndexRoute, + LayoutPublicThingsThingIdRoute: LayoutPublicThingsThingIdRoute, + LayoutPublicThingsLiveRoute: LayoutPublicThingsLiveRoute, + LayoutPublicAppsIndexRoute: LayoutPublicAppsIndexRoute, + LayoutPublicThingsIndexRoute: LayoutPublicThingsIndexRoute, + LayoutPublicAppsAccountIdGatewayIdRoute: + LayoutPublicAppsAccountIdGatewayIdRoute, + LayoutPublicAppsAccountIdIndexRoute: LayoutPublicAppsAccountIdIndexRoute, } const LayoutPublicRouteWithChildren = LayoutPublicRoute._addFileChildren( @@ -700,25 +733,15 @@ const LayoutPublicRouteWithChildren = LayoutPublicRoute._addFileChildren( ) interface LayoutRouteChildren { + LayoutAdminRoute: typeof LayoutAdminRouteWithChildren LayoutAuthenticatedRoute: typeof LayoutAuthenticatedRouteWithChildren LayoutPublicRoute: typeof LayoutPublicRouteWithChildren - LayoutThingsThingIdRoute: typeof LayoutThingsThingIdRoute - LayoutThingsLiveRoute: typeof LayoutThingsLiveRoute - LayoutAppsIndexRoute: typeof LayoutAppsIndexRoute - LayoutThingsIndexRoute: typeof LayoutThingsIndexRoute - LayoutAppsAccountIdGatewayIdRoute: typeof LayoutAppsAccountIdGatewayIdRoute - LayoutAppsAccountIdIndexRoute: typeof LayoutAppsAccountIdIndexRoute } const LayoutRouteChildren: LayoutRouteChildren = { + LayoutAdminRoute: LayoutAdminRouteWithChildren, LayoutAuthenticatedRoute: LayoutAuthenticatedRouteWithChildren, LayoutPublicRoute: LayoutPublicRouteWithChildren, - LayoutThingsThingIdRoute: LayoutThingsThingIdRoute, - LayoutThingsLiveRoute: LayoutThingsLiveRoute, - LayoutAppsIndexRoute: LayoutAppsIndexRoute, - LayoutThingsIndexRoute: LayoutThingsIndexRoute, - LayoutAppsAccountIdGatewayIdRoute: LayoutAppsAccountIdGatewayIdRoute, - LayoutAppsAccountIdIndexRoute: LayoutAppsAccountIdIndexRoute, } const LayoutRouteWithChildren = diff --git a/ui/src/routes/_layout/_admin.tsx b/ui/src/routes/_layout/_admin.tsx new file mode 100644 index 00000000..9906591e --- /dev/null +++ b/ui/src/routes/_layout/_admin.tsx @@ -0,0 +1,62 @@ +import { createFileRoute, Outlet, redirect } from "@tanstack/react-router"; +import type { SessionData } from "@/app"; +import { sessionQueryOptions } from "@/app"; + +interface AuthContext { + isAuthenticated: boolean; + user: SessionData["user"] | null; + session: SessionData["session"] | null; + activeOrganizationId: string | null; + isAnonymous: boolean; + isAdmin: boolean; + isBanned: boolean; +} + +export const Route = createFileRoute("/_layout/_admin")({ + beforeLoad: async ({ context, location }) => { + const { queryClient, authClient } = context; + + const session = await queryClient.ensureQueryData( + sessionQueryOptions(authClient, context.session), + ); + + if (!session?.user) { + throw redirect({ + to: "/login", + search: { + redirect: location.href, + }, + }); + } + + if (session.user.banned) { + throw redirect({ + to: "/login", + hash: "banned", + }); + } + + if (session.user.role !== "admin") { + throw redirect({ to: "/dashboard" }); + } + + const auth: AuthContext = { + isAuthenticated: true, + user: session.user, + session: session.session, + activeOrganizationId: session.session?.activeOrganizationId || null, + isAnonymous: session.user.isAnonymous || false, + isAdmin: session.user.role === "admin", + isBanned: session.user.banned || false, + }; + return { + auth, + session, + }; + }, + component: AdminGate, +}); + +function AdminGate() { + return <Outlet />; +} diff --git a/ui/src/routes/_layout/_authenticated/admin.tsx b/ui/src/routes/_layout/_admin/admin.tsx similarity index 65% rename from ui/src/routes/_layout/_authenticated/admin.tsx rename to ui/src/routes/_layout/_admin/admin.tsx index a044a4f4..e57fd806 100644 --- a/ui/src/routes/_layout/_authenticated/admin.tsx +++ b/ui/src/routes/_layout/_admin/admin.tsx @@ -1,12 +1,10 @@ -import { createFileRoute, Link, redirect } from "@tanstack/react-router"; -import { Shield, Users } from "lucide-react"; +import { createFileRoute, Link, Outlet, redirect } from "@tanstack/react-router"; +import { Shield } from "lucide-react"; import { getAccount } from "@/app"; -import { Card } from "@/components"; import { EmptyState } from "@/components/empty-state"; import { PageContainer } from "@/components/layout/page-container"; -import { InfoRow } from "@/components/ui/info-row"; -export const Route = createFileRoute("/_layout/_authenticated/admin")({ +export const Route = createFileRoute("/_layout/_admin/admin")({ head: () => ({ meta: [{ title: "Admin | app" }], }), @@ -109,42 +107,7 @@ function AdminPage() { /> </section> - <section className="space-y-3"> - <SectionHeader title="Tenant details" /> - <Card className="p-6 space-y-4"> - <div className="text-muted-foreground text-[11px] font-bold uppercase tracking-wider"> - Configuration - </div> - <div className="flex flex-col gap-2"> - <InfoRow label="name" value={tenant.name} /> - <InfoRow label="subdomain" value={tenant.subdomain} mono /> - <InfoRow label="account" value={tenant.accountId} mono /> - <InfoRow label="org Id" value={tenant.orgId} mono /> - <InfoRow - label="created" - value={tenant.createdAt ? new Date(tenant.createdAt).toLocaleDateString() : "—"} - /> - </div> - </Card> - </section> - - <section className="space-y-3"> - <SectionHeader title="Members & permissions" /> - <Card className="p-4 space-y-3"> - <p className="text-sm text-muted-foreground"> - This tenant is backed by an organization. Manage members, roles, and invitations - there. - </p> - <Link - to="/organizations/$slug" - params={{ slug: tenant.subdomain }} - className="h-9 px-3 inline-flex items-center gap-1.5 text-xs font-medium border-2 border-outset border-border-strong bg-card text-foreground shadow-sm hover:shadow-md active:border-inset active:shadow-none transition-all duration-200 ease-out rounded-[10px]" - > - <Users className="h-3.5 w-3.5" /> - open organization - </Link> - </Card> - </section> + <Outlet /> </div> </PageContainer> ); @@ -172,12 +135,3 @@ function StatCard({ </div> ); } - -function SectionHeader({ title, action }: { title: string; action?: React.ReactNode }) { - return ( - <div className="flex items-end justify-between gap-3"> - <h2 className="text-lg font-semibold text-foreground">{title}</h2> - {action} - </div> - ); -} diff --git a/ui/src/routes/_layout/_admin/admin/index.tsx b/ui/src/routes/_layout/_admin/admin/index.tsx new file mode 100644 index 00000000..fd5a7378 --- /dev/null +++ b/ui/src/routes/_layout/_admin/admin/index.tsx @@ -0,0 +1,152 @@ +import { createFileRoute, Link } from "@tanstack/react-router"; +import { Building2, Settings, Users } from "lucide-react"; +import { getAccount } from "@/app"; +import { Button, Card } from "@/components"; +import { InfoRow } from "@/components/ui/info-row"; + +export const Route = createFileRoute("/_layout/_admin/admin/")({ + head: () => ({ + meta: [{ title: "Admin Dashboard | app" }], + }), + component: AdminDashboard, +}); + +function AdminDashboard() { + const { auth, tenant } = Route.useRouteContext(); + const account = getAccount(); + const user = auth?.user ?? null; + + return ( + <div className="space-y-8"> + <header className="space-y-3"> + <div className="flex flex-wrap items-end justify-between gap-3"> + <div className="space-y-1"> + <h1 className="text-2xl sm:text-3xl font-bold tracking-tight text-foreground"> + Dashboard + </h1> + <p className="text-sm text-muted-foreground"> + Signed in as <span className="font-mono">{account}</span> + </p> + </div> + </div> + </header> + + <section className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4"> + <StatCard label="Account" value={account} mono /> + <StatCard label="Name" value={user?.name || user?.email || "—"} /> + <StatCard label="Role" value={user?.role ?? "—"} /> + <StatCard + label="Created" + value={user?.createdAt ? new Date(user.createdAt).toLocaleDateString() : "—"} + /> + </section> + + <section className="space-y-3"> + <h2 className="text-lg font-semibold text-foreground">Manage</h2> + <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3"> + <Card className="p-6 space-y-3"> + <div className="flex h-10 w-10 items-center justify-center rounded-[10px] bg-foreground text-background"> + <Building2 className="h-4 w-4" /> + </div> + <h3 className="text-base font-semibold text-foreground">Organizations</h3> + <p className="text-sm text-muted-foreground"> + Manage organizations, members, roles, and invitations. + </p> + <Button asChild variant="outline" size="sm"> + <Link to="/organizations"> + <Users className="h-3.5 w-3.5" /> + open organizations + </Link> + </Button> + </Card> + + <Card className="p-6 space-y-3"> + <div className="flex h-10 w-10 items-center justify-center rounded-[10px] bg-foreground text-background"> + <Settings className="h-4 w-4" /> + </div> + <h3 className="text-base font-semibold text-foreground">Settings</h3> + <p className="text-sm text-muted-foreground"> + Update your profile, auth methods, and security preferences. + </p> + <Button asChild variant="outline" size="sm"> + <Link to="/settings">open settings</Link> + </Button> + </Card> + </div> + </section> + + {tenant && ( + <section className="space-y-3"> + <SectionHeader title="Tenant details" /> + <Card className="p-6 space-y-4"> + <div className="text-muted-foreground text-[11px] font-bold uppercase tracking-wider"> + Configuration + </div> + <div className="flex flex-col gap-2"> + <InfoRow label="name" value={tenant.name} /> + <InfoRow label="subdomain" value={tenant.subdomain} mono /> + <InfoRow label="account" value={tenant.accountId} mono /> + <InfoRow label="org Id" value={tenant.orgId} mono /> + <InfoRow + label="created" + value={tenant.createdAt ? new Date(tenant.createdAt).toLocaleDateString() : "—"} + /> + </div> + </Card> + </section> + )} + + {tenant && ( + <section className="space-y-3"> + <SectionHeader title="Members & permissions" /> + <Card className="p-4 space-y-3"> + <p className="text-sm text-muted-foreground"> + This tenant is backed by an organization. Manage members, roles, and invitations + there. + </p> + <Link + to="/organizations/$slug" + params={{ slug: tenant.subdomain }} + className="h-9 px-3 inline-flex items-center gap-1.5 text-xs font-medium border-2 border-outset border-border-strong bg-card text-foreground shadow-sm hover:shadow-md active:border-inset active:shadow-none transition-all duration-200 ease-out rounded-[10px]" + > + <Users className="h-3.5 w-3.5" /> + open organization + </Link> + </Card> + </section> + )} + </div> + ); +} + +function StatCard({ + label, + value, + mono, +}: { + label: string; + value: React.ReactNode; + mono?: boolean; +}) { + return ( + <div className="border-2 border-outset border-border-strong bg-card p-4 rounded-[12px] shadow-sm space-y-1"> + <div className="text-[10px] font-bold uppercase tracking-wider text-muted-foreground"> + {label} + </div> + <div + className={`text-sm text-foreground break-all ${mono ? "font-mono text-xs" : "font-semibold"}`} + > + {value} + </div> + </div> + ); +} + +function SectionHeader({ title, action }: { title: string; action?: React.ReactNode }) { + return ( + <div className="flex items-end justify-between gap-3"> + <h2 className="text-lg font-semibold text-foreground">{title}</h2> + {action} + </div> + ); +} diff --git a/ui/src/routes/_layout/_admin/admin/system.tsx b/ui/src/routes/_layout/_admin/admin/system.tsx new file mode 100644 index 00000000..a64457a3 --- /dev/null +++ b/ui/src/routes/_layout/_admin/admin/system.tsx @@ -0,0 +1,70 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { getAccount, getActiveRuntime, getAppName, getRepository } from "@/app"; +import { Card } from "@/components"; +import { InfoRow } from "@/components/ui/info-row"; + +export const Route = createFileRoute("/_layout/_admin/admin/system")({ + loader: async ({ context }) => ({ + runtimeConfig: context.runtimeConfig, + }), + head: () => ({ + meta: [{ title: "Admin System | app" }], + }), + component: AdminSystem, +}); + +function AdminSystem() { + const { runtimeConfig } = Route.useLoaderData(); + const account = getAccount(runtimeConfig); + const appName = getAppName(runtimeConfig); + const repository = getRepository(runtimeConfig); + const runtime = getActiveRuntime(runtimeConfig); + + const env = runtimeConfig?.env; + const networkId = runtimeConfig?.networkId; + const hostUrl = runtimeConfig?.hostUrl; + const apiBase = runtimeConfig?.apiBase; + const rpcBase = runtimeConfig?.rpcBase; + const assetsUrl = runtimeConfig?.assetsUrl; + const runtimeBasePath = runtime?.runtimeBasePath; + + return ( + <div className="space-y-6"> + <header className="space-y-3"> + <div className="space-y-1"> + <h1 className="text-2xl sm:text-3xl font-bold tracking-tight text-foreground"> + System + </h1> + <p className="text-sm text-muted-foreground"> + Runtime configuration for this deployment. + </p> + </div> + </header> + + <div className="grid gap-4 sm:grid-cols-2"> + <Card className="p-6 space-y-4"> + <h2 className="text-sm font-semibold text-foreground">Runtime</h2> + <InfoRow label="account" value={runtime?.accountId ?? account} mono /> + <InfoRow label="name" value={appName} /> + <InfoRow label="base path" value={runtimeBasePath ?? "/"} mono /> + <InfoRow label="gateway" value={runtime?.gatewayId} mono /> + </Card> + + <Card className="p-6 space-y-4"> + <h2 className="text-sm font-semibold text-foreground">Deployment</h2> + <InfoRow label="env" value={env ?? "—"} mono /> + <InfoRow label="network" value={networkId ?? "—"} mono /> + <InfoRow label="host" value={hostUrl ?? "—"} mono /> + <InfoRow label="repository" value={repository} mono /> + </Card> + + <Card className="p-6 space-y-4"> + <h2 className="text-sm font-semibold text-foreground">Endpoints</h2> + <InfoRow label="api" value={apiBase} mono /> + <InfoRow label="rpc" value={rpcBase} mono /> + <InfoRow label="assets" value={assetsUrl} mono /> + </Card> + </div> + </div> + ); +} diff --git a/ui/src/routes/_layout/_authenticated.tsx b/ui/src/routes/_layout/_authenticated.tsx index 869f23e3..72eae14a 100644 --- a/ui/src/routes/_layout/_authenticated.tsx +++ b/ui/src/routes/_layout/_authenticated.tsx @@ -94,7 +94,7 @@ function AuthenticatedLayout() { const isAdmin = session?.user?.role === "admin"; const sidebarItems: SidebarItem[] = [ - { icon: Home, label: "home", to: "/home", roleRequired: "anon" }, + { icon: Home, label: "dashboard", to: "/dashboard", roleRequired: "anon" }, { icon: Shield, label: "admin", to: "/admin", roleRequired: "admin" }, { icon: MessageSquare, label: "nostr", to: "/nostr", roleRequired: "admin" }, ]; @@ -221,7 +221,7 @@ function MobileTabBar({ style={{ paddingBottom: "env(safe-area-inset-bottom, 0px)" }} > <div className="flex items-center justify-around px-2 py-1"> - <TabItem to="/home" icon={Home} label="home" active={tabActive("/home")} /> + <TabItem to="/dashboard" icon={Home} label="dashboard" active={tabActive("/dashboard")} /> <TabItem to="/apps" icon={Globe} label="apps" active={tabActive("/apps")} /> <TabItem to="/explore" icon={Compass} label="explore" active={tabActive("/explore")} /> <TabItem to="/recent" icon={ClipboardList} label="recent" active={tabActive("/recent")} /> diff --git a/ui/src/routes/_layout/_authenticated/admin/index.tsx b/ui/src/routes/_layout/_authenticated/admin/index.tsx deleted file mode 100644 index 65c3c2d3..00000000 --- a/ui/src/routes/_layout/_authenticated/admin/index.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import { createFileRoute, Link } from "@tanstack/react-router"; -import { Building2, Settings, Users } from "lucide-react"; -import { getAccount } from "@/app"; -import { Button, Card } from "@/components"; -import { PageContainer } from "@/components/layout/page-container"; - -export const Route = createFileRoute("/_layout/_authenticated/admin/")({ - head: () => ({ - meta: [{ title: "Admin Dashboard | app" }], - }), - component: AdminDashboard, -}); - -function AdminDashboard() { - const { auth } = Route.useRouteContext(); - const account = getAccount(); - const user = auth?.user ?? null; - - return ( - <PageContainer variant="wide"> - <div className="space-y-8"> - <header className="space-y-3"> - <div className="flex flex-wrap items-end justify-between gap-3"> - <div className="space-y-1"> - <h1 className="text-2xl sm:text-3xl font-bold tracking-tight text-foreground"> - Dashboard - </h1> - <p className="text-sm text-muted-foreground"> - Signed in as <span className="font-mono">{account}</span> - </p> - </div> - </div> - </header> - - <section className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4"> - <StatCard label="Account" value={account} mono /> - <StatCard label="Name" value={user?.name || user?.email || "—"} /> - <StatCard label="Role" value={user?.role ?? "—"} /> - <StatCard - label="Created" - value={user?.createdAt ? new Date(user.createdAt).toLocaleDateString() : "—"} - /> - </section> - - <section className="space-y-3"> - <h2 className="text-lg font-semibold text-foreground">Manage</h2> - <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3"> - <Card className="p-6 space-y-3"> - <div className="flex h-10 w-10 items-center justify-center rounded-[10px] bg-foreground text-background"> - <Building2 className="h-4 w-4" /> - </div> - <h3 className="text-base font-semibold text-foreground">Organizations</h3> - <p className="text-sm text-muted-foreground"> - Manage organizations, members, roles, and invitations. - </p> - <Button asChild variant="outline" size="sm"> - <Link to="/organizations"> - <Users className="h-3.5 w-3.5" /> - open organizations - </Link> - </Button> - </Card> - - <Card className="p-6 space-y-3"> - <div className="flex h-10 w-10 items-center justify-center rounded-[10px] bg-foreground text-background"> - <Settings className="h-4 w-4" /> - </div> - <h3 className="text-base font-semibold text-foreground">Settings</h3> - <p className="text-sm text-muted-foreground"> - Update your profile, auth methods, and security preferences. - </p> - <Button asChild variant="outline" size="sm"> - <Link to="/settings">open settings</Link> - </Button> - </Card> - </div> - </section> - </div> - </PageContainer> - ); -} - -function StatCard({ - label, - value, - mono, -}: { - label: string; - value: React.ReactNode; - mono?: boolean; -}) { - return ( - <div className="border-2 border-outset border-border-strong bg-card p-4 rounded-[12px] shadow-sm space-y-1"> - <div className="text-[10px] font-bold uppercase tracking-wider text-muted-foreground"> - {label} - </div> - <div - className={`text-sm text-foreground break-all ${mono ? "font-mono text-xs" : "font-semibold"}`} - > - {value} - </div> - </div> - ); -} diff --git a/ui/src/routes/_layout/_authenticated/admin/system.tsx b/ui/src/routes/_layout/_authenticated/admin/system.tsx deleted file mode 100644 index a98296af..00000000 --- a/ui/src/routes/_layout/_authenticated/admin/system.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { createFileRoute } from "@tanstack/react-router"; -import { getAccount, getActiveRuntime, getAppName, getRepository } from "@/app"; -import { Card } from "@/components"; -import { PageContainer } from "@/components/layout/page-container"; -import { InfoRow } from "@/components/ui/info-row"; - -export const Route = createFileRoute("/_layout/_authenticated/admin/system")({ - loader: async ({ context }) => ({ - runtimeConfig: context.runtimeConfig, - }), - head: () => ({ - meta: [{ title: "Admin System | app" }], - }), - component: AdminSystem, -}); - -function AdminSystem() { - const { runtimeConfig } = Route.useLoaderData(); - const account = getAccount(runtimeConfig); - const appName = getAppName(runtimeConfig); - const repository = getRepository(runtimeConfig); - const runtime = getActiveRuntime(runtimeConfig); - - const env = runtimeConfig?.env; - const networkId = runtimeConfig?.networkId; - const hostUrl = runtimeConfig?.hostUrl; - const apiBase = runtimeConfig?.apiBase; - const rpcBase = runtimeConfig?.rpcBase; - const assetsUrl = runtimeConfig?.assetsUrl; - const runtimeBasePath = runtime?.runtimeBasePath; - - return ( - <PageContainer variant="default"> - <div className="space-y-6"> - <header className="space-y-3"> - <div className="space-y-1"> - <h1 className="text-2xl sm:text-3xl font-bold tracking-tight text-foreground"> - System - </h1> - <p className="text-sm text-muted-foreground"> - Runtime configuration for this deployment. - </p> - </div> - </header> - - <div className="grid gap-4 sm:grid-cols-2"> - <Card className="p-6 space-y-4"> - <h2 className="text-sm font-semibold text-foreground">Runtime</h2> - <InfoRow label="account" value={runtime?.accountId ?? account} mono /> - <InfoRow label="name" value={appName} /> - <InfoRow label="base path" value={runtimeBasePath ?? "/"} mono /> - <InfoRow label="gateway" value={runtime?.gatewayId} mono /> - </Card> - - <Card className="p-6 space-y-4"> - <h2 className="text-sm font-semibold text-foreground">Deployment</h2> - <InfoRow label="env" value={env ?? "—"} mono /> - <InfoRow label="network" value={networkId ?? "—"} mono /> - <InfoRow label="host" value={hostUrl ?? "—"} mono /> - <InfoRow label="repository" value={repository} mono /> - </Card> - - <Card className="p-6 space-y-4"> - <h2 className="text-sm font-semibold text-foreground">Endpoints</h2> - <InfoRow label="api" value={apiBase} mono /> - <InfoRow label="rpc" value={rpcBase} mono /> - <InfoRow label="assets" value={assetsUrl} mono /> - </Card> - </div> - </div> - </PageContainer> - ); -} diff --git a/ui/src/routes/_layout/_authenticated/home.tsx b/ui/src/routes/_layout/_authenticated/dashboard.tsx similarity index 98% rename from ui/src/routes/_layout/_authenticated/home.tsx rename to ui/src/routes/_layout/_authenticated/dashboard.tsx index 1fccff0e..86f23a6c 100644 --- a/ui/src/routes/_layout/_authenticated/home.tsx +++ b/ui/src/routes/_layout/_authenticated/dashboard.tsx @@ -13,7 +13,7 @@ import { Card } from "@/components"; import { PageContainer } from "@/components/layout/page-container"; import { InfoRow } from "@/components/ui/info-row"; -export const Route = createFileRoute("/_layout/_authenticated/home")({ +export const Route = createFileRoute("/_layout/_authenticated/dashboard")({ beforeLoad: async ({ context }) => { const { apiClient, runtimeConfig } = context; const accountId = getAccount(runtimeConfig); diff --git a/ui/src/routes/_layout/apps/$accountId/$gatewayId.tsx b/ui/src/routes/_layout/_public/apps/$accountId/$gatewayId.tsx similarity index 99% rename from ui/src/routes/_layout/apps/$accountId/$gatewayId.tsx rename to ui/src/routes/_layout/_public/apps/$accountId/$gatewayId.tsx index 279e4782..52984590 100644 --- a/ui/src/routes/_layout/apps/$accountId/$gatewayId.tsx +++ b/ui/src/routes/_layout/_public/apps/$accountId/$gatewayId.tsx @@ -19,7 +19,7 @@ const getStartCommand = (accountId: string, gatewayId: string) => const getExtendsCommand = (accountId: string, gatewayId: string) => `bunx everything-dev@latest init --extends bos://${accountId}/${gatewayId}`; -export const Route = createFileRoute("/_layout/apps/$accountId/$gatewayId")({ +export const Route = createFileRoute("/_layout/_public/apps/$accountId/$gatewayId")({ loader: async ({ params, context }) => { const { queryClient, apiClient } = context; await queryClient.prefetchQuery({ diff --git a/ui/src/routes/_layout/apps/$accountId/index.tsx b/ui/src/routes/_layout/_public/apps/$accountId/index.tsx similarity index 99% rename from ui/src/routes/_layout/apps/$accountId/index.tsx rename to ui/src/routes/_layout/_public/apps/$accountId/index.tsx index 904bdaf7..0ec1eb61 100644 --- a/ui/src/routes/_layout/apps/$accountId/index.tsx +++ b/ui/src/routes/_layout/_public/apps/$accountId/index.tsx @@ -9,7 +9,7 @@ import { TooltipProvider } from "@/components/ui/tooltip"; const BASE_RUNTIME = "bos://dev.everything.near/everything.dev"; -export const Route = createFileRoute("/_layout/apps/$accountId/")({ +export const Route = createFileRoute("/_layout/_public/apps/$accountId/")({ loader: async ({ params, context }) => { const { queryClient, apiClient } = context; await queryClient.prefetchQuery({ diff --git a/ui/src/routes/_layout/apps/index.tsx b/ui/src/routes/_layout/_public/apps/index.tsx similarity index 99% rename from ui/src/routes/_layout/apps/index.tsx rename to ui/src/routes/_layout/_public/apps/index.tsx index 8ea8263f..4ce3c601 100644 --- a/ui/src/routes/_layout/apps/index.tsx +++ b/ui/src/routes/_layout/_public/apps/index.tsx @@ -21,7 +21,7 @@ type SearchParams = { preview?: string; }; -export const Route = createFileRoute("/_layout/apps/")({ +export const Route = createFileRoute("/_layout/_public/apps/")({ validateSearch: (search: Record<string, unknown>): SearchParams => ({ preview: typeof search.preview === "string" && search.preview.length > 0 ? search.preview : undefined, diff --git a/ui/src/routes/_layout/_public/login.tsx b/ui/src/routes/_layout/_public/login.tsx index fdc771d9..abc68a82 100644 --- a/ui/src/routes/_layout/_public/login.tsx +++ b/ui/src/routes/_layout/_public/login.tsx @@ -24,7 +24,7 @@ export const Route = createFileRoute("/_layout/_public/login")({ queryClient.getQueryData(sessionQueryOptions(authClient, initialSession).queryKey); if (session?.user) { - const redirectTo = search.redirect?.startsWith("/") ? search.redirect : "/home"; + const redirectTo = search.redirect?.startsWith("/") ? search.redirect : "/dashboard"; throw redirect({ to: redirectTo, search: {} }); } }, @@ -57,7 +57,7 @@ function LoginPage() { }, [auth.near]); const handleSuccess = async (message: string) => { - const redirectTo = redirect?.startsWith("/") ? redirect : "/home"; + const redirectTo = redirect?.startsWith("/") ? redirect : "/dashboard"; toast.success(message); queryClient.invalidateQueries({ queryKey: ["session"] }); navigate({ to: redirectTo, replace: true, search: {} }); @@ -110,7 +110,7 @@ function LoginPage() { }; if (session?.user) { - const redirectTo = redirect?.startsWith("/") ? redirect : "/home"; + const redirectTo = redirect?.startsWith("/") ? redirect : "/dashboard"; return <Navigate to={redirectTo} replace search={{}} />; } diff --git a/ui/src/routes/_layout/things/$thingId.tsx b/ui/src/routes/_layout/_public/things/$thingId.tsx similarity index 98% rename from ui/src/routes/_layout/things/$thingId.tsx rename to ui/src/routes/_layout/_public/things/$thingId.tsx index 0fdb7405..ac3184f4 100644 --- a/ui/src/routes/_layout/things/$thingId.tsx +++ b/ui/src/routes/_layout/_public/things/$thingId.tsx @@ -6,7 +6,7 @@ import { sessionQueryOptions, useApiClient, useAuthClient } from "@/app"; import { Badge, Button } from "@/components"; import { Skeleton } from "@/components/ui/skeleton"; -export const Route = createFileRoute("/_layout/things/$thingId")({ +export const Route = createFileRoute("/_layout/_public/things/$thingId")({ head: ({ params }) => ({ meta: [ { title: `${params.thingId} | Things | everything.dev` }, diff --git a/ui/src/routes/_layout/things/index.tsx b/ui/src/routes/_layout/_public/things/index.tsx similarity index 97% rename from ui/src/routes/_layout/things/index.tsx rename to ui/src/routes/_layout/_public/things/index.tsx index 6cbee7e0..fba26c37 100644 --- a/ui/src/routes/_layout/things/index.tsx +++ b/ui/src/routes/_layout/_public/things/index.tsx @@ -1,7 +1,7 @@ import { createFileRoute, Link, useNavigate } from "@tanstack/react-router"; import { useState } from "react"; -export const Route = createFileRoute("/_layout/things/")({ +export const Route = createFileRoute("/_layout/_public/things/")({ head: () => ({ meta: [ { title: "Things | everything.dev" }, diff --git a/ui/src/routes/_layout/things/live.tsx b/ui/src/routes/_layout/_public/things/live.tsx similarity index 98% rename from ui/src/routes/_layout/things/live.tsx rename to ui/src/routes/_layout/_public/things/live.tsx index 7be8f0c6..674d7813 100644 --- a/ui/src/routes/_layout/things/live.tsx +++ b/ui/src/routes/_layout/_public/things/live.tsx @@ -4,7 +4,7 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { useApiClient } from "@/app"; import { Badge } from "@/components"; -export const Route = createFileRoute("/_layout/things/live")({ +export const Route = createFileRoute("/_layout/_public/things/live")({ head: () => ({ meta: [ { title: "Live Stream | Things | everything.dev" }, From 9c90224a15ff2a60e38d7eade3f0f6e20f95ab15 Mon Sep 17 00:00:00 2001 From: Elliot Braem <elliot@ejlbraem.com> Date: Thu, 13 Aug 2026 17:44:55 -0500 Subject: [PATCH 06/17] test(regression): update /home references to /dashboard after route rename --- tests/regression/browser/helpers/seeded.ts | 2 +- tests/regression/browser/specs/auth-client.spec.ts | 4 ++-- tests/regression/browser/specs/auth-redirect.spec.ts | 4 ++-- tests/regression/browser/specs/logout.spec.ts | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/regression/browser/helpers/seeded.ts b/tests/regression/browser/helpers/seeded.ts index 560e0aad..c580e4f9 100644 --- a/tests/regression/browser/helpers/seeded.ts +++ b/tests/regression/browser/helpers/seeded.ts @@ -42,7 +42,7 @@ export function loadSeedData(): SeedData { } export async function verifyAuthenticated(page: Page) { - await page.goto("/home", { waitUntil: "domcontentloaded" }); + await page.goto("/dashboard", { waitUntil: "domcontentloaded" }); await page.waitForTimeout(500); await page.waitForLoadState("networkidle"); await expect(page.locator("button[title='account menu']")).toBeVisible({ timeout: 10000 }); diff --git a/tests/regression/browser/specs/auth-client.spec.ts b/tests/regression/browser/specs/auth-client.spec.ts index bda4857d..970989f7 100644 --- a/tests/regression/browser/specs/auth-client.spec.ts +++ b/tests/regression/browser/specs/auth-client.spec.ts @@ -58,9 +58,9 @@ test.describe("authClient", () => { await anonymousBtn.click(); await signInDone; - await page.waitForURL(/\/home$/, { timeout: 15000 }); + await page.waitForURL(/\/dashboard$/, { timeout: 15000 }); await page.reload(); - await page.waitForURL(/\/home$/, { timeout: 15000 }); + await page.waitForURL(/\/dashboard$/, { timeout: 15000 }); await page.waitForLoadState("networkidle"); await waitForApp(page); diff --git a/tests/regression/browser/specs/auth-redirect.spec.ts b/tests/regression/browser/specs/auth-redirect.spec.ts index f0b3aede..afbaf19a 100644 --- a/tests/regression/browser/specs/auth-redirect.spec.ts +++ b/tests/regression/browser/specs/auth-redirect.spec.ts @@ -26,8 +26,8 @@ test.describe("Auth redirect", () => { expectNoHydrationFailure(pageErrors); }); - test("unauthenticated /home redirects to /login", async ({ page }) => { - await page.goto("/home", { waitUntil: "domcontentloaded" }); + test("unauthenticated /dashboard redirects to /login", async ({ page }) => { + await page.goto("/dashboard", { waitUntil: "domcontentloaded" }); await waitForApp(page); await page.waitForURL(/\/login/, { timeout: 15000 }); diff --git a/tests/regression/browser/specs/logout.spec.ts b/tests/regression/browser/specs/logout.spec.ts index cef410e9..97642bcd 100644 --- a/tests/regression/browser/specs/logout.spec.ts +++ b/tests/regression/browser/specs/logout.spec.ts @@ -23,9 +23,9 @@ test.describe("logout", () => { await anonymousBtn.click(); await signInDone; - await page.waitForURL(/\/home$/, { timeout: 15000 }); + await page.waitForURL(/\/dashboard$/, { timeout: 15000 }); await page.reload(); - await page.waitForURL(/\/home$/, { timeout: 15000 }); + await page.waitForURL(/\/dashboard$/, { timeout: 15000 }); await page.waitForLoadState("networkidle"); await waitForApp(page); await page.locator("button[title='account menu']").click(); From 7f1b9dc11e98cdd98a404914dcde0c2bbb0bffb2 Mon Sep 17 00:00:00 2001 From: Elliot Braem <elliot@ejlbraem.com> Date: Thu, 13 Aug 2026 17:56:16 -0500 Subject: [PATCH 07/17] refactor(ui): add _anon mount for login, rename organizations to orgs, remove nostr - move login from public layout into new _anon pathless layout that redirects authed users to /dashboard and provides theme toggle header - rename organization route group from /organizations to /orgs; move invitation acceptance to /orgs/invites/$id - remove stale nostr entry from authenticated sidebar - changeset: ui-anon-orgs --- .changeset/ui-anon-orgs.md | 9 + ui/src/components/org-switcher.tsx | 2 +- ui/src/components/user-nav.tsx | 2 +- ui/src/routeTree.gen.ts | 224 ++++++++++-------- ui/src/routes/_layout/_admin/admin.tsx | 4 +- ui/src/routes/_layout/_admin/admin/index.tsx | 4 +- ui/src/routes/_layout/_anon.tsx | 57 +++++ .../_layout/{_public => _anon}/login.tsx | 4 +- ui/src/routes/_layout/_authenticated.tsx | 3 +- .../{organizations => orgs}/$slug.tsx | 12 +- .../{organizations => orgs}/index.tsx | 10 +- .../invites.$id.tsx} | 10 +- .../{organizations => orgs}/new.tsx | 10 +- .../_authenticated/tenant/$tenantId.tsx | 2 +- 14 files changed, 220 insertions(+), 133 deletions(-) create mode 100644 .changeset/ui-anon-orgs.md create mode 100644 ui/src/routes/_layout/_anon.tsx rename ui/src/routes/_layout/{_public => _anon}/login.tsx (98%) rename ui/src/routes/_layout/_authenticated/{organizations => orgs}/$slug.tsx (99%) rename ui/src/routes/_layout/_authenticated/{organizations => orgs}/index.tsx (98%) rename ui/src/routes/_layout/_authenticated/{accept-invitation.$id.tsx => orgs/invites.$id.tsx} (95%) rename ui/src/routes/_layout/_authenticated/{organizations => orgs}/new.tsx (96%) diff --git a/.changeset/ui-anon-orgs.md b/.changeset/ui-anon-orgs.md new file mode 100644 index 00000000..d1e2a805 --- /dev/null +++ b/.changeset/ui-anon-orgs.md @@ -0,0 +1,9 @@ +--- +"ui": minor +--- + +Add a dedicated anonymous mount and reorganize organization routes. + +- Add a `/_layout/_anon` pathless layout for pre-auth pages. Move login from the public layout into it; the layout redirects authenticated users to `/dashboard` and provides the theme toggle header. +- Rename the organization route group from `/organizations` to `/orgs` (`/orgs`, `/orgs/new`, `/orgs/$slug`) and move invitation acceptance to `/orgs/invites/$id`. +- Remove the stale nostr entry from the authenticated sidebar. \ No newline at end of file diff --git a/ui/src/components/org-switcher.tsx b/ui/src/components/org-switcher.tsx index 8d4eada0..7760d57f 100644 --- a/ui/src/components/org-switcher.tsx +++ b/ui/src/components/org-switcher.tsx @@ -64,7 +64,7 @@ export function OrgSwitcher({ organizations, activeOrgId, onSwitch }: OrgSwitche )} <DropdownMenuSeparator /> <DropdownMenuItem asChild> - <Link to="/organizations/new" className="flex items-center gap-2 cursor-pointer"> + <Link to="/orgs/new" className="flex items-center gap-2 cursor-pointer"> <Plus className="h-3.5 w-3.5" /> new organization </Link> diff --git a/ui/src/components/user-nav.tsx b/ui/src/components/user-nav.tsx index ba6a6107..084aca2f 100644 --- a/ui/src/components/user-nav.tsx +++ b/ui/src/components/user-nav.tsx @@ -151,7 +151,7 @@ export function UserNav() { </DropdownMenuItem> {activeOrg && ( <DropdownMenuItem asChild> - <Link to="/organizations/$slug" params={{ slug: activeOrg.slug }}> + <Link to="/orgs/$slug" params={{ slug: activeOrg.slug }}> <Building2 /> {activeOrg.name} </Link> diff --git a/ui/src/routeTree.gen.ts b/ui/src/routeTree.gen.ts index 359bd53d..4ad07bcf 100644 --- a/ui/src/routeTree.gen.ts +++ b/ui/src/routeTree.gen.ts @@ -12,19 +12,20 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as LayoutRouteImport } from './routes/_layout' import { Route as LayoutPublicRouteImport } from './routes/_layout/_public' import { Route as LayoutAuthenticatedRouteImport } from './routes/_layout/_authenticated' +import { Route as LayoutAnonRouteImport } from './routes/_layout/_anon' import { Route as LayoutAdminRouteImport } from './routes/_layout/_admin' import { Route as LayoutPublicIndexRouteImport } from './routes/_layout/_public/index' import { Route as LayoutPublicSkillRouteImport } from './routes/_layout/_public/skill' -import { Route as LayoutPublicLoginRouteImport } from './routes/_layout/_public/login' import { Route as LayoutPublicAboutRouteImport } from './routes/_layout/_public/about' import { Route as LayoutPublicAccountIdRouteImport } from './routes/_layout/_public/$accountId' import { Route as LayoutAuthenticatedSettingsRouteImport } from './routes/_layout/_authenticated/settings' import { Route as LayoutAuthenticatedDashboardRouteImport } from './routes/_layout/_authenticated/dashboard' +import { Route as LayoutAnonLoginRouteImport } from './routes/_layout/_anon/login' import { Route as LayoutAdminAdminRouteImport } from './routes/_layout/_admin/admin' import { Route as LayoutPublicThingsIndexRouteImport } from './routes/_layout/_public/things/index' import { Route as LayoutPublicAppsIndexRouteImport } from './routes/_layout/_public/apps/index' import { Route as LayoutAuthenticatedSettingsIndexRouteImport } from './routes/_layout/_authenticated/settings/index' -import { Route as LayoutAuthenticatedOrganizationsIndexRouteImport } from './routes/_layout/_authenticated/organizations/index' +import { Route as LayoutAuthenticatedOrgsIndexRouteImport } from './routes/_layout/_authenticated/orgs/index' import { Route as LayoutAdminAdminIndexRouteImport } from './routes/_layout/_admin/admin/index' import { Route as LayoutPublicThingsLiveRouteImport } from './routes/_layout/_public/things/live' import { Route as LayoutPublicThingsThingIdRouteImport } from './routes/_layout/_public/things/$thingId' @@ -34,12 +35,12 @@ import { Route as LayoutAuthenticatedTenantTenantIdRouteImport } from './routes/ import { Route as LayoutAuthenticatedSettingsSecurityRouteImport } from './routes/_layout/_authenticated/settings/security' import { Route as LayoutAuthenticatedSettingsProfileRouteImport } from './routes/_layout/_authenticated/settings/profile' import { Route as LayoutAuthenticatedSettingsAuthMethodsRouteImport } from './routes/_layout/_authenticated/settings/auth-methods' -import { Route as LayoutAuthenticatedOrganizationsNewRouteImport } from './routes/_layout/_authenticated/organizations/new' -import { Route as LayoutAuthenticatedOrganizationsSlugRouteImport } from './routes/_layout/_authenticated/organizations/$slug' -import { Route as LayoutAuthenticatedAcceptInvitationIdRouteImport } from './routes/_layout/_authenticated/accept-invitation.$id' +import { Route as LayoutAuthenticatedOrgsNewRouteImport } from './routes/_layout/_authenticated/orgs/new' +import { Route as LayoutAuthenticatedOrgsSlugRouteImport } from './routes/_layout/_authenticated/orgs/$slug' import { Route as LayoutAdminAdminSystemRouteImport } from './routes/_layout/_admin/admin/system' import { Route as LayoutPublicAppsAccountIdIndexRouteImport } from './routes/_layout/_public/apps/$accountId/index' import { Route as LayoutPublicAppsAccountIdGatewayIdRouteImport } from './routes/_layout/_public/apps/$accountId/$gatewayId' +import { Route as LayoutAuthenticatedOrgsInvitesIdRouteImport } from './routes/_layout/_authenticated/orgs/invites.$id' const LayoutRoute = LayoutRouteImport.update({ id: '/_layout', @@ -53,6 +54,10 @@ const LayoutAuthenticatedRoute = LayoutAuthenticatedRouteImport.update({ id: '/_authenticated', getParentRoute: () => LayoutRoute, } as any) +const LayoutAnonRoute = LayoutAnonRouteImport.update({ + id: '/_anon', + getParentRoute: () => LayoutRoute, +} as any) const LayoutAdminRoute = LayoutAdminRouteImport.update({ id: '/_admin', getParentRoute: () => LayoutRoute, @@ -67,11 +72,6 @@ const LayoutPublicSkillRoute = LayoutPublicSkillRouteImport.update({ path: '/skill', getParentRoute: () => LayoutPublicRoute, } as any) -const LayoutPublicLoginRoute = LayoutPublicLoginRouteImport.update({ - id: '/login', - path: '/login', - getParentRoute: () => LayoutPublicRoute, -} as any) const LayoutPublicAboutRoute = LayoutPublicAboutRouteImport.update({ id: '/about', path: '/about', @@ -94,6 +94,11 @@ const LayoutAuthenticatedDashboardRoute = path: '/dashboard', getParentRoute: () => LayoutAuthenticatedRoute, } as any) +const LayoutAnonLoginRoute = LayoutAnonLoginRouteImport.update({ + id: '/login', + path: '/login', + getParentRoute: () => LayoutAnonRoute, +} as any) const LayoutAdminAdminRoute = LayoutAdminAdminRouteImport.update({ id: '/admin', path: '/admin', @@ -115,10 +120,10 @@ const LayoutAuthenticatedSettingsIndexRoute = path: '/', getParentRoute: () => LayoutAuthenticatedSettingsRoute, } as any) -const LayoutAuthenticatedOrganizationsIndexRoute = - LayoutAuthenticatedOrganizationsIndexRouteImport.update({ - id: '/organizations/', - path: '/organizations/', +const LayoutAuthenticatedOrgsIndexRoute = + LayoutAuthenticatedOrgsIndexRouteImport.update({ + id: '/orgs/', + path: '/orgs/', getParentRoute: () => LayoutAuthenticatedRoute, } as any) const LayoutAdminAdminIndexRoute = LayoutAdminAdminIndexRouteImport.update({ @@ -173,22 +178,16 @@ const LayoutAuthenticatedSettingsAuthMethodsRoute = path: '/auth-methods', getParentRoute: () => LayoutAuthenticatedSettingsRoute, } as any) -const LayoutAuthenticatedOrganizationsNewRoute = - LayoutAuthenticatedOrganizationsNewRouteImport.update({ - id: '/organizations/new', - path: '/organizations/new', +const LayoutAuthenticatedOrgsNewRoute = + LayoutAuthenticatedOrgsNewRouteImport.update({ + id: '/orgs/new', + path: '/orgs/new', getParentRoute: () => LayoutAuthenticatedRoute, } as any) -const LayoutAuthenticatedOrganizationsSlugRoute = - LayoutAuthenticatedOrganizationsSlugRouteImport.update({ - id: '/organizations/$slug', - path: '/organizations/$slug', - getParentRoute: () => LayoutAuthenticatedRoute, - } as any) -const LayoutAuthenticatedAcceptInvitationIdRoute = - LayoutAuthenticatedAcceptInvitationIdRouteImport.update({ - id: '/accept-invitation/$id', - path: '/accept-invitation/$id', +const LayoutAuthenticatedOrgsSlugRoute = + LayoutAuthenticatedOrgsSlugRouteImport.update({ + id: '/orgs/$slug', + path: '/orgs/$slug', getParentRoute: () => LayoutAuthenticatedRoute, } as any) const LayoutAdminAdminSystemRoute = LayoutAdminAdminSystemRouteImport.update({ @@ -208,20 +207,25 @@ const LayoutPublicAppsAccountIdGatewayIdRoute = path: '/apps/$accountId/$gatewayId', getParentRoute: () => LayoutPublicRoute, } as any) +const LayoutAuthenticatedOrgsInvitesIdRoute = + LayoutAuthenticatedOrgsInvitesIdRouteImport.update({ + id: '/orgs/invites/$id', + path: '/orgs/invites/$id', + getParentRoute: () => LayoutAuthenticatedRoute, + } as any) export interface FileRoutesByFullPath { '/': typeof LayoutPublicIndexRoute '/admin': typeof LayoutAdminAdminRouteWithChildren + '/login': typeof LayoutAnonLoginRoute '/dashboard': typeof LayoutAuthenticatedDashboardRoute '/settings': typeof LayoutAuthenticatedSettingsRouteWithChildren '/$accountId': typeof LayoutPublicAccountIdRoute '/about': typeof LayoutPublicAboutRoute - '/login': typeof LayoutPublicLoginRoute '/skill': typeof LayoutPublicSkillRoute '/admin/system': typeof LayoutAdminAdminSystemRoute - '/accept-invitation/$id': typeof LayoutAuthenticatedAcceptInvitationIdRoute - '/organizations/$slug': typeof LayoutAuthenticatedOrganizationsSlugRoute - '/organizations/new': typeof LayoutAuthenticatedOrganizationsNewRoute + '/orgs/$slug': typeof LayoutAuthenticatedOrgsSlugRoute + '/orgs/new': typeof LayoutAuthenticatedOrgsNewRoute '/settings/auth-methods': typeof LayoutAuthenticatedSettingsAuthMethodsRoute '/settings/profile': typeof LayoutAuthenticatedSettingsProfileRoute '/settings/security': typeof LayoutAuthenticatedSettingsSecurityRoute @@ -231,24 +235,24 @@ export interface FileRoutesByFullPath { '/things/$thingId': typeof LayoutPublicThingsThingIdRoute '/things/live': typeof LayoutPublicThingsLiveRoute '/admin/': typeof LayoutAdminAdminIndexRoute - '/organizations/': typeof LayoutAuthenticatedOrganizationsIndexRoute + '/orgs/': typeof LayoutAuthenticatedOrgsIndexRoute '/settings/': typeof LayoutAuthenticatedSettingsIndexRoute '/apps/': typeof LayoutPublicAppsIndexRoute '/things/': typeof LayoutPublicThingsIndexRoute + '/orgs/invites/$id': typeof LayoutAuthenticatedOrgsInvitesIdRoute '/apps/$accountId/$gatewayId': typeof LayoutPublicAppsAccountIdGatewayIdRoute '/apps/$accountId/': typeof LayoutPublicAppsAccountIdIndexRoute } export interface FileRoutesByTo { '/': typeof LayoutPublicIndexRoute + '/login': typeof LayoutAnonLoginRoute '/dashboard': typeof LayoutAuthenticatedDashboardRoute '/$accountId': typeof LayoutPublicAccountIdRoute '/about': typeof LayoutPublicAboutRoute - '/login': typeof LayoutPublicLoginRoute '/skill': typeof LayoutPublicSkillRoute '/admin/system': typeof LayoutAdminAdminSystemRoute - '/accept-invitation/$id': typeof LayoutAuthenticatedAcceptInvitationIdRoute - '/organizations/$slug': typeof LayoutAuthenticatedOrganizationsSlugRoute - '/organizations/new': typeof LayoutAuthenticatedOrganizationsNewRoute + '/orgs/$slug': typeof LayoutAuthenticatedOrgsSlugRoute + '/orgs/new': typeof LayoutAuthenticatedOrgsNewRoute '/settings/auth-methods': typeof LayoutAuthenticatedSettingsAuthMethodsRoute '/settings/profile': typeof LayoutAuthenticatedSettingsProfileRoute '/settings/security': typeof LayoutAuthenticatedSettingsSecurityRoute @@ -258,10 +262,11 @@ export interface FileRoutesByTo { '/things/$thingId': typeof LayoutPublicThingsThingIdRoute '/things/live': typeof LayoutPublicThingsLiveRoute '/admin': typeof LayoutAdminAdminIndexRoute - '/organizations': typeof LayoutAuthenticatedOrganizationsIndexRoute + '/orgs': typeof LayoutAuthenticatedOrgsIndexRoute '/settings': typeof LayoutAuthenticatedSettingsIndexRoute '/apps': typeof LayoutPublicAppsIndexRoute '/things': typeof LayoutPublicThingsIndexRoute + '/orgs/invites/$id': typeof LayoutAuthenticatedOrgsInvitesIdRoute '/apps/$accountId/$gatewayId': typeof LayoutPublicAppsAccountIdGatewayIdRoute '/apps/$accountId': typeof LayoutPublicAppsAccountIdIndexRoute } @@ -269,20 +274,20 @@ export interface FileRoutesById { __root__: typeof rootRouteImport '/_layout': typeof LayoutRouteWithChildren '/_layout/_admin': typeof LayoutAdminRouteWithChildren + '/_layout/_anon': typeof LayoutAnonRouteWithChildren '/_layout/_authenticated': typeof LayoutAuthenticatedRouteWithChildren '/_layout/_public': typeof LayoutPublicRouteWithChildren '/_layout/_admin/admin': typeof LayoutAdminAdminRouteWithChildren + '/_layout/_anon/login': typeof LayoutAnonLoginRoute '/_layout/_authenticated/dashboard': typeof LayoutAuthenticatedDashboardRoute '/_layout/_authenticated/settings': typeof LayoutAuthenticatedSettingsRouteWithChildren '/_layout/_public/$accountId': typeof LayoutPublicAccountIdRoute '/_layout/_public/about': typeof LayoutPublicAboutRoute - '/_layout/_public/login': typeof LayoutPublicLoginRoute '/_layout/_public/skill': typeof LayoutPublicSkillRoute '/_layout/_public/': typeof LayoutPublicIndexRoute '/_layout/_admin/admin/system': typeof LayoutAdminAdminSystemRoute - '/_layout/_authenticated/accept-invitation/$id': typeof LayoutAuthenticatedAcceptInvitationIdRoute - '/_layout/_authenticated/organizations/$slug': typeof LayoutAuthenticatedOrganizationsSlugRoute - '/_layout/_authenticated/organizations/new': typeof LayoutAuthenticatedOrganizationsNewRoute + '/_layout/_authenticated/orgs/$slug': typeof LayoutAuthenticatedOrgsSlugRoute + '/_layout/_authenticated/orgs/new': typeof LayoutAuthenticatedOrgsNewRoute '/_layout/_authenticated/settings/auth-methods': typeof LayoutAuthenticatedSettingsAuthMethodsRoute '/_layout/_authenticated/settings/profile': typeof LayoutAuthenticatedSettingsProfileRoute '/_layout/_authenticated/settings/security': typeof LayoutAuthenticatedSettingsSecurityRoute @@ -292,10 +297,11 @@ export interface FileRoutesById { '/_layout/_public/things/$thingId': typeof LayoutPublicThingsThingIdRoute '/_layout/_public/things/live': typeof LayoutPublicThingsLiveRoute '/_layout/_admin/admin/': typeof LayoutAdminAdminIndexRoute - '/_layout/_authenticated/organizations/': typeof LayoutAuthenticatedOrganizationsIndexRoute + '/_layout/_authenticated/orgs/': typeof LayoutAuthenticatedOrgsIndexRoute '/_layout/_authenticated/settings/': typeof LayoutAuthenticatedSettingsIndexRoute '/_layout/_public/apps/': typeof LayoutPublicAppsIndexRoute '/_layout/_public/things/': typeof LayoutPublicThingsIndexRoute + '/_layout/_authenticated/orgs/invites/$id': typeof LayoutAuthenticatedOrgsInvitesIdRoute '/_layout/_public/apps/$accountId/$gatewayId': typeof LayoutPublicAppsAccountIdGatewayIdRoute '/_layout/_public/apps/$accountId/': typeof LayoutPublicAppsAccountIdIndexRoute } @@ -304,16 +310,15 @@ export interface FileRouteTypes { fullPaths: | '/' | '/admin' + | '/login' | '/dashboard' | '/settings' | '/$accountId' | '/about' - | '/login' | '/skill' | '/admin/system' - | '/accept-invitation/$id' - | '/organizations/$slug' - | '/organizations/new' + | '/orgs/$slug' + | '/orgs/new' | '/settings/auth-methods' | '/settings/profile' | '/settings/security' @@ -323,24 +328,24 @@ export interface FileRouteTypes { | '/things/$thingId' | '/things/live' | '/admin/' - | '/organizations/' + | '/orgs/' | '/settings/' | '/apps/' | '/things/' + | '/orgs/invites/$id' | '/apps/$accountId/$gatewayId' | '/apps/$accountId/' fileRoutesByTo: FileRoutesByTo to: | '/' + | '/login' | '/dashboard' | '/$accountId' | '/about' - | '/login' | '/skill' | '/admin/system' - | '/accept-invitation/$id' - | '/organizations/$slug' - | '/organizations/new' + | '/orgs/$slug' + | '/orgs/new' | '/settings/auth-methods' | '/settings/profile' | '/settings/security' @@ -350,30 +355,31 @@ export interface FileRouteTypes { | '/things/$thingId' | '/things/live' | '/admin' - | '/organizations' + | '/orgs' | '/settings' | '/apps' | '/things' + | '/orgs/invites/$id' | '/apps/$accountId/$gatewayId' | '/apps/$accountId' id: | '__root__' | '/_layout' | '/_layout/_admin' + | '/_layout/_anon' | '/_layout/_authenticated' | '/_layout/_public' | '/_layout/_admin/admin' + | '/_layout/_anon/login' | '/_layout/_authenticated/dashboard' | '/_layout/_authenticated/settings' | '/_layout/_public/$accountId' | '/_layout/_public/about' - | '/_layout/_public/login' | '/_layout/_public/skill' | '/_layout/_public/' | '/_layout/_admin/admin/system' - | '/_layout/_authenticated/accept-invitation/$id' - | '/_layout/_authenticated/organizations/$slug' - | '/_layout/_authenticated/organizations/new' + | '/_layout/_authenticated/orgs/$slug' + | '/_layout/_authenticated/orgs/new' | '/_layout/_authenticated/settings/auth-methods' | '/_layout/_authenticated/settings/profile' | '/_layout/_authenticated/settings/security' @@ -383,10 +389,11 @@ export interface FileRouteTypes { | '/_layout/_public/things/$thingId' | '/_layout/_public/things/live' | '/_layout/_admin/admin/' - | '/_layout/_authenticated/organizations/' + | '/_layout/_authenticated/orgs/' | '/_layout/_authenticated/settings/' | '/_layout/_public/apps/' | '/_layout/_public/things/' + | '/_layout/_authenticated/orgs/invites/$id' | '/_layout/_public/apps/$accountId/$gatewayId' | '/_layout/_public/apps/$accountId/' fileRoutesById: FileRoutesById @@ -418,6 +425,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutAuthenticatedRouteImport parentRoute: typeof LayoutRoute } + '/_layout/_anon': { + id: '/_layout/_anon' + path: '' + fullPath: '/' + preLoaderRoute: typeof LayoutAnonRouteImport + parentRoute: typeof LayoutRoute + } '/_layout/_admin': { id: '/_layout/_admin' path: '' @@ -439,13 +453,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutPublicSkillRouteImport parentRoute: typeof LayoutPublicRoute } - '/_layout/_public/login': { - id: '/_layout/_public/login' - path: '/login' - fullPath: '/login' - preLoaderRoute: typeof LayoutPublicLoginRouteImport - parentRoute: typeof LayoutPublicRoute - } '/_layout/_public/about': { id: '/_layout/_public/about' path: '/about' @@ -474,6 +481,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutAuthenticatedDashboardRouteImport parentRoute: typeof LayoutAuthenticatedRoute } + '/_layout/_anon/login': { + id: '/_layout/_anon/login' + path: '/login' + fullPath: '/login' + preLoaderRoute: typeof LayoutAnonLoginRouteImport + parentRoute: typeof LayoutAnonRoute + } '/_layout/_admin/admin': { id: '/_layout/_admin/admin' path: '/admin' @@ -502,11 +516,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutAuthenticatedSettingsIndexRouteImport parentRoute: typeof LayoutAuthenticatedSettingsRoute } - '/_layout/_authenticated/organizations/': { - id: '/_layout/_authenticated/organizations/' - path: '/organizations' - fullPath: '/organizations/' - preLoaderRoute: typeof LayoutAuthenticatedOrganizationsIndexRouteImport + '/_layout/_authenticated/orgs/': { + id: '/_layout/_authenticated/orgs/' + path: '/orgs' + fullPath: '/orgs/' + preLoaderRoute: typeof LayoutAuthenticatedOrgsIndexRouteImport parentRoute: typeof LayoutAuthenticatedRoute } '/_layout/_admin/admin/': { @@ -572,25 +586,18 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutAuthenticatedSettingsAuthMethodsRouteImport parentRoute: typeof LayoutAuthenticatedSettingsRoute } - '/_layout/_authenticated/organizations/new': { - id: '/_layout/_authenticated/organizations/new' - path: '/organizations/new' - fullPath: '/organizations/new' - preLoaderRoute: typeof LayoutAuthenticatedOrganizationsNewRouteImport + '/_layout/_authenticated/orgs/new': { + id: '/_layout/_authenticated/orgs/new' + path: '/orgs/new' + fullPath: '/orgs/new' + preLoaderRoute: typeof LayoutAuthenticatedOrgsNewRouteImport parentRoute: typeof LayoutAuthenticatedRoute } - '/_layout/_authenticated/organizations/$slug': { - id: '/_layout/_authenticated/organizations/$slug' - path: '/organizations/$slug' - fullPath: '/organizations/$slug' - preLoaderRoute: typeof LayoutAuthenticatedOrganizationsSlugRouteImport - parentRoute: typeof LayoutAuthenticatedRoute - } - '/_layout/_authenticated/accept-invitation/$id': { - id: '/_layout/_authenticated/accept-invitation/$id' - path: '/accept-invitation/$id' - fullPath: '/accept-invitation/$id' - preLoaderRoute: typeof LayoutAuthenticatedAcceptInvitationIdRouteImport + '/_layout/_authenticated/orgs/$slug': { + id: '/_layout/_authenticated/orgs/$slug' + path: '/orgs/$slug' + fullPath: '/orgs/$slug' + preLoaderRoute: typeof LayoutAuthenticatedOrgsSlugRouteImport parentRoute: typeof LayoutAuthenticatedRoute } '/_layout/_admin/admin/system': { @@ -614,6 +621,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutPublicAppsAccountIdGatewayIdRouteImport parentRoute: typeof LayoutPublicRoute } + '/_layout/_authenticated/orgs/invites/$id': { + id: '/_layout/_authenticated/orgs/invites/$id' + path: '/orgs/invites/$id' + fullPath: '/orgs/invites/$id' + preLoaderRoute: typeof LayoutAuthenticatedOrgsInvitesIdRouteImport + parentRoute: typeof LayoutAuthenticatedRoute + } } } @@ -642,6 +656,18 @@ const LayoutAdminRouteWithChildren = LayoutAdminRoute._addFileChildren( LayoutAdminRouteChildren, ) +interface LayoutAnonRouteChildren { + LayoutAnonLoginRoute: typeof LayoutAnonLoginRoute +} + +const LayoutAnonRouteChildren: LayoutAnonRouteChildren = { + LayoutAnonLoginRoute: LayoutAnonLoginRoute, +} + +const LayoutAnonRouteWithChildren = LayoutAnonRoute._addFileChildren( + LayoutAnonRouteChildren, +) + interface LayoutAuthenticatedSettingsRouteChildren { LayoutAuthenticatedSettingsAuthMethodsRoute: typeof LayoutAuthenticatedSettingsAuthMethodsRoute LayoutAuthenticatedSettingsProfileRoute: typeof LayoutAuthenticatedSettingsProfileRoute @@ -669,31 +695,27 @@ const LayoutAuthenticatedSettingsRouteWithChildren = interface LayoutAuthenticatedRouteChildren { LayoutAuthenticatedDashboardRoute: typeof LayoutAuthenticatedDashboardRoute LayoutAuthenticatedSettingsRoute: typeof LayoutAuthenticatedSettingsRouteWithChildren - LayoutAuthenticatedAcceptInvitationIdRoute: typeof LayoutAuthenticatedAcceptInvitationIdRoute - LayoutAuthenticatedOrganizationsSlugRoute: typeof LayoutAuthenticatedOrganizationsSlugRoute - LayoutAuthenticatedOrganizationsNewRoute: typeof LayoutAuthenticatedOrganizationsNewRoute + LayoutAuthenticatedOrgsSlugRoute: typeof LayoutAuthenticatedOrgsSlugRoute + LayoutAuthenticatedOrgsNewRoute: typeof LayoutAuthenticatedOrgsNewRoute LayoutAuthenticatedTenantTenantIdRoute: typeof LayoutAuthenticatedTenantTenantIdRoute LayoutAuthenticatedTenantNewRoute: typeof LayoutAuthenticatedTenantNewRoute LayoutAuthenticatedThingsNewRoute: typeof LayoutAuthenticatedThingsNewRoute - LayoutAuthenticatedOrganizationsIndexRoute: typeof LayoutAuthenticatedOrganizationsIndexRoute + LayoutAuthenticatedOrgsIndexRoute: typeof LayoutAuthenticatedOrgsIndexRoute + LayoutAuthenticatedOrgsInvitesIdRoute: typeof LayoutAuthenticatedOrgsInvitesIdRoute } const LayoutAuthenticatedRouteChildren: LayoutAuthenticatedRouteChildren = { LayoutAuthenticatedDashboardRoute: LayoutAuthenticatedDashboardRoute, LayoutAuthenticatedSettingsRoute: LayoutAuthenticatedSettingsRouteWithChildren, - LayoutAuthenticatedAcceptInvitationIdRoute: - LayoutAuthenticatedAcceptInvitationIdRoute, - LayoutAuthenticatedOrganizationsSlugRoute: - LayoutAuthenticatedOrganizationsSlugRoute, - LayoutAuthenticatedOrganizationsNewRoute: - LayoutAuthenticatedOrganizationsNewRoute, + LayoutAuthenticatedOrgsSlugRoute: LayoutAuthenticatedOrgsSlugRoute, + LayoutAuthenticatedOrgsNewRoute: LayoutAuthenticatedOrgsNewRoute, LayoutAuthenticatedTenantTenantIdRoute: LayoutAuthenticatedTenantTenantIdRoute, LayoutAuthenticatedTenantNewRoute: LayoutAuthenticatedTenantNewRoute, LayoutAuthenticatedThingsNewRoute: LayoutAuthenticatedThingsNewRoute, - LayoutAuthenticatedOrganizationsIndexRoute: - LayoutAuthenticatedOrganizationsIndexRoute, + LayoutAuthenticatedOrgsIndexRoute: LayoutAuthenticatedOrgsIndexRoute, + LayoutAuthenticatedOrgsInvitesIdRoute: LayoutAuthenticatedOrgsInvitesIdRoute, } const LayoutAuthenticatedRouteWithChildren = @@ -702,7 +724,6 @@ const LayoutAuthenticatedRouteWithChildren = interface LayoutPublicRouteChildren { LayoutPublicAccountIdRoute: typeof LayoutPublicAccountIdRoute LayoutPublicAboutRoute: typeof LayoutPublicAboutRoute - LayoutPublicLoginRoute: typeof LayoutPublicLoginRoute LayoutPublicSkillRoute: typeof LayoutPublicSkillRoute LayoutPublicIndexRoute: typeof LayoutPublicIndexRoute LayoutPublicThingsThingIdRoute: typeof LayoutPublicThingsThingIdRoute @@ -716,7 +737,6 @@ interface LayoutPublicRouteChildren { const LayoutPublicRouteChildren: LayoutPublicRouteChildren = { LayoutPublicAccountIdRoute: LayoutPublicAccountIdRoute, LayoutPublicAboutRoute: LayoutPublicAboutRoute, - LayoutPublicLoginRoute: LayoutPublicLoginRoute, LayoutPublicSkillRoute: LayoutPublicSkillRoute, LayoutPublicIndexRoute: LayoutPublicIndexRoute, LayoutPublicThingsThingIdRoute: LayoutPublicThingsThingIdRoute, @@ -734,12 +754,14 @@ const LayoutPublicRouteWithChildren = LayoutPublicRoute._addFileChildren( interface LayoutRouteChildren { LayoutAdminRoute: typeof LayoutAdminRouteWithChildren + LayoutAnonRoute: typeof LayoutAnonRouteWithChildren LayoutAuthenticatedRoute: typeof LayoutAuthenticatedRouteWithChildren LayoutPublicRoute: typeof LayoutPublicRouteWithChildren } const LayoutRouteChildren: LayoutRouteChildren = { LayoutAdminRoute: LayoutAdminRouteWithChildren, + LayoutAnonRoute: LayoutAnonRouteWithChildren, LayoutAuthenticatedRoute: LayoutAuthenticatedRouteWithChildren, LayoutPublicRoute: LayoutPublicRouteWithChildren, } diff --git a/ui/src/routes/_layout/_admin/admin.tsx b/ui/src/routes/_layout/_admin/admin.tsx index e57fd806..9fee1727 100644 --- a/ui/src/routes/_layout/_admin/admin.tsx +++ b/ui/src/routes/_layout/_admin/admin.tsx @@ -55,7 +55,7 @@ function AdminPage() { home </Link> <Link - to="/organizations" + to="/orgs" className="h-10 px-4 inline-flex items-center gap-1.5 text-sm font-medium border-2 border-outset border-border-strong bg-card text-foreground shadow-sm hover:shadow-md active:border-inset active:shadow-none transition-all duration-200 ease-out rounded-[12px]" > organizations @@ -93,7 +93,7 @@ function AdminPage() { label="Organization" value={ <Link - to="/organizations/$slug" + to="/orgs/$slug" params={{ slug: tenant.subdomain }} className="text-foreground hover:underline font-mono" > diff --git a/ui/src/routes/_layout/_admin/admin/index.tsx b/ui/src/routes/_layout/_admin/admin/index.tsx index fd5a7378..137d4e33 100644 --- a/ui/src/routes/_layout/_admin/admin/index.tsx +++ b/ui/src/routes/_layout/_admin/admin/index.tsx @@ -53,7 +53,7 @@ function AdminDashboard() { Manage organizations, members, roles, and invitations. </p> <Button asChild variant="outline" size="sm"> - <Link to="/organizations"> + <Link to="/orgs"> <Users className="h-3.5 w-3.5" /> open organizations </Link> @@ -105,7 +105,7 @@ function AdminDashboard() { there. </p> <Link - to="/organizations/$slug" + to="/orgs/$slug" params={{ slug: tenant.subdomain }} className="h-9 px-3 inline-flex items-center gap-1.5 text-xs font-medium border-2 border-outset border-border-strong bg-card text-foreground shadow-sm hover:shadow-md active:border-inset active:shadow-none transition-all duration-200 ease-out rounded-[10px]" > diff --git a/ui/src/routes/_layout/_anon.tsx b/ui/src/routes/_layout/_anon.tsx new file mode 100644 index 00000000..da38e147 --- /dev/null +++ b/ui/src/routes/_layout/_anon.tsx @@ -0,0 +1,57 @@ +import { createFileRoute, Link, Outlet, redirect, useRouterState } from "@tanstack/react-router"; +import { getAppName, sessionQueryOptions } from "@/app"; +import { ThemeToggle } from "@/components/theme-toggle"; + +export const Route = createFileRoute("/_layout/_anon")({ + beforeLoad: async ({ context }) => { + const { queryClient, authClient } = context; + const initialSession = context.session; + const session = initialSession ?? queryClient.getQueryData( + sessionQueryOptions(authClient, initialSession).queryKey, + ); + if (session?.user) { + throw redirect({ to: "/dashboard", search: {} }); + } + }, + component: AnonLayout, +}); + +function AnonLayout() { + const { runtimeConfig } = Route.useRouteContext(); + const appName = getAppName(runtimeConfig); + const pathname = useRouterState({ select: (s) => s.location.pathname }); + + return ( + <div className="flex-1 flex flex-col min-h-0"> + <header className="shrink-0 bg-card/50 border-b border-border transition-all duration-200 overflow-hidden h-12"> + <div className="flex items-center justify-between px-4 sm:px-6 h-12"> + <Link + to="/" + aria-label={`${appName} home`} + className="flex items-center justify-center w-10 h-10 transition-opacity duration-200 hover:opacity-70" + > + <svg + viewBox="0 0 24 24" + fill="currentColor" + className="w-5 h-5 text-foreground" + aria-label={`${appName} logo`} + > + <title>{appName} + + + + +
+ +
+
+ + +
+
+ +
+
+
+ ); +} \ No newline at end of file diff --git a/ui/src/routes/_layout/_public/login.tsx b/ui/src/routes/_layout/_anon/login.tsx similarity index 98% rename from ui/src/routes/_layout/_public/login.tsx rename to ui/src/routes/_layout/_anon/login.tsx index abc68a82..fb37f0ee 100644 --- a/ui/src/routes/_layout/_public/login.tsx +++ b/ui/src/routes/_layout/_anon/login.tsx @@ -11,7 +11,7 @@ type SearchParams = { redirect?: string; }; -export const Route = createFileRoute("/_layout/_public/login")({ +export const Route = createFileRoute("/_layout/_anon/login")({ ssr: false, validateSearch: (search: Record): SearchParams => ({ redirect: typeof search.redirect === "string" ? search.redirect : undefined, @@ -200,7 +200,7 @@ function LoginPage() {
diff --git a/ui/src/routes/_layout/_authenticated.tsx b/ui/src/routes/_layout/_authenticated.tsx index 72eae14a..75ca082d 100644 --- a/ui/src/routes/_layout/_authenticated.tsx +++ b/ui/src/routes/_layout/_authenticated.tsx @@ -1,5 +1,5 @@ import { createFileRoute, Link, Outlet, redirect, useRouterState } from "@tanstack/react-router"; -import { ClipboardList, Compass, Globe, Home, Menu, MessageSquare, Shield } from "lucide-react"; +import { ClipboardList, Compass, Globe, Home, Menu, Shield } from "lucide-react"; import { useState } from "react"; import type { SessionData } from "@/app"; import { getAccount, getActiveRuntime, getAppName, sessionQueryOptions } from "@/app"; @@ -96,7 +96,6 @@ function AuthenticatedLayout() { const sidebarItems: SidebarItem[] = [ { icon: Home, label: "dashboard", to: "/dashboard", roleRequired: "anon" }, { icon: Shield, label: "admin", to: "/admin", roleRequired: "admin" }, - { icon: MessageSquare, label: "nostr", to: "/nostr", roleRequired: "admin" }, ]; const visibleItems = filterSidebarByRole(sidebarItems, getUserRole(true, isAdmin)); diff --git a/ui/src/routes/_layout/_authenticated/organizations/$slug.tsx b/ui/src/routes/_layout/_authenticated/orgs/$slug.tsx similarity index 99% rename from ui/src/routes/_layout/_authenticated/organizations/$slug.tsx rename to ui/src/routes/_layout/_authenticated/orgs/$slug.tsx index e95cc943..ae78e315 100644 --- a/ui/src/routes/_layout/_authenticated/organizations/$slug.tsx +++ b/ui/src/routes/_layout/_authenticated/orgs/$slug.tsx @@ -59,7 +59,7 @@ const orgMembersQueryKey = (orgId: string) => ["org-members", orgId] as const; const orgInvitationsQueryKey = (orgId: string) => ["org-invitations", orgId] as const; const orgApiKeysQueryKey = (orgId: string) => ["org-api-keys", orgId] as const; -export const Route = createFileRoute("/_layout/_authenticated/organizations/$slug")({ +export const Route = createFileRoute("/_layout/_authenticated/orgs/$slug")({ head: () => ({ title: "Organization | auth.everything.dev", meta: [{ name: "description", content: "Manage organization details and members." }], @@ -328,7 +328,7 @@ function OrganizationDetail() { onSuccess: async () => { toast.success("You have left the organization"); await queryClient.invalidateQueries({ queryKey: ["organizations"] }); - await router.navigate({ to: "/organizations" }); + await router.navigate({ to: "/orgs" }); }, onError: (error: Error) => toast.error(error.message || "Failed to leave organization"), }); @@ -341,7 +341,7 @@ function OrganizationDetail() { onSuccess: async () => { toast.success("Organization deleted"); await queryClient.invalidateQueries({ queryKey: ["organizations"] }); - await router.navigate({ to: "/organizations" }); + await router.navigate({ to: "/orgs" }); }, onError: (error: Error) => toast.error(error.message || "Failed to delete organization"), }); @@ -369,7 +369,7 @@ function OrganizationDetail() { description="This organization does not exist or you do not have access." action={ } /> @@ -383,7 +383,7 @@ function OrganizationDetail() {
- + Organizations / @@ -657,7 +657,7 @@ function OrganizationDetail() {
- ); + ) } function Chip({ children, accent }: { children: React.ReactNode; accent?: boolean }) { diff --git a/ui/src/routes/_layout/_authenticated/organizations/index.tsx b/ui/src/routes/_layout/_authenticated/orgs/index.tsx similarity index 98% rename from ui/src/routes/_layout/_authenticated/organizations/index.tsx rename to ui/src/routes/_layout/_authenticated/orgs/index.tsx index 96e4c576..ae09675a 100644 --- a/ui/src/routes/_layout/_authenticated/organizations/index.tsx +++ b/ui/src/routes/_layout/_authenticated/orgs/index.tsx @@ -18,7 +18,7 @@ type UserInvitationsResponse = Awaited< >; type UserInvitationItem = NonNullable[number]; -export const Route = createFileRoute("/_layout/_authenticated/organizations/")({ +export const Route = createFileRoute("/_layout/_authenticated/orgs/")({ head: () => ({ title: "Organizations | auth.everything.dev", meta: [{ name: "description", content: "Manage your organizations and teams." }], @@ -114,7 +114,7 @@ function OrganizationsList() { await queryClient.refetchQueries({ queryKey: ["organizations"] }); if (invitation.organizationSlug) { await router.navigate({ - to: "/organizations/$slug", + to: "/orgs/$slug", params: { slug: invitation.organizationSlug }, }); } @@ -164,7 +164,7 @@ function OrganizationsList() { @@ -253,7 +253,7 @@ function OrganizationsList() {

No organizations yet.

create your first org @@ -303,7 +303,7 @@ function OrganizationsList() {
diff --git a/ui/src/routes/_layout/_authenticated/accept-invitation.$id.tsx b/ui/src/routes/_layout/_authenticated/orgs/invites.$id.tsx similarity index 95% rename from ui/src/routes/_layout/_authenticated/accept-invitation.$id.tsx rename to ui/src/routes/_layout/_authenticated/orgs/invites.$id.tsx index e7941f9a..b1c997e1 100644 --- a/ui/src/routes/_layout/_authenticated/accept-invitation.$id.tsx +++ b/ui/src/routes/_layout/_authenticated/orgs/invites.$id.tsx @@ -5,7 +5,7 @@ import { toast } from "sonner"; import { getAppName, useAuthClient } from "@/app"; import { Badge, Button, Card, CardContent } from "@/components"; -export const Route = createFileRoute("/_layout/_authenticated/accept-invitation/$id")({ +export const Route = createFileRoute("/_layout/_authenticated/orgs/invites/$id")({ head: () => ({ meta: [{ title: `Accept Invitation | ${getAppName()}` }], }), @@ -63,7 +63,7 @@ function AcceptInvitation() { ]); await queryClient.refetchQueries({ queryKey: ["organizations"] }); await router.navigate({ - to: "/organizations/$slug", + to: "/orgs/$slug", params: { slug: invitation?.organizationSlug ?? "" }, }); }, @@ -78,7 +78,7 @@ function AcceptInvitation() { onSuccess: async () => { toast.success("Invitation declined"); await queryClient.invalidateQueries({ queryKey: ["user-invitations"] }); - await router.navigate({ to: "/organizations" }); + await router.navigate({ to: "/orgs" }); }, onError: (error: Error) => toast.error(error.message || "Failed to decline invitation"), }); @@ -100,7 +100,7 @@ function AcceptInvitation() { This invitation does not exist, has expired, or is not addressed to your account.

@@ -166,7 +166,7 @@ function AcceptInvitation() {
diff --git a/ui/src/routes/_layout/_authenticated/organizations/new.tsx b/ui/src/routes/_layout/_authenticated/orgs/new.tsx similarity index 96% rename from ui/src/routes/_layout/_authenticated/organizations/new.tsx rename to ui/src/routes/_layout/_authenticated/orgs/new.tsx index 664b81e7..b08c6b31 100644 --- a/ui/src/routes/_layout/_authenticated/organizations/new.tsx +++ b/ui/src/routes/_layout/_authenticated/orgs/new.tsx @@ -7,7 +7,7 @@ import { useAuthClient } from "@/app"; import { Button, Card, CardContent, Input } from "@/components"; import { PageContainer } from "@/components/layout/page-container"; -export const Route = createFileRoute("/_layout/_authenticated/organizations/new")({ +export const Route = createFileRoute("/_layout/_authenticated/orgs/new")({ head: () => ({ title: "New Organization | auth.everything.dev", meta: [{ name: "description", content: "Create a new organization." }], @@ -37,7 +37,7 @@ function NewOrganization() { await queryClient.refetchQueries({ queryKey: ["organizations"] }); if (data?.slug) { await router.navigate({ - to: "/organizations/$slug", + to: "/orgs/$slug", params: { slug: data.slug }, }); } @@ -76,7 +76,7 @@ function NewOrganization() {
@@ -122,7 +122,7 @@ function NewOrganization() {
- ); + ) } function Field({ diff --git a/ui/src/routes/_layout/_authenticated/tenant/$tenantId.tsx b/ui/src/routes/_layout/_authenticated/tenant/$tenantId.tsx index 4a49f5c9..e9d6e78b 100644 --- a/ui/src/routes/_layout/_authenticated/tenant/$tenantId.tsx +++ b/ui/src/routes/_layout/_authenticated/tenant/$tenantId.tsx @@ -403,7 +403,7 @@ function TenantDetail() { there.

+ + + + Menu + +
+
+ {visibleItems.map((item) => { + const Icon = item.icon; + const active = isActive(item); + return ( + setDrawerOpen(false)} + className={cn( + "flex items-center gap-3 px-3 py-2 rounded-[10px] text-sm font-medium transition-colors", + active + ? "bg-foreground/10 text-foreground" + : "text-muted-foreground hover:text-foreground hover:bg-muted", + )} + > + + {item.label} + + ); + })} +
+
+
+ +
+
+ +
+
+ + + + ); +} + +function TabItem({ + to, + icon: Icon, + label, + active, +}: { + to: string; + icon: React.ComponentType<{ className?: string }>; + label: string; + active: boolean; +}) { + return ( + + + {label} + + ); +} diff --git a/ui/src/components/layout/simple-header.tsx b/ui/src/components/layout/simple-header.tsx new file mode 100644 index 00000000..033ed496 --- /dev/null +++ b/ui/src/components/layout/simple-header.tsx @@ -0,0 +1,40 @@ +import { Link } from "@tanstack/react-router"; +import type { ClientRuntimeConfig } from "@/app"; +import { getAppName } from "@/app"; +import { ThemeToggle } from "@/components/theme-toggle"; + +interface SimpleHeaderProps { + runtimeConfig?: Partial; + rightSlot?: React.ReactNode; +} + +export function SimpleHeader({ runtimeConfig, rightSlot }: SimpleHeaderProps) { + const appName = getAppName(runtimeConfig); + + return ( +
+
+ + + {appName} + + + + +
+ + {rightSlot} +
+
+
+ ); +} diff --git a/ui/src/components/ui/network-toggle.tsx b/ui/src/components/ui/network-toggle.tsx index bec227b9..4ceabfc4 100644 --- a/ui/src/components/ui/network-toggle.tsx +++ b/ui/src/components/ui/network-toggle.tsx @@ -1,33 +1,34 @@ -import { useQuery } from "@tanstack/react-query"; +import { Globe } from "lucide-react"; import { useAuthClient } from "@/app"; -import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { sessionQueryOptions } from "@/lib/auth"; export function NetworkToggle() { const auth = useAuthClient(); - const { data: session } = useQuery(sessionQueryOptions(auth)); const supportedNetworks = auth.near.getSupportedNetworks(); const currentNetwork = auth.useActiveNetwork(); - if (session?.user) return null; if (supportedNetworks.length <= 1) return null; return ( -
- { - auth.near.setNetwork(network as "mainnet" | "testnet"); - }} - > - - {supportedNetworks.map((network) => ( - - {network === "mainnet" ? "Mainnet" : "Testnet"} - - ))} - - +
+ {supportedNetworks.map((network) => ( + + ))}
); } diff --git a/ui/src/components/user-nav.tsx b/ui/src/components/user-nav.tsx index 084aca2f..13f7ef2e 100644 --- a/ui/src/components/user-nav.tsx +++ b/ui/src/components/user-nav.tsx @@ -82,6 +82,7 @@ export function UserNav() { const handleOrgSwitch = async () => { await queryClient.invalidateQueries({ queryKey: ["session"] }); await queryClient.invalidateQueries({ queryKey: ["organizations"] }); + await router.invalidate(); }; const avatarSrc = resolveNearImageUrl(nearProfile?.image) ?? user.image ?? undefined; diff --git a/ui/src/lib/use-relayer-fund.ts b/ui/src/lib/use-relayer-fund.ts new file mode 100644 index 00000000..fa4c98bd --- /dev/null +++ b/ui/src/lib/use-relayer-fund.ts @@ -0,0 +1,111 @@ +import { type UseQueryResult, useQuery } from "@tanstack/react-query"; +import { useCallback, useMemo, useState } from "react"; +import { toast } from "sonner"; +import type { AuthClient } from "./auth"; +import { useAuthClient } from "./auth"; + +export interface RelayerInfoData { + accountId?: string; + mode?: "ephemeral" | "explicit"; + enabled?: boolean; + balance?: string; + available?: string; + network?: "mainnet" | "testnet"; +} + +export interface RelayerInfoResult { + data: RelayerInfoData | null; + refetch: () => Promise; +} + +export const relayerInfoQueryKey = ["relayer-info"] as const; + +export function useRelayerInfoQuery( + auth: AuthClient = useAuthClient(), +): UseQueryResult { + return useQuery({ + queryKey: relayerInfoQueryKey, + queryFn: async () => { + const { data } = await auth.near.getRelayerInfo(); + return (data ?? null) as RelayerInfoData | null; + }, + refetchInterval: 30_000, + }); +} + +export function useRelayerFund( + info: RelayerInfoData | null | undefined, + options?: { onSuccess?: () => void }, +) { + const auth = useAuthClient(); + const [amount, setAmount] = useState("5"); + const [sending, setSending] = useState(false); + + const parsedAmount = useMemo(() => { + const value = Number(amount); + if (!amount || Number.isNaN(value) || value <= 0) return null; + return value; + }, [amount]); + + const sendFund = useCallback(async () => { + const target = info?.accountId; + if (!target) { + toast.error("Relayer not configured on the server."); + return; + } + if (parsedAmount === null) { + toast.error("Enter a valid amount in NEAR."); + return; + } + const connected = await auth.near.ensureConnected(); + if (!connected) { + toast.error("Connect a NEAR wallet first"); + return; + } + const signer = auth.near.getAccountId(); + if (!signer) { + toast.error("Connect a NEAR wallet first"); + return; + } + setSending(true); + try { + const result = await auth.near + .getNearClient() + .transaction(signer) + .transfer(target, `${parsedAmount} NEAR`) + .send({ waitUntil: "FINAL" }); + toast.success("Relayer funded", { + description: result.transaction?.hash + ? `tx: ${result.transaction.hash}` + : `Sent ${parsedAmount} NEAR → ${target}`, + }); + options?.onSuccess?.(); + } catch (err) { + toast.error(err instanceof Error ? err.message : "Funding failed"); + } finally { + setSending(false); + } + }, [auth, info?.accountId, parsedAmount, options]); + + return { + amount, + setAmount, + sending, + parsedAmount, + sendFund, + }; +} + +export function formatYocto(value: string | bigint | number | null | undefined): string | null { + if (value === null || value === undefined) return null; + try { + const v = typeof value === "string" ? BigInt(value) : BigInt(value); + if (v === 0n) return "0 NEAR"; + const whole = v / 10n ** 24n; + const frac = v % 10n ** 24n; + const fracStr = frac.toString().padStart(24, "0").slice(0, 4); + return fracStr === "0000" ? `${whole} NEAR` : `${whole}.${fracStr} NEAR`; + } catch { + return null; + } +} diff --git a/ui/src/routeTree.gen.ts b/ui/src/routeTree.gen.ts index 89244e6f..337a84f4 100644 --- a/ui/src/routeTree.gen.ts +++ b/ui/src/routeTree.gen.ts @@ -15,6 +15,7 @@ import { Route as LayoutAuthenticatedRouteImport } from './routes/_layout/_authe import { Route as LayoutAnonRouteImport } from './routes/_layout/_anon' import { Route as LayoutAdminRouteImport } from './routes/_layout/_admin' import { Route as LayoutPublicIndexRouteImport } from './routes/_layout/_public/index' +import { Route as LayoutPublicStakeRouteImport } from './routes/_layout/_public/stake' import { Route as LayoutPublicSkillRouteImport } from './routes/_layout/_public/skill' import { Route as LayoutPublicAboutRouteImport } from './routes/_layout/_public/about' import { Route as LayoutPublicAccountIdRouteImport } from './routes/_layout/_public/$accountId' @@ -32,17 +33,20 @@ import { Route as LayoutPublicThingsLiveRouteImport } from './routes/_layout/_pu import { Route as LayoutPublicThingsThingIdRouteImport } from './routes/_layout/_public/things/$thingId' import { Route as LayoutPublicAccountIdAppsRouteImport } from './routes/_layout/_public/$accountId/apps' import { Route as LayoutAuthenticatedThingsNewRouteImport } from './routes/_layout/_authenticated/things/new' -import { Route as LayoutAuthenticatedTenantNewRouteImport } from './routes/_layout/_authenticated/tenant/new' import { Route as LayoutAuthenticatedTenantTenantIdRouteImport } from './routes/_layout/_authenticated/tenant/$tenantId' import { Route as LayoutAuthenticatedSettingsSecurityRouteImport } from './routes/_layout/_authenticated/settings/security' import { Route as LayoutAuthenticatedSettingsProfileRouteImport } from './routes/_layout/_authenticated/settings/profile' import { Route as LayoutAuthenticatedSettingsAuthMethodsRouteImport } from './routes/_layout/_authenticated/settings/auth-methods' import { Route as LayoutAuthenticatedOrgsNewRouteImport } from './routes/_layout/_authenticated/orgs/new' import { Route as LayoutAuthenticatedOrgsSlugRouteImport } from './routes/_layout/_authenticated/orgs/$slug' +import { Route as LayoutAdminAdminTenantsRouteImport } from './routes/_layout/_admin/admin/tenants' import { Route as LayoutAdminAdminSystemRouteImport } from './routes/_layout/_admin/admin/system' +import { Route as LayoutAdminAdminRelayerRouteImport } from './routes/_layout/_admin/admin/relayer' import { Route as LayoutPublicAppsAccountIdIndexRouteImport } from './routes/_layout/_public/apps/$accountId/index' +import { Route as LayoutAdminAdminTenantsIndexRouteImport } from './routes/_layout/_admin/admin/tenants/index' import { Route as LayoutPublicAppsAccountIdGatewayIdRouteImport } from './routes/_layout/_public/apps/$accountId/$gatewayId' import { Route as LayoutAuthenticatedOrgsInvitesIdRouteImport } from './routes/_layout/_authenticated/orgs/invites.$id' +import { Route as LayoutAdminAdminTenantsNewRouteImport } from './routes/_layout/_admin/admin/tenants/new' const LayoutRoute = LayoutRouteImport.update({ id: '/_layout', @@ -69,6 +73,11 @@ const LayoutPublicIndexRoute = LayoutPublicIndexRouteImport.update({ path: '/', getParentRoute: () => LayoutPublicRoute, } as any) +const LayoutPublicStakeRoute = LayoutPublicStakeRouteImport.update({ + id: '/stake', + path: '/stake', + getParentRoute: () => LayoutPublicRoute, +} as any) const LayoutPublicSkillRoute = LayoutPublicSkillRouteImport.update({ id: '/skill', path: '/skill', @@ -162,12 +171,6 @@ const LayoutAuthenticatedThingsNewRoute = path: '/things/new', getParentRoute: () => LayoutAuthenticatedRoute, } as any) -const LayoutAuthenticatedTenantNewRoute = - LayoutAuthenticatedTenantNewRouteImport.update({ - id: '/tenant/new', - path: '/tenant/new', - getParentRoute: () => LayoutAuthenticatedRoute, - } as any) const LayoutAuthenticatedTenantTenantIdRoute = LayoutAuthenticatedTenantTenantIdRouteImport.update({ id: '/tenant/$tenantId', @@ -204,17 +207,33 @@ const LayoutAuthenticatedOrgsSlugRoute = path: '/orgs/$slug', getParentRoute: () => LayoutAuthenticatedRoute, } as any) +const LayoutAdminAdminTenantsRoute = LayoutAdminAdminTenantsRouteImport.update({ + id: '/tenants', + path: '/tenants', + getParentRoute: () => LayoutAdminAdminRoute, +} as any) const LayoutAdminAdminSystemRoute = LayoutAdminAdminSystemRouteImport.update({ id: '/system', path: '/system', getParentRoute: () => LayoutAdminAdminRoute, } as any) +const LayoutAdminAdminRelayerRoute = LayoutAdminAdminRelayerRouteImport.update({ + id: '/relayer', + path: '/relayer', + getParentRoute: () => LayoutAdminAdminRoute, +} as any) const LayoutPublicAppsAccountIdIndexRoute = LayoutPublicAppsAccountIdIndexRouteImport.update({ id: '/apps/$accountId/', path: '/apps/$accountId/', getParentRoute: () => LayoutPublicRoute, } as any) +const LayoutAdminAdminTenantsIndexRoute = + LayoutAdminAdminTenantsIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => LayoutAdminAdminTenantsRoute, + } as any) const LayoutPublicAppsAccountIdGatewayIdRoute = LayoutPublicAppsAccountIdGatewayIdRouteImport.update({ id: '/apps/$accountId/$gatewayId', @@ -227,6 +246,12 @@ const LayoutAuthenticatedOrgsInvitesIdRoute = path: '/orgs/invites/$id', getParentRoute: () => LayoutAuthenticatedRoute, } as any) +const LayoutAdminAdminTenantsNewRoute = + LayoutAdminAdminTenantsNewRouteImport.update({ + id: '/new', + path: '/new', + getParentRoute: () => LayoutAdminAdminTenantsRoute, + } as any) export interface FileRoutesByFullPath { '/': typeof LayoutPublicIndexRoute @@ -237,14 +262,16 @@ export interface FileRoutesByFullPath { '/$accountId': typeof LayoutPublicAccountIdRouteWithChildren '/about': typeof LayoutPublicAboutRoute '/skill': typeof LayoutPublicSkillRoute + '/stake': typeof LayoutPublicStakeRoute + '/admin/relayer': typeof LayoutAdminAdminRelayerRoute '/admin/system': typeof LayoutAdminAdminSystemRoute + '/admin/tenants': typeof LayoutAdminAdminTenantsRouteWithChildren '/orgs/$slug': typeof LayoutAuthenticatedOrgsSlugRoute '/orgs/new': typeof LayoutAuthenticatedOrgsNewRoute '/settings/auth-methods': typeof LayoutAuthenticatedSettingsAuthMethodsRoute '/settings/profile': typeof LayoutAuthenticatedSettingsProfileRoute '/settings/security': typeof LayoutAuthenticatedSettingsSecurityRoute '/tenant/$tenantId': typeof LayoutAuthenticatedTenantTenantIdRoute - '/tenant/new': typeof LayoutAuthenticatedTenantNewRoute '/things/new': typeof LayoutAuthenticatedThingsNewRoute '/$accountId/apps': typeof LayoutPublicAccountIdAppsRoute '/things/$thingId': typeof LayoutPublicThingsThingIdRoute @@ -255,8 +282,10 @@ export interface FileRoutesByFullPath { '/$accountId/': typeof LayoutPublicAccountIdIndexRoute '/apps/': typeof LayoutPublicAppsIndexRoute '/things/': typeof LayoutPublicThingsIndexRoute + '/admin/tenants/new': typeof LayoutAdminAdminTenantsNewRoute '/orgs/invites/$id': typeof LayoutAuthenticatedOrgsInvitesIdRoute '/apps/$accountId/$gatewayId': typeof LayoutPublicAppsAccountIdGatewayIdRoute + '/admin/tenants/': typeof LayoutAdminAdminTenantsIndexRoute '/apps/$accountId/': typeof LayoutPublicAppsAccountIdIndexRoute } export interface FileRoutesByTo { @@ -265,6 +294,8 @@ export interface FileRoutesByTo { '/dashboard': typeof LayoutAuthenticatedDashboardRoute '/about': typeof LayoutPublicAboutRoute '/skill': typeof LayoutPublicSkillRoute + '/stake': typeof LayoutPublicStakeRoute + '/admin/relayer': typeof LayoutAdminAdminRelayerRoute '/admin/system': typeof LayoutAdminAdminSystemRoute '/orgs/$slug': typeof LayoutAuthenticatedOrgsSlugRoute '/orgs/new': typeof LayoutAuthenticatedOrgsNewRoute @@ -272,7 +303,6 @@ export interface FileRoutesByTo { '/settings/profile': typeof LayoutAuthenticatedSettingsProfileRoute '/settings/security': typeof LayoutAuthenticatedSettingsSecurityRoute '/tenant/$tenantId': typeof LayoutAuthenticatedTenantTenantIdRoute - '/tenant/new': typeof LayoutAuthenticatedTenantNewRoute '/things/new': typeof LayoutAuthenticatedThingsNewRoute '/$accountId/apps': typeof LayoutPublicAccountIdAppsRoute '/things/$thingId': typeof LayoutPublicThingsThingIdRoute @@ -283,8 +313,10 @@ export interface FileRoutesByTo { '/$accountId': typeof LayoutPublicAccountIdIndexRoute '/apps': typeof LayoutPublicAppsIndexRoute '/things': typeof LayoutPublicThingsIndexRoute + '/admin/tenants/new': typeof LayoutAdminAdminTenantsNewRoute '/orgs/invites/$id': typeof LayoutAuthenticatedOrgsInvitesIdRoute '/apps/$accountId/$gatewayId': typeof LayoutPublicAppsAccountIdGatewayIdRoute + '/admin/tenants': typeof LayoutAdminAdminTenantsIndexRoute '/apps/$accountId': typeof LayoutPublicAppsAccountIdIndexRoute } export interface FileRoutesById { @@ -301,15 +333,17 @@ export interface FileRoutesById { '/_layout/_public/$accountId': typeof LayoutPublicAccountIdRouteWithChildren '/_layout/_public/about': typeof LayoutPublicAboutRoute '/_layout/_public/skill': typeof LayoutPublicSkillRoute + '/_layout/_public/stake': typeof LayoutPublicStakeRoute '/_layout/_public/': typeof LayoutPublicIndexRoute + '/_layout/_admin/admin/relayer': typeof LayoutAdminAdminRelayerRoute '/_layout/_admin/admin/system': typeof LayoutAdminAdminSystemRoute + '/_layout/_admin/admin/tenants': typeof LayoutAdminAdminTenantsRouteWithChildren '/_layout/_authenticated/orgs/$slug': typeof LayoutAuthenticatedOrgsSlugRoute '/_layout/_authenticated/orgs/new': typeof LayoutAuthenticatedOrgsNewRoute '/_layout/_authenticated/settings/auth-methods': typeof LayoutAuthenticatedSettingsAuthMethodsRoute '/_layout/_authenticated/settings/profile': typeof LayoutAuthenticatedSettingsProfileRoute '/_layout/_authenticated/settings/security': typeof LayoutAuthenticatedSettingsSecurityRoute '/_layout/_authenticated/tenant/$tenantId': typeof LayoutAuthenticatedTenantTenantIdRoute - '/_layout/_authenticated/tenant/new': typeof LayoutAuthenticatedTenantNewRoute '/_layout/_authenticated/things/new': typeof LayoutAuthenticatedThingsNewRoute '/_layout/_public/$accountId/apps': typeof LayoutPublicAccountIdAppsRoute '/_layout/_public/things/$thingId': typeof LayoutPublicThingsThingIdRoute @@ -320,8 +354,10 @@ export interface FileRoutesById { '/_layout/_public/$accountId/': typeof LayoutPublicAccountIdIndexRoute '/_layout/_public/apps/': typeof LayoutPublicAppsIndexRoute '/_layout/_public/things/': typeof LayoutPublicThingsIndexRoute + '/_layout/_admin/admin/tenants/new': typeof LayoutAdminAdminTenantsNewRoute '/_layout/_authenticated/orgs/invites/$id': typeof LayoutAuthenticatedOrgsInvitesIdRoute '/_layout/_public/apps/$accountId/$gatewayId': typeof LayoutPublicAppsAccountIdGatewayIdRoute + '/_layout/_admin/admin/tenants/': typeof LayoutAdminAdminTenantsIndexRoute '/_layout/_public/apps/$accountId/': typeof LayoutPublicAppsAccountIdIndexRoute } export interface FileRouteTypes { @@ -335,14 +371,16 @@ export interface FileRouteTypes { | '/$accountId' | '/about' | '/skill' + | '/stake' + | '/admin/relayer' | '/admin/system' + | '/admin/tenants' | '/orgs/$slug' | '/orgs/new' | '/settings/auth-methods' | '/settings/profile' | '/settings/security' | '/tenant/$tenantId' - | '/tenant/new' | '/things/new' | '/$accountId/apps' | '/things/$thingId' @@ -353,8 +391,10 @@ export interface FileRouteTypes { | '/$accountId/' | '/apps/' | '/things/' + | '/admin/tenants/new' | '/orgs/invites/$id' | '/apps/$accountId/$gatewayId' + | '/admin/tenants/' | '/apps/$accountId/' fileRoutesByTo: FileRoutesByTo to: @@ -363,6 +403,8 @@ export interface FileRouteTypes { | '/dashboard' | '/about' | '/skill' + | '/stake' + | '/admin/relayer' | '/admin/system' | '/orgs/$slug' | '/orgs/new' @@ -370,7 +412,6 @@ export interface FileRouteTypes { | '/settings/profile' | '/settings/security' | '/tenant/$tenantId' - | '/tenant/new' | '/things/new' | '/$accountId/apps' | '/things/$thingId' @@ -381,8 +422,10 @@ export interface FileRouteTypes { | '/$accountId' | '/apps' | '/things' + | '/admin/tenants/new' | '/orgs/invites/$id' | '/apps/$accountId/$gatewayId' + | '/admin/tenants' | '/apps/$accountId' id: | '__root__' @@ -398,15 +441,17 @@ export interface FileRouteTypes { | '/_layout/_public/$accountId' | '/_layout/_public/about' | '/_layout/_public/skill' + | '/_layout/_public/stake' | '/_layout/_public/' + | '/_layout/_admin/admin/relayer' | '/_layout/_admin/admin/system' + | '/_layout/_admin/admin/tenants' | '/_layout/_authenticated/orgs/$slug' | '/_layout/_authenticated/orgs/new' | '/_layout/_authenticated/settings/auth-methods' | '/_layout/_authenticated/settings/profile' | '/_layout/_authenticated/settings/security' | '/_layout/_authenticated/tenant/$tenantId' - | '/_layout/_authenticated/tenant/new' | '/_layout/_authenticated/things/new' | '/_layout/_public/$accountId/apps' | '/_layout/_public/things/$thingId' @@ -417,8 +462,10 @@ export interface FileRouteTypes { | '/_layout/_public/$accountId/' | '/_layout/_public/apps/' | '/_layout/_public/things/' + | '/_layout/_admin/admin/tenants/new' | '/_layout/_authenticated/orgs/invites/$id' | '/_layout/_public/apps/$accountId/$gatewayId' + | '/_layout/_admin/admin/tenants/' | '/_layout/_public/apps/$accountId/' fileRoutesById: FileRoutesById } @@ -470,6 +517,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutPublicIndexRouteImport parentRoute: typeof LayoutPublicRoute } + '/_layout/_public/stake': { + id: '/_layout/_public/stake' + path: '/stake' + fullPath: '/stake' + preLoaderRoute: typeof LayoutPublicStakeRouteImport + parentRoute: typeof LayoutPublicRoute + } '/_layout/_public/skill': { id: '/_layout/_public/skill' path: '/skill' @@ -589,13 +643,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutAuthenticatedThingsNewRouteImport parentRoute: typeof LayoutAuthenticatedRoute } - '/_layout/_authenticated/tenant/new': { - id: '/_layout/_authenticated/tenant/new' - path: '/tenant/new' - fullPath: '/tenant/new' - preLoaderRoute: typeof LayoutAuthenticatedTenantNewRouteImport - parentRoute: typeof LayoutAuthenticatedRoute - } '/_layout/_authenticated/tenant/$tenantId': { id: '/_layout/_authenticated/tenant/$tenantId' path: '/tenant/$tenantId' @@ -638,6 +685,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutAuthenticatedOrgsSlugRouteImport parentRoute: typeof LayoutAuthenticatedRoute } + '/_layout/_admin/admin/tenants': { + id: '/_layout/_admin/admin/tenants' + path: '/tenants' + fullPath: '/admin/tenants' + preLoaderRoute: typeof LayoutAdminAdminTenantsRouteImport + parentRoute: typeof LayoutAdminAdminRoute + } '/_layout/_admin/admin/system': { id: '/_layout/_admin/admin/system' path: '/system' @@ -645,6 +699,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutAdminAdminSystemRouteImport parentRoute: typeof LayoutAdminAdminRoute } + '/_layout/_admin/admin/relayer': { + id: '/_layout/_admin/admin/relayer' + path: '/relayer' + fullPath: '/admin/relayer' + preLoaderRoute: typeof LayoutAdminAdminRelayerRouteImport + parentRoute: typeof LayoutAdminAdminRoute + } '/_layout/_public/apps/$accountId/': { id: '/_layout/_public/apps/$accountId/' path: '/apps/$accountId' @@ -652,6 +713,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutPublicAppsAccountIdIndexRouteImport parentRoute: typeof LayoutPublicRoute } + '/_layout/_admin/admin/tenants/': { + id: '/_layout/_admin/admin/tenants/' + path: '/' + fullPath: '/admin/tenants/' + preLoaderRoute: typeof LayoutAdminAdminTenantsIndexRouteImport + parentRoute: typeof LayoutAdminAdminTenantsRoute + } '/_layout/_public/apps/$accountId/$gatewayId': { id: '/_layout/_public/apps/$accountId/$gatewayId' path: '/apps/$accountId/$gatewayId' @@ -666,16 +734,43 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutAuthenticatedOrgsInvitesIdRouteImport parentRoute: typeof LayoutAuthenticatedRoute } + '/_layout/_admin/admin/tenants/new': { + id: '/_layout/_admin/admin/tenants/new' + path: '/new' + fullPath: '/admin/tenants/new' + preLoaderRoute: typeof LayoutAdminAdminTenantsNewRouteImport + parentRoute: typeof LayoutAdminAdminTenantsRoute + } } } +interface LayoutAdminAdminTenantsRouteChildren { + LayoutAdminAdminTenantsNewRoute: typeof LayoutAdminAdminTenantsNewRoute + LayoutAdminAdminTenantsIndexRoute: typeof LayoutAdminAdminTenantsIndexRoute +} + +const LayoutAdminAdminTenantsRouteChildren: LayoutAdminAdminTenantsRouteChildren = + { + LayoutAdminAdminTenantsNewRoute: LayoutAdminAdminTenantsNewRoute, + LayoutAdminAdminTenantsIndexRoute: LayoutAdminAdminTenantsIndexRoute, + } + +const LayoutAdminAdminTenantsRouteWithChildren = + LayoutAdminAdminTenantsRoute._addFileChildren( + LayoutAdminAdminTenantsRouteChildren, + ) + interface LayoutAdminAdminRouteChildren { + LayoutAdminAdminRelayerRoute: typeof LayoutAdminAdminRelayerRoute LayoutAdminAdminSystemRoute: typeof LayoutAdminAdminSystemRoute + LayoutAdminAdminTenantsRoute: typeof LayoutAdminAdminTenantsRouteWithChildren LayoutAdminAdminIndexRoute: typeof LayoutAdminAdminIndexRoute } const LayoutAdminAdminRouteChildren: LayoutAdminAdminRouteChildren = { + LayoutAdminAdminRelayerRoute: LayoutAdminAdminRelayerRoute, LayoutAdminAdminSystemRoute: LayoutAdminAdminSystemRoute, + LayoutAdminAdminTenantsRoute: LayoutAdminAdminTenantsRouteWithChildren, LayoutAdminAdminIndexRoute: LayoutAdminAdminIndexRoute, } @@ -736,7 +831,6 @@ interface LayoutAuthenticatedRouteChildren { LayoutAuthenticatedOrgsSlugRoute: typeof LayoutAuthenticatedOrgsSlugRoute LayoutAuthenticatedOrgsNewRoute: typeof LayoutAuthenticatedOrgsNewRoute LayoutAuthenticatedTenantTenantIdRoute: typeof LayoutAuthenticatedTenantTenantIdRoute - LayoutAuthenticatedTenantNewRoute: typeof LayoutAuthenticatedTenantNewRoute LayoutAuthenticatedThingsNewRoute: typeof LayoutAuthenticatedThingsNewRoute LayoutAuthenticatedOrgsIndexRoute: typeof LayoutAuthenticatedOrgsIndexRoute LayoutAuthenticatedOrgsInvitesIdRoute: typeof LayoutAuthenticatedOrgsInvitesIdRoute @@ -750,7 +844,6 @@ const LayoutAuthenticatedRouteChildren: LayoutAuthenticatedRouteChildren = { LayoutAuthenticatedOrgsNewRoute: LayoutAuthenticatedOrgsNewRoute, LayoutAuthenticatedTenantTenantIdRoute: LayoutAuthenticatedTenantTenantIdRoute, - LayoutAuthenticatedTenantNewRoute: LayoutAuthenticatedTenantNewRoute, LayoutAuthenticatedThingsNewRoute: LayoutAuthenticatedThingsNewRoute, LayoutAuthenticatedOrgsIndexRoute: LayoutAuthenticatedOrgsIndexRoute, LayoutAuthenticatedOrgsInvitesIdRoute: LayoutAuthenticatedOrgsInvitesIdRoute, @@ -778,6 +871,7 @@ interface LayoutPublicRouteChildren { LayoutPublicAccountIdRoute: typeof LayoutPublicAccountIdRouteWithChildren LayoutPublicAboutRoute: typeof LayoutPublicAboutRoute LayoutPublicSkillRoute: typeof LayoutPublicSkillRoute + LayoutPublicStakeRoute: typeof LayoutPublicStakeRoute LayoutPublicIndexRoute: typeof LayoutPublicIndexRoute LayoutPublicThingsThingIdRoute: typeof LayoutPublicThingsThingIdRoute LayoutPublicThingsLiveRoute: typeof LayoutPublicThingsLiveRoute @@ -791,6 +885,7 @@ const LayoutPublicRouteChildren: LayoutPublicRouteChildren = { LayoutPublicAccountIdRoute: LayoutPublicAccountIdRouteWithChildren, LayoutPublicAboutRoute: LayoutPublicAboutRoute, LayoutPublicSkillRoute: LayoutPublicSkillRoute, + LayoutPublicStakeRoute: LayoutPublicStakeRoute, LayoutPublicIndexRoute: LayoutPublicIndexRoute, LayoutPublicThingsThingIdRoute: LayoutPublicThingsThingIdRoute, LayoutPublicThingsLiveRoute: LayoutPublicThingsLiveRoute, diff --git a/ui/src/routes/_layout.tsx b/ui/src/routes/_layout.tsx index 3f68e524..de1371ac 100644 --- a/ui/src/routes/_layout.tsx +++ b/ui/src/routes/_layout.tsx @@ -9,9 +9,16 @@ export const Route = createFileRoute("/_layout")({ function Layout() { const isNavigating = useRouterState({ select: (s) => s.status === "pending" }); + const [showBar, setShowBar] = useState(false); - const [mounted, setMounted] = useState(false); - useEffect(() => setMounted(true), []); + useEffect(() => { + if (!isNavigating) { + setShowBar(false); + return; + } + const t = setTimeout(() => setShowBar(true), 150); + return () => clearTimeout(t); + }, [isNavigating]); const [betaBannerDismissed, setBetaBannerDismissed] = useState(() => { if (typeof window === "undefined") return false; @@ -48,7 +55,7 @@ function Layout() {
)} - {mounted && isNavigating && ( + {showBar && (
diff --git a/ui/src/routes/_layout/_admin.tsx b/ui/src/routes/_layout/_admin.tsx index 9906591e..6adcd61a 100644 --- a/ui/src/routes/_layout/_admin.tsx +++ b/ui/src/routes/_layout/_admin.tsx @@ -1,6 +1,14 @@ -import { createFileRoute, Outlet, redirect } from "@tanstack/react-router"; +import { useQueryClient } from "@tanstack/react-query"; +import { createFileRoute, Link, redirect } from "@tanstack/react-router"; +import { ChevronDown, ChevronUp, Copy, ExternalLink, Fuel, Wallet } from "lucide-react"; +import { useState } from "react"; +import { toast } from "sonner"; import type { SessionData } from "@/app"; -import { sessionQueryOptions } from "@/app"; +import { sessionQueryOptions, useAuthClient } from "@/app"; +import { Badge, Button, Field, FieldLabel, Input } from "@/components"; +import { AuthShell } from "@/components/layout/auth-shell"; +import { formatYocto, useRelayerFund, useRelayerInfoQuery } from "@/lib/use-relayer-fund"; +import { cn } from "@/lib/utils"; interface AuthContext { isAuthenticated: boolean; @@ -12,6 +20,8 @@ interface AuthContext { isBanned: boolean; } +const FUND_PRESETS = ["1", "5", "10"] as const; + export const Route = createFileRoute("/_layout/_admin")({ beforeLoad: async ({ context, location }) => { const { queryClient, authClient } = context; @@ -58,5 +68,200 @@ export const Route = createFileRoute("/_layout/_admin")({ }); function AdminGate() { - return ; + const { runtimeConfig, session } = Route.useRouteContext(); + return ( + <> + + + + ); +} + +function RelayerPanel() { + const auth = useAuthClient(); + const queryClient = useQueryClient(); + const nearAccountId = auth.near.getAccountId(); + const infoQuery = useRelayerInfoQuery(); + const info = infoQuery.data; + const [expanded, setExpanded] = useState(false); + + const fund = useRelayerFund(info, { + onSuccess: () => { + infoQuery.refetch(); + queryClient.invalidateQueries({ queryKey: ["relay-history"] }); + }, + }); + + const handleConnect = async () => { + const connected = await auth.near.ensureConnected(); + if (connected) { + toast.success("Wallet connected"); + infoQuery.refetch(); + } else { + toast.error("Wallet connection declined"); + } + }; + + const handleCopy = async (value: string) => { + try { + await navigator.clipboard.writeText(value); + toast.success("Copied to clipboard"); + } catch { + toast.error("Copy failed"); + } + }; + + const statusLabel = !info + ? "not configured" + : info.enabled + ? "active" + : info.accountId + ? "needs funding" + : "initialising"; + + const statusVariant = + !info || info.enabled ? "default" : info.accountId ? "destructive" : "secondary"; + + const balance = formatYocto(info?.balance); + const canExpand = !!info?.accountId; + const shortAccount = info?.accountId + ? `${info.accountId.slice(0, 6)}…${info.accountId.slice(-4)}` + : null; + + return ( +
+
+
+
+ +
+ + relayer + + {statusLabel} + {info?.mode && ( + + {info.mode} + + )} +
+ {info?.accountId && ( +
+ + {shortAccount} + + +
+ )} + {balance && ( +
+ {balance} +
+ )} +
+ + {canExpand && ( + + )} +
+
+ + {expanded && canExpand && ( +
+ {!nearAccountId ? ( +
+

+ Connect a NEAR wallet to send NEAR to the relayer account. +

+ +
+ ) : ( +
+
+

+ fund relayer +

+

+ sending from{" "} + {nearAccountId} +

+
+ + amount (NEAR) + fund.setAmount(e.target.value)} + disabled={fund.sending} + className="max-w-xs" + /> + +
+
+ {FUND_PRESETS.map((preset) => ( + + ))} +
+ +
+
+ )} +
+ )} +
+
+
+ ); } diff --git a/ui/src/routes/_layout/_admin/admin.tsx b/ui/src/routes/_layout/_admin/admin.tsx index 9fee1727..65b7e6a4 100644 --- a/ui/src/routes/_layout/_admin/admin.tsx +++ b/ui/src/routes/_layout/_admin/admin.tsx @@ -1,8 +1,11 @@ -import { createFileRoute, Link, Outlet, redirect } from "@tanstack/react-router"; -import { Shield } from "lucide-react"; -import { getAccount } from "@/app"; +import { useQuery } from "@tanstack/react-query"; +import { createFileRoute, Link, Outlet, useRouterState } from "@tanstack/react-router"; +import { Building2, Fuel, LayoutDashboard, Settings, Shield } from "lucide-react"; +import { getAccount, useAuthClient } from "@/app"; +import { Badge } from "@/components"; import { EmptyState } from "@/components/empty-state"; import { PageContainer } from "@/components/layout/page-container"; +import { cn } from "@/lib/utils"; export const Route = createFileRoute("/_layout/_admin/admin")({ head: () => ({ @@ -17,9 +20,6 @@ export const Route = createFileRoute("/_layout/_admin/admin")({ } catch { tenant = null; } - if (!tenant) { - throw redirect({ to: "/" }); - } return { tenant }; }, component: AdminPage, @@ -27,15 +27,26 @@ export const Route = createFileRoute("/_layout/_admin/admin")({ function AdminPage() { const { tenant, session } = Route.useRouteContext(); + const auth = useAuthClient(); const activeOrgId = session?.session?.activeOrganizationId ?? null; const isMember = !!tenant && !!activeOrgId && activeOrgId === tenant.orgId; const isAdmin = session?.user?.role === "admin"; const authorized = isMember || isAdmin; - if (!tenant) return null; + const { data: relayerInfo } = useQuery({ + queryKey: ["relayer-info"], + queryFn: async () => { + const { data } = await auth.near.getRelayerInfo(); + return data ?? null; + }, + refetchInterval: 60_000, + }); + + const relayerNeedsFunding = + relayerInfo && relayerInfo.enabled === false && !!relayerInfo.accountId; - if (!authorized) { + if (tenant && !authorized) { return (
-
-
- - Admin -
-
-
-

- {tenant.name} -

-

- {tenant.subdomain} · {tenant.accountId} -

+ {tenant && ( +
+
+ + Admin
-
-
+
+
+

+ {tenant.name} +

+

+ {tenant.subdomain} · {tenant.accountId} +

+
+
+ + )} + + {tenant && ( +
+ + + + {tenant.subdomain} + + } + /> + +
+ )} + + {relayerNeedsFunding && ( + +
+
+ +
+

Relayer needs funding

+

+ The ephemeral relayer{" "} + {relayerInfo?.accountId} has + zero balance — gasless relay is disabled. Fund it with NEAR to enable tenant + + app meta publishes. +

+
+
+ action needed +
+ + )} -
- - - - {tenant.subdomain} - - } - /> - -
+
@@ -113,6 +153,40 @@ function AdminPage() { ); } +const NAV_ITEMS = [ + { label: "dashboard", to: "/admin", icon: LayoutDashboard }, + { label: "tenants", to: "/admin/tenants", icon: Building2 }, + { label: "relayer", to: "/admin/relayer", icon: Fuel }, + { label: "system", to: "/admin/system", icon: Settings }, +] as const; + +function AdminNav() { + const pathname = useRouterState({ select: (s) => s.location.pathname }); + const isActive = (to: string) => + to === "/admin" ? pathname === "/admin" || pathname === "/admin/" : pathname.startsWith(to); + + return ( + + ); +} + function StatCard({ label, value, diff --git a/ui/src/routes/_layout/_admin/admin/index.tsx b/ui/src/routes/_layout/_admin/admin/index.tsx index 137d4e33..c2b43a39 100644 --- a/ui/src/routes/_layout/_admin/admin/index.tsx +++ b/ui/src/routes/_layout/_admin/admin/index.tsx @@ -1,5 +1,5 @@ import { createFileRoute, Link } from "@tanstack/react-router"; -import { Building2, Settings, Users } from "lucide-react"; +import { Building2, LayoutDashboard, Settings, Users } from "lucide-react"; import { getAccount } from "@/app"; import { Button, Card } from "@/components"; import { InfoRow } from "@/components/ui/info-row"; @@ -44,6 +44,22 @@ function AdminDashboard() {

Manage

+ +
+ +
+

Tenants

+

+ Create and manage tenant deployments for your organization. +

+ +
+
diff --git a/ui/src/routes/_layout/_admin/admin/relayer.tsx b/ui/src/routes/_layout/_admin/admin/relayer.tsx new file mode 100644 index 00000000..17923bdc --- /dev/null +++ b/ui/src/routes/_layout/_admin/admin/relayer.tsx @@ -0,0 +1,240 @@ +import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { createFileRoute } from "@tanstack/react-router"; +import { Coins, Fuel, Wallet } from "lucide-react"; +import { toast } from "sonner"; +import { useAuthClient } from "@/app"; +import { Badge, Button, Card, CardContent, Field, FieldLabel, Input } from "@/components"; +import { InfoRow } from "@/components/ui/info-row"; +import { + formatYocto, + relayerInfoQueryKey, + useRelayerFund, + useRelayerInfoQuery, +} from "@/lib/use-relayer-fund"; + +export const Route = createFileRoute("/_layout/_admin/admin/relayer")({ + head: () => ({ + meta: [{ title: "Relayer | app" }], + }), + component: AdminRelayerPage, +}); + +const FUND_PRESETS = ["1", "5", "10"] as const; + +function AdminRelayerPage() { + const auth = useAuthClient(); + const queryClient = useQueryClient(); + const nearAccountId = auth.near.getAccountId(); + + const relayerInfoQuery = useRelayerInfoQuery(); + const info = relayerInfoQuery.data; + + const fund = useRelayerFund(info, { + onSuccess: () => { + relayerInfoQuery.refetch(); + queryClient.invalidateQueries({ queryKey: ["relay-history"] }); + }, + }); + + const relayHistoryQuery = useQuery({ + queryKey: ["relay-history"], + queryFn: async () => { + const { data } = await auth.near.relayHistory(); + return data ?? null; + }, + refetchInterval: 30_000, + }); + + const refresh = () => { + queryClient.invalidateQueries({ queryKey: relayerInfoQueryKey }); + queryClient.invalidateQueries({ queryKey: ["relay-history"] }); + }; + + const handleConnect = async () => { + const connected = await auth.near.ensureConnected(); + if (connected) { + toast.success("Wallet connected"); + refresh(); + } else { + toast.error("Wallet connection declined"); + } + }; + + const history = relayHistoryQuery.data; + + const statusLabel = !info + ? "not configured" + : info.enabled + ? "active" + : info.accountId + ? "needs funding" + : "initialising"; + + const statusVariant = + !info || info.enabled ? "default" : info.accountId ? "destructive" : "secondary"; + + return ( +
+
+
+

Relayer

+

+ Gasless NEP-366 delegate action relay for tenant config + app metadata writes. +

+
+
+ +
+ +
+

Status

+
+ {statusLabel} + {info?.mode && ( + + {info.mode} + + )} +
+
+ + {relayerInfoQuery.isLoading ? ( +

Loading relayer info…

+ ) : !info ? ( +

+ No relayer configured. Update bos.config.json with{" "} + app.auth.variables.siwn.relayer and run{" "} + bos publish to enable. +

+ ) : !info.enabled ? ( +

+ {info.accountId ? ( + <> + Relayer keypair generated but the account has zero balance. Fund{" "} + {info.accountId} with NEAR to + activate gasless relay. + + ) : ( + "Restart the auth service to complete ephemeral keypair generation." + )} +

+ ) : ( +
+ + + + +
+ )} + + +
+ + +
+ +

Top up

+
+ + {!nearAccountId ? ( +
+

+ Connect a NEAR wallet to fund the relayer. +

+ +
+ ) : ( +
+

+ Sending from {nearAccountId} +

+ + amount (NEAR) + fund.setAmount(e.target.value)} + disabled={fund.sending} + /> + +
+ {FUND_PRESETS.map((preset) => ( + + ))} +
+ +
+ )} +
+
+ + + +

Recent relays

+ {relayHistoryQuery.isLoading ? ( +

Loading…

+ ) : !history?.transactions?.length ? ( +

+ No relayed transactions yet. Tenant republish + app metadata writes will appear here. +

+ ) : ( +
    + {history.transactions.slice(0, 8).map((tx) => ( +
  • + {tx.txHash.slice(0, 12)}… + {tx.senderId} + + {tx.status} + +
  • + ))} +
+ )} +
+
+
+ ); +} diff --git a/ui/src/routes/_layout/_admin/admin/tenants.tsx b/ui/src/routes/_layout/_admin/admin/tenants.tsx new file mode 100644 index 00000000..a4069aac --- /dev/null +++ b/ui/src/routes/_layout/_admin/admin/tenants.tsx @@ -0,0 +1,5 @@ +import { createFileRoute, Outlet } from "@tanstack/react-router"; + +export const Route = createFileRoute("/_layout/_admin/admin/tenants")({ + component: () => , +}); diff --git a/ui/src/routes/_layout/_admin/admin/tenants/index.tsx b/ui/src/routes/_layout/_admin/admin/tenants/index.tsx new file mode 100644 index 00000000..68ce8b55 --- /dev/null +++ b/ui/src/routes/_layout/_admin/admin/tenants/index.tsx @@ -0,0 +1,163 @@ +import { useQuery } from "@tanstack/react-query"; +import { createFileRoute, Link } from "@tanstack/react-router"; +import type { ColumnDef } from "@tanstack/react-table"; +import { Building2, Plus } from "lucide-react"; +import { useMemo } from "react"; +import { getActiveRuntime, useApiClient } from "@/app"; +import { Badge, Button, Card, Skeleton } from "@/components"; +import { EmptyState } from "@/components/empty-state"; +import { DataTable } from "@/components/ui/data-table"; + +type ApiClient = ReturnType; +type Tenant = Awaited>[number]; + +export const Route = createFileRoute("/_layout/_admin/admin/tenants/")({ + head: () => ({ + meta: [{ title: "Tenants | app" }], + }), + component: AdminTenants, +}); + +const STATUS_VARIANT: Record = { + active: "default", + pending: "secondary", + suspended: "destructive", + pending_deletion: "secondary", +}; + +function AdminTenants() { + const apiClient = useApiClient(); + const gatewayId = getActiveRuntime()?.gatewayId ?? "everything.dev"; + + const { + data: tenants = [], + isLoading, + error, + refetch, + } = useQuery({ + queryKey: ["tenants"], + queryFn: async () => apiClient.listTenants(), + staleTime: 30 * 1000, + }); + + const columns = useMemo[]>( + () => [ + { + accessorKey: "name", + header: "Name", + cell: ({ row }) => ( + + {row.original.name} + + ), + }, + { + accessorKey: "subdomain", + header: "Subdomain", + cell: ({ row }) => ( + + {row.original.subdomain}.{gatewayId} + + ), + }, + { + accessorKey: "accountId", + header: "Account", + cell: ({ row }) => ( + {row.original.accountId} + ), + }, + { + accessorKey: "status", + header: "Status", + cell: ({ row }) => ( + {row.original.status} + ), + }, + { + accessorKey: "createdAt", + header: "Created", + cell: ({ row }) => + row.original.createdAt ? new Date(row.original.createdAt).toLocaleDateString() : "—", + }, + { + id: "actions", + header: "", + cell: ({ row }) => ( + + ), + }, + ], + [gatewayId], + ); + + return ( +
+
+
+
+
+ + Tenants +
+

+ Tenants +

+

+ Manage tenant deployments for your organization. +

+
+ +
+
+ + {isLoading ? ( + + {[1, 2, 3].map((n) => ( + + ))} + + ) : error ? ( + refetch()}> + retry + + } + /> + ) : tenants.length === 0 ? ( + + + + create tenant + + + } + /> + ) : ( + + )} +
+ ); +} diff --git a/ui/src/routes/_layout/_authenticated/tenant/new.tsx b/ui/src/routes/_layout/_admin/admin/tenants/new.tsx similarity index 99% rename from ui/src/routes/_layout/_authenticated/tenant/new.tsx rename to ui/src/routes/_layout/_admin/admin/tenants/new.tsx index 1ec2c150..b3867da3 100644 --- a/ui/src/routes/_layout/_authenticated/tenant/new.tsx +++ b/ui/src/routes/_layout/_admin/admin/tenants/new.tsx @@ -9,7 +9,7 @@ import { Button, Card, CardContent, Field, FieldLabel, Input } from "@/component import { PageContainer } from "@/components/layout/page-container"; import { StepList, useStepper } from "@/components/ui/stepper"; -export const Route = createFileRoute("/_layout/_authenticated/tenant/new")({ +export const Route = createFileRoute("/_layout/_admin/admin/tenants/new")({ head: () => ({ title: "New Tenant | app", meta: [{ name: "description", content: "Create a new tenant." }], @@ -240,7 +240,7 @@ function NewTenantPage() { return auth.near .getNearClient() - .transaction(accountId) + .transaction(nearAccountId) .functionCall(prepared.data.contractId, prepared.data.methodName, prepared.data.args, { gas: METADATA_GAS, attachedDeposit: 0n, @@ -286,7 +286,7 @@ function NewTenantPage() { return auth.near .getNearClient() - .transaction(accountId) + .transaction(nearAccountId) .functionCall(prepared.data.contractId, prepared.data.methodName, prepared.data.args, { gas: CONFIG_GAS, attachedDeposit: 0n, diff --git a/ui/src/routes/_layout/_anon.tsx b/ui/src/routes/_layout/_anon.tsx index 2a14987a..59dc0fe4 100644 --- a/ui/src/routes/_layout/_anon.tsx +++ b/ui/src/routes/_layout/_anon.tsx @@ -1,6 +1,6 @@ -import { createFileRoute, Link, Outlet, redirect } from "@tanstack/react-router"; -import { getAppName, sessionQueryOptions } from "@/app"; -import { ThemeToggle } from "@/components/theme-toggle"; +import { createFileRoute, Outlet, redirect } from "@tanstack/react-router"; +import { sessionQueryOptions } from "@/app"; +import { SimpleHeader } from "@/components/layout/simple-header"; export const Route = createFileRoute("/_layout/_anon")({ beforeLoad: async ({ context }) => { @@ -18,33 +18,10 @@ export const Route = createFileRoute("/_layout/_anon")({ function AnonLayout() { const { runtimeConfig } = Route.useRouteContext(); - const appName = getAppName(runtimeConfig); return (
-
-
- - - {appName} - - - - -
- -
-
-
+
diff --git a/ui/src/routes/_layout/_authenticated.tsx b/ui/src/routes/_layout/_authenticated.tsx index 83bfd82d..40edbe1a 100644 --- a/ui/src/routes/_layout/_authenticated.tsx +++ b/ui/src/routes/_layout/_authenticated.tsx @@ -1,14 +1,7 @@ -import { createFileRoute, Link, Outlet, redirect, useRouterState } from "@tanstack/react-router"; -import { ClipboardList, Compass, Globe, Home, Menu, Shield } from "lucide-react"; -import { useState } from "react"; +import { createFileRoute, redirect } from "@tanstack/react-router"; import type { SessionData } from "@/app"; -import { getAccount, getActiveRuntime, getAppName, sessionQueryOptions } from "@/app"; -import { NearBranding } from "@/components/near-branding"; -import { ThemeToggle } from "@/components/theme-toggle"; -import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"; -import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; -import { UserNav } from "@/components/user-nav"; -import { cn } from "@/lib/utils"; +import { sessionQueryOptions } from "@/app"; +import { AuthShell } from "@/components/layout/auth-shell"; interface AuthContext { isAuthenticated: boolean; @@ -20,30 +13,6 @@ interface AuthContext { isBanned: boolean; } -type SidebarRole = "anon" | "member" | "admin"; - -interface SidebarItem { - icon: React.ComponentType<{ className?: string }>; - label: string; - to: string; - roleRequired: SidebarRole; -} - -function filterSidebarByRole(items: SidebarItem[], userRole: SidebarRole): SidebarItem[] { - return items.filter((item) => { - if (item.roleRequired === "anon") return true; - if (item.roleRequired === "member" && userRole !== "anon") return true; - if (item.roleRequired === "admin" && userRole === "admin") return true; - return false; - }); -} - -function getUserRole(isAuthenticated: boolean, isAdmin: boolean): SidebarRole { - if (isAdmin) return "admin"; - if (isAuthenticated) return "member"; - return "anon"; -} - export const Route = createFileRoute("/_layout/_authenticated")({ beforeLoad: async ({ context, location }) => { const { queryClient, authClient } = context; @@ -86,218 +55,7 @@ export const Route = createFileRoute("/_layout/_authenticated")({ }); function AuthenticatedLayout() { - const pathname = useRouterState({ select: (s) => s.location.pathname }); const { runtimeConfig, session } = Route.useRouteContext(); - const appName = getAppName(runtimeConfig); - const runtime = getActiveRuntime(runtimeConfig); - const account = getAccount(runtimeConfig); const isAdmin = session?.user?.role === "admin"; - - const sidebarItems: SidebarItem[] = [ - { icon: Home, label: "dashboard", to: "/dashboard", roleRequired: "anon" }, - { icon: Shield, label: "admin", to: "/admin", roleRequired: "admin" }, - ]; - const visibleItems = filterSidebarByRole(sidebarItems, getUserRole(true, isAdmin)); - - const isActive = (item: SidebarItem) => { - return pathname === item.to || (item.to !== "/" && pathname.startsWith(`${item.to}/`)); - }; - - return ( -
- - -
-
-
-
- - - {appName} - - - - -
- {runtime?.accountId ?? account} - / - - {pathname === "/" ? "home" : pathname.slice(1).split("/").join(" / ")} - -
-
- -
- -
-
-
- -
-
- -
-
-
- - -
- ); -} - -function MobileTabBar({ - visibleItems, - isActive, -}: { - visibleItems: SidebarItem[]; - isActive: (item: SidebarItem) => boolean; -}) { - const [drawerOpen, setDrawerOpen] = useState(false); - const pathname = useRouterState({ select: (s) => s.location.pathname }); - const tabActive = (to: string) => (to === "/" ? pathname === "/" : pathname.startsWith(to)); - - return ( - - ); -} - -function TabItem({ - to, - icon: Icon, - label, - active, -}: { - to: string; - icon: React.ComponentType<{ className?: string }>; - label: string; - active: boolean; -}) { - return ( - - - {label} - - ); + return ; } diff --git a/ui/src/routes/_layout/_authenticated/orgs/$slug.tsx b/ui/src/routes/_layout/_authenticated/orgs/$slug.tsx index c26288a3..16591f33 100644 --- a/ui/src/routes/_layout/_authenticated/orgs/$slug.tsx +++ b/ui/src/routes/_layout/_authenticated/orgs/$slug.tsx @@ -86,14 +86,7 @@ function OrganizationDetail() { const { slug: orgSlug } = Route.useParams(); const auth = useAuthClient(); - const { data: session } = useQuery({ - queryKey: ["session"], - queryFn: async () => { - const { data } = await auth.getSession(); - return data ?? null; - }, - staleTime: 60 * 1000, - }); + const { data: session } = useQuery(sessionQueryOptions(auth)); const { data: organizations = [], isLoading: isLoadingOrgs } = useQuery({ queryKey: ["organizations"], diff --git a/ui/src/routes/_layout/_authenticated/orgs/index.tsx b/ui/src/routes/_layout/_authenticated/orgs/index.tsx index ae09675a..a759f5ce 100644 --- a/ui/src/routes/_layout/_authenticated/orgs/index.tsx +++ b/ui/src/routes/_layout/_authenticated/orgs/index.tsx @@ -38,9 +38,13 @@ export const Route = createFileRoute("/_layout/_authenticated/orgs/")({ await context.queryClient.ensureQueryData({ queryKey: ["user-invitations"], queryFn: async (): Promise => { - const { data, error } = await context.authClient.organization.listUserInvitations(); - if (error) throw new Error(error.message); - return (data ?? []) as UserInvitationItem[]; + try { + const { data, error } = await context.authClient.organization.listUserInvitations(); + if (error) throw new Error(error.message); + return (data ?? []) as UserInvitationItem[]; + } catch { + return []; + } }, staleTime: 30 * 1000, }); @@ -53,14 +57,7 @@ function OrganizationsList() { const apiClient = useApiClient(); const router = useRouter(); const queryClient = useQueryClient(); - const { data: session } = useQuery({ - queryKey: ["session"], - queryFn: async () => { - const { data } = await auth.getSession(); - return data ?? null; - }, - staleTime: 60 * 1000, - }); + const { data: session } = useQuery(sessionQueryOptions(auth)); const { data: organizations, isLoading } = useQuery({ queryKey: ["organizations"], queryFn: async () => { @@ -73,9 +70,13 @@ function OrganizationsList() { const { data: userInvitations = [] } = useQuery({ queryKey: ["user-invitations"], queryFn: async (): Promise => { - const { data, error } = await auth.organization.listUserInvitations(); - if (error) throw new Error(error.message); - return (data ?? []) as UserInvitationItem[]; + try { + const { data, error } = await auth.organization.listUserInvitations(); + if (error) throw new Error(error.message); + return (data ?? []) as UserInvitationItem[]; + } catch { + return []; + } }, staleTime: 30 * 1000, }); diff --git a/ui/src/routes/_layout/_authenticated/tenant/$tenantId.tsx b/ui/src/routes/_layout/_authenticated/tenant/$tenantId.tsx index e9d6e78b..996b5699 100644 --- a/ui/src/routes/_layout/_authenticated/tenant/$tenantId.tsx +++ b/ui/src/routes/_layout/_authenticated/tenant/$tenantId.tsx @@ -37,18 +37,42 @@ async function publishTenantConfig( config: tenantConfig, }); - const signed = await auth.near.buildSignedDelegateAction( - prepared.data.contractId, - (builder: TransactionBuilder) => - builder.functionCall(prepared.data.contractId, prepared.data.methodName, prepared.data.args, { - gas: CONFIG_GAS, - attachedDeposit: 0n, - }), - ); + const relayerInfo = await auth.near.getRelayerInfo(); + const hasRelayer = relayerInfo.data?.enabled === true; + + if (hasRelayer) { + const signed = await auth.near.buildSignedDelegateAction( + prepared.data.contractId, + (builder: TransactionBuilder) => + builder.functionCall( + prepared.data.contractId, + prepared.data.methodName, + prepared.data.args, + { + gas: CONFIG_GAS, + attachedDeposit: 0n, + }, + ), + ); + + const relayed = await auth.near.relayTransaction({ payload: signed }); + if (relayed.error) throw new Error(relayed.error.message); + return relayed; + } + + const signerAccountId = auth.near.getAccountId(); + if (!signerAccountId) { + throw new Error("Connect a NEAR wallet first"); + } - const relayed = await auth.near.relayTransaction({ payload: signed }); - if (relayed.error) throw new Error(relayed.error.message); - return relayed; + return auth.near + .getNearClient() + .transaction(signerAccountId) + .functionCall(prepared.data.contractId, prepared.data.methodName, prepared.data.args, { + gas: CONFIG_GAS, + attachedDeposit: 0n, + }) + .send({ waitUntil: "EXECUTED" }); } export const Route = createFileRoute("/_layout/_authenticated/tenant/$tenantId")({ diff --git a/ui/src/routes/_layout/_public.tsx b/ui/src/routes/_layout/_public.tsx index 4001e2dd..bd004997 100644 --- a/ui/src/routes/_layout/_public.tsx +++ b/ui/src/routes/_layout/_public.tsx @@ -1,7 +1,6 @@ -import { createFileRoute, Link, Outlet } from "@tanstack/react-router"; -import { getAppName } from "@/app"; +import { createFileRoute, Outlet } from "@tanstack/react-router"; +import { SimpleHeader } from "@/components/layout/simple-header"; import { NearBranding } from "@/components/near-branding"; -import { ThemeToggle } from "@/components/theme-toggle"; import { UserNav } from "@/components/user-nav"; export const Route = createFileRoute("/_layout/_public")({ @@ -10,34 +9,10 @@ export const Route = createFileRoute("/_layout/_public")({ function PublicLayout() { const { runtimeConfig } = Route.useRouteContext(); - const appName = getAppName(runtimeConfig); return (
-
-
- - - {appName} - - - - -
- - -
-
-
+ } />
diff --git a/ui/src/routes/_layout/_public/index.tsx b/ui/src/routes/_layout/_public/index.tsx index 8cbb4f06..3fed3c94 100644 --- a/ui/src/routes/_layout/_public/index.tsx +++ b/ui/src/routes/_layout/_public/index.tsx @@ -1,7 +1,9 @@ +import { useQuery } from "@tanstack/react-query"; import { createFileRoute, Link } from "@tanstack/react-router"; -import { ArrowRight, Building2, FileCode2, Lock, Sparkles } from "lucide-react"; -import { getAccount, getActiveRuntime, getAppName, getRepository } from "@/app"; -import { Button, Card, PageContainer } from "@/components"; +import { ArrowRight, Landmark, Server, Sparkles } from "lucide-react"; +import { getAccount, getActiveRuntime, getAppName, useApiClient } from "@/app"; +import { Button, Card } from "@/components"; +import { PageContainer } from "@/components/layout/page-container"; export const Route = createFileRoute("/_layout/_public/")({ loader: async ({ context }) => ({ @@ -9,10 +11,10 @@ export const Route = createFileRoute("/_layout/_public/")({ }), head: () => ({ meta: [ - { title: "Welcome | app" }, + { title: "City Nodes | app" }, { name: "description", - content: "A modern starter app built with TanStack Router and Better Auth on NEAR.", + content: "Stake NEAR to city validator pools and help run decentralized city nodes.", }, ], }), @@ -21,13 +23,19 @@ export const Route = createFileRoute("/_layout/_public/")({ function LandingPage() { const { runtimeConfig } = Route.useLoaderData(); + const apiClient = useApiClient(); const appName = getAppName(runtimeConfig); const account = getAccount(runtimeConfig); const runtime = getActiveRuntime(runtimeConfig); - const repository = getRepository(runtimeConfig); const accountId = runtime?.accountId ?? account; + const { data: cityNodes = [] } = useQuery({ + queryKey: ["citynodes"], + queryFn: () => apiClient.listCityNodes(), + staleTime: 30 * 1000, + }); + return (
@@ -42,8 +50,8 @@ function LandingPage() { {appName}

- A production-ready starter built on TanStack Router, Better Auth, and Effect — with - organization management, a guarded dashboard, and a fully typed API. + Every city runs its own validator pool on NEAR. Pick a city, sign in with your wallet, + and stake NEAR to help keep it online.

@@ -54,57 +62,59 @@ function LandingPage() { -
-
- -
- -
-

Secure authentication

-

- NEAR wallet sign-in, email, and passkeys via Better Auth — with an authenticated - layout guard and session-aware routing. -

-
- - -
- +
+
+
+

Live cities

+

+ Stake to a city's validator pool from its subdomain. +

-

Organizations

-

- Create and manage organizations with members, roles, invitations, and API keys — all - backed by typed oRPC endpoints. -

- + + {cityNodes.length} {cityNodes.length === 1 ? "city" : "cities"} + +
- -
- + {cityNodes.length === 0 ? ( + +

+ No city nodes yet. Create a tenant and publish the first one. +

+
+ ) : ( +
+ {cityNodes.map((cityNode) => ( + +
+
+ +
+
+

+ {cityNode.name} +

+

+ {cityNode.hostname}.{runtime?.gatewayId ?? "citynode.app"} +

+
+
+
+ + {cityNode.validatorPool} +
+ +
+ ))}
-

Typed end to end

-

- One contract drives the API and the client — schemas, validation, and types stay in - sync across the whole stack. -

- + )}
- - {repository && ( -
-

Fork the template and make it yours.

- -
- )}
); diff --git a/ui/src/routes/_layout/_public/stake.tsx b/ui/src/routes/_layout/_public/stake.tsx new file mode 100644 index 00000000..d3e9b819 --- /dev/null +++ b/ui/src/routes/_layout/_public/stake.tsx @@ -0,0 +1,687 @@ +import { PingpayOnramp, PingpayOnrampError } from "@pingpay/onramp-sdk"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { createFileRoute } from "@tanstack/react-router"; +import { Landmark, Pencil, Plus, Server, Trash2, Wallet } from "lucide-react"; +import { formatAmount } from "near-kit"; +import { useEffect, useMemo, useRef, useState } from "react"; +import { toast } from "sonner"; +import { + getAccount, + getActiveRuntime, + sessionQueryOptions, + useApiClient, + useAuthClient, +} from "@/app"; +import pingpayLogoDark from "@/assets/brands/pingpay/pingpay-logo-dark.png"; +import pingpayLogoLight from "@/assets/brands/pingpay/pingpay-logo-light.png"; +import { Badge, Button, Card, Field, FieldLabel, Input } from "@/components"; +import { PageContainer } from "@/components/layout/page-container"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; +import { cn } from "@/lib/utils"; + +const STAKE_GAS = "300000000000000"; + +type StakeSearch = { city?: string }; + +export const Route = createFileRoute("/_layout/_public/stake")({ + validateSearch: (search: Record): StakeSearch => ({ + city: typeof search.city === "string" ? search.city : undefined, + }), + head: () => ({ + meta: [ + { title: "Stake | app" }, + { name: "description", content: "Stake NEAR to a city validator pool." }, + ], + }), + component: StakePage, +}); + +function StakePage() { + const apiClient = useApiClient(); + const auth = useAuthClient(); + const queryClient = useQueryClient(); + const { runtimeConfig } = Route.useRouteContext(); + const { city: cityFromSearch } = Route.useSearch(); + const account = getAccount(runtimeConfig); + const gatewayId = getActiveRuntime()?.gatewayId ?? "citynode.app"; + + const { data: session } = useQuery(sessionQueryOptions(auth, undefined)); + + const [nearAccountId, setNearAccountId] = useState(() => auth.near.getAccountId()); + const [connectingWallet, setConnectingWallet] = useState(false); + + const handleConnectWallet = async () => { + setConnectingWallet(true); + try { + const connected = await auth.near.ensureConnected(); + if (connected) { + setNearAccountId(auth.near.getAccountId()); + } else { + toast.error("Failed to connect wallet"); + } + } catch { + toast.error("Failed to connect wallet"); + } finally { + setConnectingWallet(false); + } + }; + + const { data: activeMember } = useQuery({ + queryKey: ["active-member"], + queryFn: async () => { + const { data } = await auth.organization.getActiveMember(); + return data ?? null; + }, + enabled: !!session?.session?.activeOrganizationId, + staleTime: 30 * 1000, + }); + + const [amount, setAmount] = useState("1"); + const [selectedCity, setSelectedCity] = useState(() => cityFromSearch ?? ""); + const [editingPool, setEditingPool] = useState(false); + const [poolInput, setPoolInput] = useState(""); + const [deleteOpen, setDeleteOpen] = useState(false); + const [creating, setCreating] = useState(false); + const [newTenantId, setNewTenantId] = useState(""); + const [newPool, setNewPool] = useState(""); + + const { data: resolvedCityNode } = useQuery({ + queryKey: ["citynode", "resolve", account], + queryFn: () => apiClient.resolveCityNode({ accountId: account }), + staleTime: 30 * 1000, + }); + + const { data: allCityNodes = [] } = useQuery({ + queryKey: ["citynodes"], + queryFn: () => apiClient.listCityNodes(), + staleTime: 30 * 1000, + }); + + const isTenantSubdomain = !!resolvedCityNode; + const selectedCityNode = + resolvedCityNode ?? allCityNodes.find((c) => c.hostname === selectedCity) ?? null; + + const { data: members = [] } = useQuery({ + queryKey: ["org-members", selectedCityNode?.orgId], + queryFn: async () => { + if (!selectedCityNode?.orgId) return []; + const { data, error } = await auth.organization.listMembers({ + query: { organizationId: selectedCityNode.orgId }, + }); + if (error) throw new Error(error.message); + return (data?.members ?? []) as Array<{ userId: string; role: string }>; + }, + enabled: !!selectedCityNode?.orgId, + }); + + const { data: totalStaked, isLoading: totalStakedLoading } = useQuery({ + queryKey: ["pool-total-staked", selectedCityNode?.validatorPool], + queryFn: () => + auth.near + .getNearClient() + .view(selectedCityNode?.validatorPool as string, "get_total_staked_balance"), + enabled: !!selectedCityNode?.validatorPool, + staleTime: 30 * 1000, + retry: 1, + }); + + const { data: numDelegators, isLoading: numDelegatorsLoading } = useQuery({ + queryKey: ["pool-num-accounts", selectedCityNode?.validatorPool], + queryFn: () => + auth.near + .getNearClient() + .view(selectedCityNode?.validatorPool as string, "get_number_of_accounts"), + enabled: !!selectedCityNode?.validatorPool, + staleTime: 30 * 1000, + retry: 1, + }); + + const myRole = members.find((m) => m.userId === session?.user?.id)?.role; + const activeOrgId = session?.session?.activeOrganizationId; + const activeOrgRole = activeMember?.role; + const isActiveOrgAdmin = activeOrgRole === "admin" || activeOrgRole === "owner"; + const isOrgAdmin = + !!selectedCityNode && + selectedCityNode.orgId === activeOrgId && + (myRole === "admin" || myRole === "owner"); + + const { data: orgTenants = [] } = useQuery({ + queryKey: ["org-tenants", activeOrgId], + queryFn: () => apiClient.listTenants(), + enabled: !!activeOrgId && !isTenantSubdomain, + staleTime: 30 * 1000, + }); + + const tenantAlreadyLinked = new Set(allCityNodes.map((c) => c.tenantId)); + const availableTenants = orgTenants.filter((t) => !tenantAlreadyLinked.has(t.id)); + + const createMutation = useMutation({ + mutationFn: async () => { + if (!newTenantId || !newPool.trim()) + throw new Error("Select a tenant and enter a validator pool."); + return apiClient.createCityNode({ + tenantId: newTenantId, + validatorPool: newPool.trim(), + }); + }, + onSuccess: async () => { + toast.success("City node created"); + setCreating(false); + setNewTenantId(""); + setNewPool(""); + await queryClient.invalidateQueries({ queryKey: ["citynodes"] }); + }, + onError: (err: Error) => toast.error(err.message || "Failed to create city node"), + }); + + const parsedYocto = useMemo(() => { + const value = Number(amount); + if (!amount || Number.isNaN(value) || value <= 0) return null; + return BigInt(parseFloat(amount) * 1e24); + }, [amount]); + + const stakeMutation = useMutation({ + mutationFn: async () => { + if (!nearAccountId) throw new Error("Connect a NEAR wallet to stake."); + if (!selectedCityNode) throw new Error("Select a city to stake to."); + if (!parsedYocto) throw new Error("Enter a valid amount to stake."); + const near = auth.near.getNearClient(); + const result = await near + .transaction(nearAccountId) + .functionCall( + selectedCityNode.validatorPool, + "deposit_and_stake", + {}, + { gas: STAKE_GAS, attachedDeposit: parsedYocto }, + ) + .send({ waitUntil: "FINAL" }); + return result; + }, + onSuccess: (result) => { + toast.success("Staked", { + description: result.transaction?.hash ? `tx: ${result.transaction.hash}` : undefined, + }); + }, + onError: (err: Error) => toast.error(err.message || "Failed to stake"), + }); + + const onrampRef = useRef(null); + + const onrampMutation = useMutation({ + mutationFn: async () => { + const onramp = new PingpayOnramp({ + destinationAddress: nearAccountId ?? undefined, + onPopupClose: () => onrampMutation.reset(), + }); + onrampRef.current = onramp; + return onramp.initiateOnramp({ chain: "NEAR", asset: "NEAR" }); + }, + onSuccess: (result) => { + toast.success("Purchase complete", { + description: `Deposited to ${result.depositAddress}`, + }); + }, + onError: (err: Error) => { + if (err instanceof PingpayOnrampError) { + toast.error(err.message || "Onramp failed"); + } else { + toast.error("Unexpected error during purchase"); + } + }, + }); + + useEffect(() => { + return () => onrampRef.current?.close(); + }, []); + + const updatePoolMutation = useMutation({ + mutationFn: async () => { + if (!selectedCityNode) throw new Error("No city node selected"); + return apiClient.updateCityNode({ + cityNodeId: selectedCityNode.id, + validatorPool: poolInput.trim(), + }); + }, + onSuccess: async () => { + toast.success("Validator pool updated"); + setEditingPool(false); + await queryClient.invalidateQueries({ queryKey: ["citynode", "resolve", account] }); + await queryClient.invalidateQueries({ queryKey: ["citynodes"] }); + }, + onError: (err: Error) => toast.error(err.message || "Failed to update validator pool"), + }); + + const deleteMutation = useMutation({ + mutationFn: async () => { + if (!selectedCityNode) throw new Error("No city node selected"); + return apiClient.deleteCityNode({ cityNodeId: selectedCityNode.id }); + }, + onSuccess: async () => { + toast.success("City node deleted"); + setDeleteOpen(false); + await queryClient.invalidateQueries({ queryKey: ["citynode", "resolve", account] }); + await queryClient.invalidateQueries({ queryKey: ["citynodes"] }); + }, + onError: (err: Error) => toast.error(err.message || "Failed to delete city node"), + }); + + return ( + +
+
+
+ + Stake +
+

+ Stake NEAR to a city +

+

+ Deposits are staked directly to the city's validator pool via{" "} + deposit_and_stake. +

+
+ + {isTenantSubdomain && resolvedCityNode ? ( +
+ { + setPoolInput(resolvedCityNode.validatorPool); + setEditingPool(true); + }} + onDelete={() => setDeleteOpen(true)} + /> + {editingPool && ( + + + Validator pool + setPoolInput(e.target.value)} + placeholder="city-node-3.pool.near" + className="h-9 text-sm font-mono" + /> + +
+ + +
+
+ )} +
+ ) : ( +
+ + City + + + {selectedCityNode && ( + { + setPoolInput(selectedCityNode.validatorPool); + setEditingPool(true); + }} + onDelete={() => setDeleteOpen(true)} + /> + )} +
+ )} + + {!isTenantSubdomain && isActiveOrgAdmin && ( +
+ + {creating && ( + +

+ Link a validator pool to one of your organization's tenants. +

+ + Tenant + + + + Validator pool + setNewPool(e.target.value)} + placeholder="city-node-3.pool.near" + className="h-9 text-sm font-mono" + /> + + + {availableTenants.length === 0 && ( +

+ No tenants available. Create a tenant first, then link it to a pool. +

+ )} +
+ )} +
+ )} + + +
+ Stake +
+ {!nearAccountId ? ( +
+

+ No NEAR wallet linked. Connect one to stake. +

+ +
+ ) : ( + <> +
+ + {nearAccountId} +
+ + Amount (NEAR) + setAmount(e.target.value)} + className="h-9 text-sm" + /> + + +

+ Staking sends NEAR to the pool contract — your stake stays under your account. +

+ + )} +
+ + onrampMutation.mutate()} + /> + + {deleteOpen && selectedCityNode && ( + +

+ Delete city node {selectedCityNode.hostname}? This + unlinks it from its validator pool. +

+
+ + +
+
+ )} +
+
+ ); +} + +const TENANT_STATUS_VARIANT: Record = { + active: "default", + pending: "secondary", + suspended: "destructive", + pending_deletion: "destructive", +}; + +function CityNodeCard({ + cityNode, + gatewayId, + isAdmin, + memberCount, + totalStaked, + totalStakedLoading, + numDelegators, + numDelegatorsLoading, + onEdit, + onDelete, +}: { + cityNode: { + id: string; + hostname: string; + name: string; + accountId: string; + validatorPool: string; + tenantStatus: string; + }; + gatewayId: string; + isAdmin: boolean; + memberCount: number; + totalStaked: string | undefined; + totalStakedLoading: boolean; + numDelegators: number | undefined; + numDelegatorsLoading: boolean; + onEdit: () => void; + onDelete: () => void; +}) { + return ( + +
+
+
+ +
+
+
+

+ {cityNode.name} +

+ + {cityNode.tenantStatus.replace(/_/g, " ")} + +
+

+ {cityNode.hostname}.{gatewayId} · {cityNode.accountId} +

+
+
+ {isAdmin && ( +
+ + +
+ )} +
+
+ + {cityNode.validatorPool} +
+
+ + + +
+
+ ); +} + +function StatBlock({ label, value }: { label: string; value: string | number }) { + return ( +
+

+ {label} +

+

{value}

+
+ ); +} + +function PingOnrampBanner({ + disabled, + pending, + onBuy, +}: { + disabled: boolean; + pending: boolean; + onBuy: () => void; +}) { + const button = ( + + ); + + return ( +
+
+
+
+ +
+
+

+ Need NEAR to stake? +

+

+ Buy instantly with card, Apple Pay, or bank transfer. +

+
+
+ {disabled ? ( + + {button} + + Connect a NEAR wallet to buy NEAR + + + ) : ( + button + )} +
+
+ ); +} From 0e0ea1b23e93b635e09e33a7c5e6e310ca8f7005 Mon Sep 17 00:00:00 2001 From: Elliot Braem Date: Sat, 15 Aug 2026 21:49:18 -0500 Subject: [PATCH 14/17] relayer --- bos.config.json | 14 +- ui/src/components/index.ts | 1 + ui/src/components/layout/auth-shell.tsx | 15 +- ui/src/components/layout/simple-header.tsx | 6 +- ui/src/components/user-nav.tsx | 11 +- ui/src/routeTree.gen.ts | 43 ++-- ui/src/routes/_layout/_admin.tsx | 210 +----------------- .../{_public => _authenticated}/stake.tsx | 4 +- 8 files changed, 52 insertions(+), 252 deletions(-) rename ui/src/routes/_layout/{_public => _authenticated}/stake.tsx (99%) diff --git a/bos.config.json b/bos.config.json index ff90f8e7..07a09d83 100644 --- a/bos.config.json +++ b/bos.config.json @@ -12,7 +12,7 @@ "app": { "host": { "development": "local:host", - "production": "https://elliot-braem-7375-host-everything-dev-nearbuilder-5ef47f16d-ze.zephyrcloud.app", + "production": "https://elliot-braem-7380-host-everything-dev-nearbuilder-58d75d144-ze.zephyrcloud.app", "secrets": [ "CORS_ORIGIN", "CSP_STRICT" @@ -21,14 +21,14 @@ }, "ui": { "development": "local:ui", - "production": "https://elliot-braem-7371-ui-everything-dev-nearbuilders-0423ed3a2-ze.zephyrcloud.app", - "ssr": "https://elliot-braem-7374-ui-everything-dev-nearbuilders-6460adb8d-ze.zephyrcloud.app", - "integrity": "sha384-QdZZu1hwpHi1qImC2jVp9QmIYYKpDBZBXh1FPTDDxtegZT3yUXs4xfd+j01xQyo9", - "ssrIntegrity": "sha384-olwa4R7LwVK2cMJg2IdR7a5yDPaypwjHlVwjpxNNtCHF7y8LMv0VlrOHYCVo7kqx" + "production": "https://elliot-braem-7376-ui-everything-dev-nearbuilders-640fbb40f-ze.zephyrcloud.app", + "ssr": "https://elliot-braem-7379-ui-everything-dev-nearbuilders-9e2e0e650-ze.zephyrcloud.app", + "integrity": "sha384-UQX/yKubluHrdSEKhNtBs39QAbtXHs/iJ0aqkyPIW/ey/k26xRVvCEKsL2iK+cmb", + "ssrIntegrity": "sha384-V8ObgdJVdJ/jwU5xZbwX0R0+APdf2pli4B+ApUAfkB9vtzpxGVrFNDC3qS0FsL+i" }, "api": { "development": "local:api", - "production": "https://elliot-braem-7373-api-everything-dev-nearbuilders-3c3b45512-ze.zephyrcloud.app", + "production": "https://elliot-braem-7377-api-everything-dev-nearbuilders-d0f1039d8-ze.zephyrcloud.app", "secrets": [ "API_DATABASE_URL" ], @@ -75,7 +75,7 @@ "plugins": { "apps": { "development": "local:plugins/apps", - "production": "https://elliot-braem-7372-everything-dev-apps-plugin-ever-39050cf2b-ze.zephyrcloud.app", + "production": "https://elliot-braem-7378-everything-dev-apps-plugin-ever-bd36fe158-ze.zephyrcloud.app", "variables": { "registryNamespace": "v1.citynode.near" }, diff --git a/ui/src/components/index.ts b/ui/src/components/index.ts index f7bcfd5c..716fa632 100644 --- a/ui/src/components/index.ts +++ b/ui/src/components/index.ts @@ -9,6 +9,7 @@ export { ConfirmDialog } from "./confirm-dialog"; export { EmptyState } from "./empty-state"; export { PageContainer } from "./layout/page-container"; export { OrgSwitcher } from "./org-switcher"; +export { ThemeToggle } from "./theme-toggle"; export { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar"; export { Badge } from "./ui/badge"; export { Button } from "./ui/button"; diff --git a/ui/src/components/layout/auth-shell.tsx b/ui/src/components/layout/auth-shell.tsx index 96c38886..85b3b704 100644 --- a/ui/src/components/layout/auth-shell.tsx +++ b/ui/src/components/layout/auth-shell.tsx @@ -1,10 +1,9 @@ import { Link, Outlet, useRouterState } from "@tanstack/react-router"; -import { ClipboardList, Compass, Globe, Home, Landmark, Menu, Shield } from "lucide-react"; +import { Building2, Home, Landmark, Menu, Shield } from "lucide-react"; import { useState } from "react"; import type { ClientRuntimeConfig, SessionData } from "@/app"; import { getAccount, getActiveRuntime, getAppName } from "@/app"; import { NearBranding } from "@/components/near-branding"; -import { ThemeToggle } from "@/components/theme-toggle"; import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { UserNav } from "@/components/user-nav"; @@ -105,7 +104,7 @@ export function AuthShell({ session, runtimeConfig, isAdmin = false }: AuthShell
- +
@@ -175,9 +174,8 @@ function MobileTabBar({ >
- - - + +
-
+
-
- -
diff --git a/ui/src/components/layout/simple-header.tsx b/ui/src/components/layout/simple-header.tsx index 033ed496..a9506edc 100644 --- a/ui/src/components/layout/simple-header.tsx +++ b/ui/src/components/layout/simple-header.tsx @@ -1,7 +1,6 @@ import { Link } from "@tanstack/react-router"; import type { ClientRuntimeConfig } from "@/app"; import { getAppName } from "@/app"; -import { ThemeToggle } from "@/components/theme-toggle"; interface SimpleHeaderProps { runtimeConfig?: Partial; @@ -30,10 +29,7 @@ export function SimpleHeader({ runtimeConfig, rightSlot }: SimpleHeaderProps) { -
- - {rightSlot} -
+
{rightSlot}
); diff --git a/ui/src/components/user-nav.tsx b/ui/src/components/user-nav.tsx index 13f7ef2e..d4a61673 100644 --- a/ui/src/components/user-nav.tsx +++ b/ui/src/components/user-nav.tsx @@ -4,7 +4,7 @@ import { Building2, Home, LogOut, Settings, User } from "lucide-react"; import { useMemo } from "react"; import type { Organization } from "@/app"; import { sessionQueryOptions, useAuthClient } from "@/app"; -import { Avatar, AvatarFallback, AvatarImage, OrgSwitcher } from "@/components"; +import { Avatar, AvatarFallback, AvatarImage, OrgSwitcher, ThemeToggle } from "@/components"; import { DropdownMenu, DropdownMenuContent, @@ -165,6 +165,15 @@ export function UserNav() { + +
+ + + theme + +
+
+ { diff --git a/ui/src/routeTree.gen.ts b/ui/src/routeTree.gen.ts index 337a84f4..c3b89a83 100644 --- a/ui/src/routeTree.gen.ts +++ b/ui/src/routeTree.gen.ts @@ -15,10 +15,10 @@ import { Route as LayoutAuthenticatedRouteImport } from './routes/_layout/_authe import { Route as LayoutAnonRouteImport } from './routes/_layout/_anon' import { Route as LayoutAdminRouteImport } from './routes/_layout/_admin' import { Route as LayoutPublicIndexRouteImport } from './routes/_layout/_public/index' -import { Route as LayoutPublicStakeRouteImport } from './routes/_layout/_public/stake' import { Route as LayoutPublicSkillRouteImport } from './routes/_layout/_public/skill' import { Route as LayoutPublicAboutRouteImport } from './routes/_layout/_public/about' import { Route as LayoutPublicAccountIdRouteImport } from './routes/_layout/_public/$accountId' +import { Route as LayoutAuthenticatedStakeRouteImport } from './routes/_layout/_authenticated/stake' import { Route as LayoutAuthenticatedSettingsRouteImport } from './routes/_layout/_authenticated/settings' import { Route as LayoutAuthenticatedDashboardRouteImport } from './routes/_layout/_authenticated/dashboard' import { Route as LayoutAnonLoginRouteImport } from './routes/_layout/_anon/login' @@ -73,11 +73,6 @@ const LayoutPublicIndexRoute = LayoutPublicIndexRouteImport.update({ path: '/', getParentRoute: () => LayoutPublicRoute, } as any) -const LayoutPublicStakeRoute = LayoutPublicStakeRouteImport.update({ - id: '/stake', - path: '/stake', - getParentRoute: () => LayoutPublicRoute, -} as any) const LayoutPublicSkillRoute = LayoutPublicSkillRouteImport.update({ id: '/skill', path: '/skill', @@ -93,6 +88,12 @@ const LayoutPublicAccountIdRoute = LayoutPublicAccountIdRouteImport.update({ path: '/$accountId', getParentRoute: () => LayoutPublicRoute, } as any) +const LayoutAuthenticatedStakeRoute = + LayoutAuthenticatedStakeRouteImport.update({ + id: '/stake', + path: '/stake', + getParentRoute: () => LayoutAuthenticatedRoute, + } as any) const LayoutAuthenticatedSettingsRoute = LayoutAuthenticatedSettingsRouteImport.update({ id: '/settings', @@ -259,10 +260,10 @@ export interface FileRoutesByFullPath { '/login': typeof LayoutAnonLoginRoute '/dashboard': typeof LayoutAuthenticatedDashboardRoute '/settings': typeof LayoutAuthenticatedSettingsRouteWithChildren + '/stake': typeof LayoutAuthenticatedStakeRoute '/$accountId': typeof LayoutPublicAccountIdRouteWithChildren '/about': typeof LayoutPublicAboutRoute '/skill': typeof LayoutPublicSkillRoute - '/stake': typeof LayoutPublicStakeRoute '/admin/relayer': typeof LayoutAdminAdminRelayerRoute '/admin/system': typeof LayoutAdminAdminSystemRoute '/admin/tenants': typeof LayoutAdminAdminTenantsRouteWithChildren @@ -292,9 +293,9 @@ export interface FileRoutesByTo { '/': typeof LayoutPublicIndexRoute '/login': typeof LayoutAnonLoginRoute '/dashboard': typeof LayoutAuthenticatedDashboardRoute + '/stake': typeof LayoutAuthenticatedStakeRoute '/about': typeof LayoutPublicAboutRoute '/skill': typeof LayoutPublicSkillRoute - '/stake': typeof LayoutPublicStakeRoute '/admin/relayer': typeof LayoutAdminAdminRelayerRoute '/admin/system': typeof LayoutAdminAdminSystemRoute '/orgs/$slug': typeof LayoutAuthenticatedOrgsSlugRoute @@ -330,10 +331,10 @@ export interface FileRoutesById { '/_layout/_anon/login': typeof LayoutAnonLoginRoute '/_layout/_authenticated/dashboard': typeof LayoutAuthenticatedDashboardRoute '/_layout/_authenticated/settings': typeof LayoutAuthenticatedSettingsRouteWithChildren + '/_layout/_authenticated/stake': typeof LayoutAuthenticatedStakeRoute '/_layout/_public/$accountId': typeof LayoutPublicAccountIdRouteWithChildren '/_layout/_public/about': typeof LayoutPublicAboutRoute '/_layout/_public/skill': typeof LayoutPublicSkillRoute - '/_layout/_public/stake': typeof LayoutPublicStakeRoute '/_layout/_public/': typeof LayoutPublicIndexRoute '/_layout/_admin/admin/relayer': typeof LayoutAdminAdminRelayerRoute '/_layout/_admin/admin/system': typeof LayoutAdminAdminSystemRoute @@ -368,10 +369,10 @@ export interface FileRouteTypes { | '/login' | '/dashboard' | '/settings' + | '/stake' | '/$accountId' | '/about' | '/skill' - | '/stake' | '/admin/relayer' | '/admin/system' | '/admin/tenants' @@ -401,9 +402,9 @@ export interface FileRouteTypes { | '/' | '/login' | '/dashboard' + | '/stake' | '/about' | '/skill' - | '/stake' | '/admin/relayer' | '/admin/system' | '/orgs/$slug' @@ -438,10 +439,10 @@ export interface FileRouteTypes { | '/_layout/_anon/login' | '/_layout/_authenticated/dashboard' | '/_layout/_authenticated/settings' + | '/_layout/_authenticated/stake' | '/_layout/_public/$accountId' | '/_layout/_public/about' | '/_layout/_public/skill' - | '/_layout/_public/stake' | '/_layout/_public/' | '/_layout/_admin/admin/relayer' | '/_layout/_admin/admin/system' @@ -517,13 +518,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutPublicIndexRouteImport parentRoute: typeof LayoutPublicRoute } - '/_layout/_public/stake': { - id: '/_layout/_public/stake' - path: '/stake' - fullPath: '/stake' - preLoaderRoute: typeof LayoutPublicStakeRouteImport - parentRoute: typeof LayoutPublicRoute - } '/_layout/_public/skill': { id: '/_layout/_public/skill' path: '/skill' @@ -545,6 +539,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LayoutPublicAccountIdRouteImport parentRoute: typeof LayoutPublicRoute } + '/_layout/_authenticated/stake': { + id: '/_layout/_authenticated/stake' + path: '/stake' + fullPath: '/stake' + preLoaderRoute: typeof LayoutAuthenticatedStakeRouteImport + parentRoute: typeof LayoutAuthenticatedRoute + } '/_layout/_authenticated/settings': { id: '/_layout/_authenticated/settings' path: '/settings' @@ -828,6 +829,7 @@ const LayoutAuthenticatedSettingsRouteWithChildren = interface LayoutAuthenticatedRouteChildren { LayoutAuthenticatedDashboardRoute: typeof LayoutAuthenticatedDashboardRoute LayoutAuthenticatedSettingsRoute: typeof LayoutAuthenticatedSettingsRouteWithChildren + LayoutAuthenticatedStakeRoute: typeof LayoutAuthenticatedStakeRoute LayoutAuthenticatedOrgsSlugRoute: typeof LayoutAuthenticatedOrgsSlugRoute LayoutAuthenticatedOrgsNewRoute: typeof LayoutAuthenticatedOrgsNewRoute LayoutAuthenticatedTenantTenantIdRoute: typeof LayoutAuthenticatedTenantTenantIdRoute @@ -840,6 +842,7 @@ const LayoutAuthenticatedRouteChildren: LayoutAuthenticatedRouteChildren = { LayoutAuthenticatedDashboardRoute: LayoutAuthenticatedDashboardRoute, LayoutAuthenticatedSettingsRoute: LayoutAuthenticatedSettingsRouteWithChildren, + LayoutAuthenticatedStakeRoute: LayoutAuthenticatedStakeRoute, LayoutAuthenticatedOrgsSlugRoute: LayoutAuthenticatedOrgsSlugRoute, LayoutAuthenticatedOrgsNewRoute: LayoutAuthenticatedOrgsNewRoute, LayoutAuthenticatedTenantTenantIdRoute: @@ -871,7 +874,6 @@ interface LayoutPublicRouteChildren { LayoutPublicAccountIdRoute: typeof LayoutPublicAccountIdRouteWithChildren LayoutPublicAboutRoute: typeof LayoutPublicAboutRoute LayoutPublicSkillRoute: typeof LayoutPublicSkillRoute - LayoutPublicStakeRoute: typeof LayoutPublicStakeRoute LayoutPublicIndexRoute: typeof LayoutPublicIndexRoute LayoutPublicThingsThingIdRoute: typeof LayoutPublicThingsThingIdRoute LayoutPublicThingsLiveRoute: typeof LayoutPublicThingsLiveRoute @@ -885,7 +887,6 @@ const LayoutPublicRouteChildren: LayoutPublicRouteChildren = { LayoutPublicAccountIdRoute: LayoutPublicAccountIdRouteWithChildren, LayoutPublicAboutRoute: LayoutPublicAboutRoute, LayoutPublicSkillRoute: LayoutPublicSkillRoute, - LayoutPublicStakeRoute: LayoutPublicStakeRoute, LayoutPublicIndexRoute: LayoutPublicIndexRoute, LayoutPublicThingsThingIdRoute: LayoutPublicThingsThingIdRoute, LayoutPublicThingsLiveRoute: LayoutPublicThingsLiveRoute, diff --git a/ui/src/routes/_layout/_admin.tsx b/ui/src/routes/_layout/_admin.tsx index 6adcd61a..cf6857f1 100644 --- a/ui/src/routes/_layout/_admin.tsx +++ b/ui/src/routes/_layout/_admin.tsx @@ -1,14 +1,7 @@ -import { useQueryClient } from "@tanstack/react-query"; -import { createFileRoute, Link, redirect } from "@tanstack/react-router"; -import { ChevronDown, ChevronUp, Copy, ExternalLink, Fuel, Wallet } from "lucide-react"; -import { useState } from "react"; -import { toast } from "sonner"; +import { createFileRoute, redirect } from "@tanstack/react-router"; import type { SessionData } from "@/app"; -import { sessionQueryOptions, useAuthClient } from "@/app"; -import { Badge, Button, Field, FieldLabel, Input } from "@/components"; +import { sessionQueryOptions } from "@/app"; import { AuthShell } from "@/components/layout/auth-shell"; -import { formatYocto, useRelayerFund, useRelayerInfoQuery } from "@/lib/use-relayer-fund"; -import { cn } from "@/lib/utils"; interface AuthContext { isAuthenticated: boolean; @@ -20,8 +13,6 @@ interface AuthContext { isBanned: boolean; } -const FUND_PRESETS = ["1", "5", "10"] as const; - export const Route = createFileRoute("/_layout/_admin")({ beforeLoad: async ({ context, location }) => { const { queryClient, authClient } = context; @@ -69,199 +60,6 @@ export const Route = createFileRoute("/_layout/_admin")({ function AdminGate() { const { runtimeConfig, session } = Route.useRouteContext(); - return ( - <> - - - - ); -} - -function RelayerPanel() { - const auth = useAuthClient(); - const queryClient = useQueryClient(); - const nearAccountId = auth.near.getAccountId(); - const infoQuery = useRelayerInfoQuery(); - const info = infoQuery.data; - const [expanded, setExpanded] = useState(false); - - const fund = useRelayerFund(info, { - onSuccess: () => { - infoQuery.refetch(); - queryClient.invalidateQueries({ queryKey: ["relay-history"] }); - }, - }); - - const handleConnect = async () => { - const connected = await auth.near.ensureConnected(); - if (connected) { - toast.success("Wallet connected"); - infoQuery.refetch(); - } else { - toast.error("Wallet connection declined"); - } - }; - - const handleCopy = async (value: string) => { - try { - await navigator.clipboard.writeText(value); - toast.success("Copied to clipboard"); - } catch { - toast.error("Copy failed"); - } - }; - - const statusLabel = !info - ? "not configured" - : info.enabled - ? "active" - : info.accountId - ? "needs funding" - : "initialising"; - - const statusVariant = - !info || info.enabled ? "default" : info.accountId ? "destructive" : "secondary"; - - const balance = formatYocto(info?.balance); - const canExpand = !!info?.accountId; - const shortAccount = info?.accountId - ? `${info.accountId.slice(0, 6)}…${info.accountId.slice(-4)}` - : null; - - return ( -
-
-
-
- -
- - relayer - - {statusLabel} - {info?.mode && ( - - {info.mode} - - )} -
- {info?.accountId && ( -
- - {shortAccount} - - -
- )} - {balance && ( -
- {balance} -
- )} -
- - {canExpand && ( - - )} -
-
- - {expanded && canExpand && ( -
- {!nearAccountId ? ( -
-

- Connect a NEAR wallet to send NEAR to the relayer account. -

- -
- ) : ( -
-
-

- fund relayer -

-

- sending from{" "} - {nearAccountId} -

-
- - amount (NEAR) - fund.setAmount(e.target.value)} - disabled={fund.sending} - className="max-w-xs" - /> - -
-
- {FUND_PRESETS.map((preset) => ( - - ))} -
- -
-
- )} -
- )} -
-
-
- ); + const isAdmin = session?.user?.role === "admin"; + return ; } diff --git a/ui/src/routes/_layout/_public/stake.tsx b/ui/src/routes/_layout/_authenticated/stake.tsx similarity index 99% rename from ui/src/routes/_layout/_public/stake.tsx rename to ui/src/routes/_layout/_authenticated/stake.tsx index d3e9b819..a48625c1 100644 --- a/ui/src/routes/_layout/_public/stake.tsx +++ b/ui/src/routes/_layout/_authenticated/stake.tsx @@ -23,7 +23,7 @@ const STAKE_GAS = "300000000000000"; type StakeSearch = { city?: string }; -export const Route = createFileRoute("/_layout/_public/stake")({ +export const Route = createFileRoute("/_layout/_authenticated/stake")({ validateSearch: (search: Record): StakeSearch => ({ city: typeof search.city === "string" ? search.city : undefined, }), @@ -593,7 +593,7 @@ function CityNodeCard({ - ); + ) } function StatBlock({ label, value }: { label: string; value: string | number }) { From d09237013c9f3bd92f1ee308a788554d7b596cd7 Mon Sep 17 00:00:00 2001 From: Elliot Braem Date: Sat, 15 Aug 2026 22:13:48 -0500 Subject: [PATCH 15/17] feat(cli): lock deploys in FastKV, expose infra export, resolve plugin origin - Add per-account/gateway FastKV deploy lock (apps///lock/deploy.json) acquired before publish and released in a finally block. Stale or concurrent dispatches fail fast with status "locked" and a conflict payload listing owner, nonce, expires, and txHash. opt out with --no-deploy-lock. - bos publish holds the lock for 10 minutes by default; bos deploy holds it for 25 minutes to cover publish + Railway redeploy. Override with BOS_DEPLOY_LOCK_TTL_MS. - Add bos deploy lock inspect|release for ops visibility and stuck-lock recovery. - Add bos infra export that emits the resolved CI infra plan (env + services + account/gateway/project/generatedAt) so CI consumers stop duplicating port and DATABASE_URL knowledge from cli/infra.ts. - Update .github/workflows/deploy.yml to consume the export and drop the hardcoded API_DATABASE_URL / AUTH_DATABASE_URL / CORS_ORIGIN env block. - buildOriginMap now reads runtimeConfig.plugins[id].extendsRef and runtimeConfig.auth?.extendsRef instead of re-parsing raw bos.config.json. - Tests for the lock helpers, the CI plan builder, the resolved-config origin lookup, and the per-command TTL resolution. Co-authored-by: opencode --- .changeset/deploy-lock-and-infra-export.md | 12 + .github/workflows/deploy.yml | 12 +- packages/everything-dev/src/cli.ts | 80 ++++- .../everything-dev/src/cli/deploy-lock.ts | 229 ++++++++++++ packages/everything-dev/src/cli/infra.ts | 180 ++++++++-- packages/everything-dev/src/contract.meta.ts | 20 ++ packages/everything-dev/src/contract.ts | 90 ++++- packages/everything-dev/src/plugin.ts | 88 +++++ packages/everything-dev/src/publish.ts | 325 +++++++++++------- .../tests/unit/deploy-lock.test.ts | 115 +++++++ .../tests/unit/infra-export.test.ts | 199 +++++++++++ .../routes/_layout/_authenticated/stake.tsx | 2 +- 12 files changed, 1188 insertions(+), 164 deletions(-) create mode 100644 .changeset/deploy-lock-and-infra-export.md create mode 100644 packages/everything-dev/src/cli/deploy-lock.ts create mode 100644 packages/everything-dev/tests/unit/deploy-lock.test.ts create mode 100644 packages/everything-dev/tests/unit/infra-export.test.ts diff --git a/.changeset/deploy-lock-and-infra-export.md b/.changeset/deploy-lock-and-infra-export.md new file mode 100644 index 00000000..c7c363da --- /dev/null +++ b/.changeset/deploy-lock-and-infra-export.md @@ -0,0 +1,12 @@ +--- +"everything-dev": minor +--- + +Add a per-account/gateway FastKV-backed deploy lock, a `bos infra export` command that emits the resolved CI infra plan, and read plugin `extendsRef` from the resolved runtime config (instead of re-parsing raw `bos.config.json`). + +- `bos publish --deploy` and `bos deploy` now acquire `apps///lock/deploy.json` before publishing. Stale or concurrent dispatches fail fast with `status: "locked"` and a conflict payload listing the owner, nonce, and txHash. Use `--no-deploy-lock` to opt out (deploy lock is on by default). The lock is released in a `finally` block after publish confirmation. + - `bos publish` holds the lock for 10 minutes by default (the FastKV write window). `bos deploy` holds it for 25 minutes by default to cover publish + Railway redeploy. Override either with `BOS_DEPLOY_LOCK_TTL_MS` (positive integer, milliseconds). +- `bos deploy lock inspect` reports the active lock owner/nonce/expires/txHash; `bos deploy lock release` force-clears a stuck lock. +- `bos infra export [--target ci|local] [--network mainnet|testnet]` emits `{env, services, account, gateway, project, generatedAt}` JSON. The deploy workflow now consumes this to populate `$GITHUB_ENV` instead of repeating `API_DATABASE_URL`, `AUTH_DATABASE_URL`, and `CORS_ORIGIN` literals. Host port comes from `BOS_CI_HOST_PORT` env or `runtimeConfig.host.port`. +- `buildOriginMap` now derives plugin origins from `runtimeConfig.plugins[id].extendsRef` / `runtimeConfig.auth?.extendsRef` (already populated by `loadResolvedConfig`), removing the duplicate raw-JSON read and the parent-runtime fallback logic. +- Three new routes on the `bos` contract: `infraExport`, `deployLockInspect`, `deployLockRelease`. New tests cover the lock helpers, the CI plan builder, and the resolved-config origin lookup. diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 300ff4e6..d5d82faf 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -32,9 +32,7 @@ jobs: ZE_USER_EMAIL: ${{ secrets.ZEPHYR_USER_EMAIL }} RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }} TARGET_BRANCH: ${{ github.ref_name }} - API_DATABASE_URL: postgres://everythingdev:everythingdev@127.0.0.1:5432/api_db - AUTH_DATABASE_URL: postgres://everythingdev:everythingdev@127.0.0.1:5433/auth_db - CORS_ORIGIN: http://127.0.0.1:4100 + BOS_CI_HOST_PORT: "4100" services: postgres-api: image: postgres:17-alpine @@ -95,6 +93,14 @@ jobs: run: | echo "ZE_CI_TOKEN=$ZEPHYR_CI_TOKEN" >> "$GITHUB_ENV" + - name: Emit CI infra plan + id: infra + run: | + bun run bos infra export --target ci --network mainnet > .ci-infra.json + jq -r '.env | to_entries[] | "\(.key)=\(.value)"' .ci-infra.json >> "$GITHUB_ENV" + echo "## CI infra plan" >> "$GITHUB_STEP_SUMMARY" + jq -r '"Services: " + (.services | length | tostring) + "\nEnv vars: " + (.env | keys | length | tostring)' .ci-infra.json >> "$GITHUB_STEP_SUMMARY" + - name: Publish with deploy timeout-minutes: 20 run: bun run bos publish --deploy${{ inputs.verbose == true && ' --verbose' || '' }} diff --git a/packages/everything-dev/src/cli.ts b/packages/everything-dev/src/cli.ts index b1bbd372..ed9de076 100644 --- a/packages/everything-dev/src/cli.ts +++ b/packages/everything-dev/src/cli.ts @@ -1019,12 +1019,24 @@ async function main() { return; } - if (result.status === "error") { + if (result.status === "error" || result.status === "locked") { console.log(); - console.log(colors.error(`${icons.err} Publish failed`)); + console.log( + colors.error( + `${icons.err} ${result.status === "locked" ? "Deploy lock blocks publish" : "Publish failed"}`, + ), + ); if (result.error) { console.log(` ${colors.dim("Error:")} ${result.error}`); } + if (result.lockConflict?.value) { + const owner = result.lockConflict.value.owner; + const nonce = result.lockConflict.value.nonce; + const expiresAt = result.lockConflict.value.expiresAt; + console.log(` ${colors.dim("Lock owner:")} ${owner}`); + console.log(` ${colors.dim("Lock nonce:")} ${nonce}`); + console.log(` ${colors.dim("Lock expires:")} ${new Date(expiresAt).toISOString()}`); + } if (result.deployResults && result.deployResults.length > 0) { const failures = result.deployResults.filter((r: any) => !r.success); if (failures.length > 0) { @@ -1077,12 +1089,24 @@ async function main() { return; } - if (deployResult.status === "error") { + if (deployResult.status === "error" || deployResult.status === "locked") { console.log(); - console.log(colors.error(`${icons.err} Deploy failed`)); + console.log( + colors.error( + `${icons.err} ${deployResult.status === "locked" ? "Deploy lock blocks publish" : "Deploy failed"}`, + ), + ); if (deployResult.error) { console.log(` ${colors.dim("Error:")} ${deployResult.error}`); } + if (deployResult.lockConflict?.value) { + const owner = deployResult.lockConflict.value.owner; + const nonce = deployResult.lockConflict.value.nonce; + const expiresAt = deployResult.lockConflict.value.expiresAt; + console.log(` ${colors.dim("Lock owner:")} ${owner}`); + console.log(` ${colors.dim("Lock nonce:")} ${nonce}`); + console.log(` ${colors.dim("Lock expires:")} ${new Date(expiresAt).toISOString()}`); + } if (deployResult.deployResults && deployResult.deployResults.length > 0) { const failures = deployResult.deployResults.filter((r: any) => !r.success); if (failures.length > 0) { @@ -1145,6 +1169,54 @@ async function main() { process.exit(1); } } + + if (descriptor.key === "infraExport") { + process.stdout.write(`${JSON.stringify(result, null, 2)}\n`); + return; + } + + if (descriptor.key === "deployLockInspect") { + const inspect = result as any; + console.log(); + console.log(` ${colors.dim("Account:")} ${inspect.account}`); + console.log(` ${colors.dim("Gateway:")} ${inspect.gateway}`); + console.log(` ${colors.dim("Network:")} ${inspect.network}`); + console.log(` ${colors.dim("Config:")} ${inspect.configRegistryUrl}`); + console.log(` ${colors.dim("Lock:")} ${inspect.lockRegistryUrl}`); + console.log( + ` ${colors.dim("Status:")} ${inspect.active ? colors.yellow("ACTIVE") : colors.green("free")}`, + ); + if (inspect.value) { + console.log(` ${colors.dim("Owner:")} ${inspect.value.owner}`); + console.log(` ${colors.dim("Nonce:")} ${inspect.value.nonce}`); + console.log( + ` ${colors.dim("Started:")} ${new Date(inspect.value.startedAt).toISOString()}`, + ); + console.log( + ` ${colors.dim("Expires:")} ${new Date(inspect.value.expiresAt).toISOString()}`, + ); + if (inspect.value.txHash) { + console.log(` ${colors.dim("Tx:")} ${inspect.value.txHash}`); + } + } + console.log(); + return; + } + + if (descriptor.key === "deployLockRelease") { + const release = result as { released: boolean; txHash?: string }; + console.log(); + if (release.released) { + console.log(colors.green(`${icons.ok} Deploy lock released`)); + if (release.txHash) { + console.log(` ${colors.dim("Transaction:")} ${release.txHash}`); + } + } else { + console.log(colors.dim(" No active deploy lock to release.")); + } + console.log(); + return; + } } catch (error) { console.error(`[CLI] ${error instanceof Error ? error.message : String(error)}`); process.exit(1); diff --git a/packages/everything-dev/src/cli/deploy-lock.ts b/packages/everything-dev/src/cli/deploy-lock.ts new file mode 100644 index 00000000..2b2c212e --- /dev/null +++ b/packages/everything-dev/src/cli/deploy-lock.ts @@ -0,0 +1,229 @@ +import { randomBytes } from "node:crypto"; +import { Effect } from "effect"; +import { + buildRegistryConfigUrl, + fetchBosConfigFromFastKv, + getFastKvBaseUrlForNetwork, + getRegistryNamespaceForNetwork, + type NetworkId, + parseBosUrl, +} from "../fastkv"; +import { fetchJsonOrNull } from "../http-client"; +import { executeTransaction, type NearSigningMode } from "../near-cli"; + +const LOCK_SUFFIX = "lock/deploy.json"; +const DEFAULT_TTL_MS = 10 * 60 * 1000; +const RELEASE_SENTINEL_VALUE = "{}"; + +export interface DeployLockValue { + owner: string; + pid: number | string; + startedAt: number; + expiresAt: number; + network: NetworkId; + nonce: string; + txHash?: string; +} + +export interface DeployLockConflict { + active: boolean; + expiresAt: number; + value: DeployLockValue | null; + reason: "active" | "verify-mismatch"; +} + +export type AcquireDeployLockResult = + | { acquired: true; nonce: string; txHash?: string } + | { acquired: false; conflict: DeployLockConflict }; + +export interface LockContext { + account: string; + gateway: string; + network: NetworkId; +} + +export interface AcquireOptions extends LockContext { + privateKey?: string; + signingMode?: NearSigningMode; + ttlMs?: number; + owner?: string; +} + +function buildDeployLockKey(account: string, gateway: string): string { + return `apps/${account}/${gateway}/${LOCK_SUFFIX}`; +} + +function buildDeployLockUrl(account: string, gateway: string, network: NetworkId): string { + const baseUrl = getFastKvBaseUrlForNetwork(network); + const namespace = getRegistryNamespaceForNetwork(network); + const key = buildDeployLockKey(account, gateway); + return `${baseUrl}/v0/latest/${encodeURIComponent(namespace)}/${encodeURIComponent(account)}/${encodeURIComponent(key)}`; +} + +function parseDeployLockValue(raw: unknown): DeployLockValue | null { + if (raw == null) return null; + let parsed: unknown = raw; + if (typeof raw === "string") { + if (raw === RELEASE_SENTINEL_VALUE) return null; + try { + parsed = JSON.parse(raw); + } catch { + return null; + } + } + if (!parsed || typeof parsed !== "object") return null; + const obj = parsed as Record; + if (typeof obj.nonce !== "string" || typeof obj.expiresAt !== "number") return null; + return { + owner: typeof obj.owner === "string" ? obj.owner : "unknown", + pid: typeof obj.pid === "number" || typeof obj.pid === "string" ? obj.pid : -1, + startedAt: typeof obj.startedAt === "number" ? obj.startedAt : 0, + expiresAt: obj.expiresAt, + network: + typeof obj.network === "string" && (obj.network === "mainnet" || obj.network === "testnet") + ? obj.network + : "mainnet", + nonce: obj.nonce, + txHash: typeof obj.txHash === "string" ? obj.txHash : undefined, + }; +} + +export async function fetchDeployLock( + ctx: LockContext, +): Promise<{ active: boolean; value: DeployLockValue | null }> { + const url = buildDeployLockUrl(ctx.account, ctx.gateway, ctx.network); + const payload = await fetchJsonOrNull<{ entries?: Array<{ value: unknown } | null> }>(url, { + retries: 0, + }); + const raw = payload?.entries?.find(Boolean)?.value; + const value = parseDeployLockValue(raw); + if (!value) return { active: false, value: null }; + return { active: value.expiresAt > Date.now(), value }; +} + +function buildLockArgsBase64(key: string, value: string): string { + const payload = JSON.stringify({ [key]: value }); + return Buffer.from(payload).toString("base64"); +} + +async function writeDeployLockValue( + ctx: LockContext, + value: string, + signingMode: NearSigningMode, + options: { privateKey?: string; verbose?: boolean }, +): Promise { + const tx = await Effect.runPromise( + executeTransaction( + { + account: ctx.account, + contract: getRegistryNamespaceForNetwork(ctx.network), + method: "__fastdata_kv", + argsBase64: buildLockArgsBase64(buildDeployLockKey(ctx.account, ctx.gateway), value), + network: ctx.network, + privateKey: signingMode._tag === "privateKey" ? signingMode.privateKey : undefined, + gas: "100Tgas", + deposit: "0NEAR", + verbose: options.verbose ?? false, + }, + signingMode, + ), + ); + return tx.txHash; +} + +export async function acquireDeployLock(options: AcquireOptions): Promise { + const signingMode = options.signingMode ?? { + _tag: "privateKey" as const, + privateKey: options.privateKey ?? "", + }; + const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS; + const owner = options.owner ?? "bos-publish"; + const nonce = randomBytes(8).toString("hex"); + const now = Date.now(); + const desired: DeployLockValue = { + owner, + pid: process.pid, + startedAt: now, + expiresAt: now + ttlMs, + network: options.network, + nonce, + }; + + const initial = await fetchDeployLock(options); + if (initial.active && initial.value) { + return { + acquired: false, + conflict: { + active: true, + expiresAt: initial.value.expiresAt, + value: initial.value, + reason: "active" as const, + }, + }; + } + + const txHash = await writeDeployLockValue(options, JSON.stringify(desired), signingMode, { + privateKey: options.privateKey, + }); + + const verify = await fetchDeployLock(options); + if (!verify.active || verify.value?.nonce !== nonce) { + return { + acquired: false, + conflict: { + active: verify.active, + expiresAt: verify.value?.expiresAt ?? 0, + value: verify.value, + reason: "verify-mismatch" as const, + }, + }; + } + + return { acquired: true, nonce, txHash }; +} + +export async function releaseDeployLock( + ctx: LockContext, + options: { privateKey?: string; signingMode?: NearSigningMode; force?: boolean }, +): Promise<{ released: boolean; txHash?: string }> { + const signingMode = options.signingMode ?? { + _tag: "privateKey" as const, + privateKey: options.privateKey ?? "", + }; + + const current = await fetchDeployLock(ctx); + + if (!options.force && !current.active) { + return { released: false }; + } + + const txHash = await writeDeployLockValue(ctx, RELEASE_SENTINEL_VALUE, signingMode, { + privateKey: options.privateKey, + }); + return { released: true, txHash }; +} + +export interface InspectDeployLockResult { + account: string; + gateway: string; + network: NetworkId; + configRegistryUrl: string; + lockRegistryUrl: string; + active: boolean; + value: DeployLockValue | null; +} + +export async function inspectDeployLock(ctx: LockContext): Promise { + const state = await fetchDeployLock(ctx); + return { + account: ctx.account, + gateway: ctx.gateway, + network: ctx.network, + configRegistryUrl: buildRegistryConfigUrl(ctx.account, ctx.gateway), + lockRegistryUrl: buildDeployLockUrl(ctx.account, ctx.gateway, ctx.network), + active: state.active, + value: state.value, + }; +} + +export { buildDeployLockKey, buildDeployLockUrl, parseDeployLockValue }; diff --git a/packages/everything-dev/src/cli/infra.ts b/packages/everything-dev/src/cli/infra.ts index 92a0731f..69b80990 100644 --- a/packages/everything-dev/src/cli/infra.ts +++ b/packages/everything-dev/src/cli/infra.ts @@ -118,6 +118,12 @@ export function normalizeRedisSlug(secret: string): string { return secret.replace(/_REDIS_URL$/, "").toLowerCase(); } +function extendsRefAccount(extendsRef: string | undefined): string | null { + if (!extendsRef || typeof extendsRef !== "string") return null; + const match = extendsRef.match(/^bos:\/\/([^/]+)\//); + return match?.[1] ?? null; +} + export function getSecretGroups(runtimeConfig: RuntimeConfig): SecretGroup[] { const groups: SecretGroup[] = []; const seen = new Set(); @@ -172,48 +178,23 @@ export function normalizeDatabaseSlug(secret: string): string { } export function buildOriginMap( - configDir: string, + _configDir: string, runtimeConfig: RuntimeConfig, ): Map { - const configPath = join(configDir, "bos.config.json"); - const originMap = new Map(); const account = runtimeConfig.account; - const resolveOrigin = (extendsRef: unknown): string | null => { - if (typeof extendsRef === "string") { - const match = extendsRef.match(/^bos:\/\/([^/]+)\//); - return match?.[1] ?? null; - } - return null; - }; - - const rawConfig = existsSync(configPath) - ? (JSON.parse(readFileSync(configPath, "utf-8")) as Record) - : null; - const rawPlugins = rawConfig?.plugins as Record | undefined; - for (const secret of runtimeConfig.api.secrets ?? []) { if (!originMap.has(secret)) originMap.set(secret, account); } - const rawApp = rawConfig?.app as Record | undefined; - const authExtends = (rawApp?.auth as Record | undefined)?.extends; - const authOrigin = resolveOrigin(authExtends) ?? account; + const authOrigin = extendsRefAccount(runtimeConfig.auth?.extendsRef) ?? account; for (const secret of runtimeConfig.auth?.secrets ?? []) { if (!originMap.has(secret)) originMap.set(secret, authOrigin); } for (const [pluginKey, pluginEntry] of Object.entries(runtimeConfig.plugins ?? {})) { - const rawPlugin = rawPlugins?.[pluginKey]; - let pluginOrigin: string; - if (typeof rawPlugin === "string") { - pluginOrigin = resolveOrigin(rawPlugin) ?? account; - } else if (rawPlugin && typeof rawPlugin === "object") { - pluginOrigin = resolveOrigin((rawPlugin as Record).extends) ?? account; - } else { - pluginOrigin = account; - } + const pluginOrigin = extendsRefAccount(pluginEntry.extendsRef) ?? account; for (const secret of pluginEntry.secrets ?? []) { if (!originMap.has(secret)) originMap.set(secret, pluginOrigin); } @@ -714,3 +695,146 @@ export function loadProjectEnv(configDir: string): void { loadDotenv({ path: envPath, processEnv: process.env, quiet: true }); envLoadedDir = configDir; } + +export interface CiServiceSpec { + key: string; + slug: string; + image: string; + env: Record; + ports: string[]; + healthcheck?: { test: string[]; interval: string; timeout: string; retries: number }; + volumes: string[]; + database?: { user: string; password: string; name: string }; +} + +export interface CiInfraPlan { + account: string; + gateway: string; + project: string; + env: Record; + services: CiServiceSpec[]; + generatedAt: string; +} + +export function buildCiInfraPlan( + runtimeConfig: RuntimeConfig, + options: { configDir?: string; hostPortOverride?: number } = {}, +): CiInfraPlan { + const configDir = options.configDir; + const originMap = configDir + ? buildOriginMap(configDir, runtimeConfig) + : new Map(); + const portState = loadPortState(configDir); + const allDatabaseSecrets = uniqueSecrets( + collectAllSecrets(runtimeConfig).filter((s) => s.endsWith("_DATABASE_URL")), + ); + const allDatabaseConfigs = buildDatabaseConfigs( + allDatabaseSecrets, + originMap, + portState.postgresPorts, + ); + const allRedisSecrets = uniqueSecrets( + collectAllSecrets(runtimeConfig).filter((s) => s.endsWith("_REDIS_URL")), + ); + const allRedisConfigs = buildRedisConfigs(allRedisSecrets, originMap, portState.redisPorts); + if (configDir) savePortState(configDir, portState); + + const hostPort = + options.hostPortOverride ?? + (Number.isFinite(Number(process.env.BOS_CI_HOST_PORT)) + ? Number(process.env.BOS_CI_HOST_PORT) + : undefined) ?? + resolveDevHostPort(runtimeConfig); + const env: Record = {}; + const envBySecret = new Map(); + for (const db of allDatabaseConfigs) envBySecret.set(db.secret, db.url); + for (const r of allRedisConfigs) envBySecret.set(r.secret, r.url); + + const groups = getSecretGroups(runtimeConfig); + for (const group of groups) { + for (const secret of group.secrets) { + const value = envBySecret.get(secret); + if (value) env[secret] = value; + } + } + + env["CORS_ORIGIN"] = `http://127.0.0.1:${hostPort}`; + if (!env["BETTER_AUTH_SECRET"]) env["BETTER_AUTH_SECRET"] = ""; + + const services: CiServiceSpec[] = []; + + for (const db of allDatabaseConfigs) { + const fromKey = db.fromKey || runtimeConfig.account; + services.push({ + key: db.slug, + slug: db.slug, + image: "postgres:17-alpine", + env: { + POSTGRES_USER: POSTGRES_USER, + POSTGRES_PASSWORD: POSTGRES_PASSWORD, + POSTGRES_DB: db.databaseName, + }, + ports: [`${db.port}:5432`], + healthcheck: { + test: ["CMD-SHELL", `pg_isready -U ${POSTGRES_USER}`], + interval: "3s", + timeout: "3s", + retries: 10, + }, + volumes: [`${fromKey.replace(/\./g, "_")}_postgres_${db.slug}_data:/var/lib/postgresql/data`], + database: { user: POSTGRES_USER, password: POSTGRES_PASSWORD, name: db.databaseName }, + }); + } + + for (const r of allRedisConfigs) { + services.push({ + key: r.slug, + slug: r.slug, + image: "redis:7-alpine", + env: {}, + ports: [`${r.port}:6379`], + healthcheck: { + test: ["CMD", "redis-cli", "ping"], + interval: "3s", + timeout: "3s", + retries: 10, + }, + volumes: [ + `${(r.fromKey || runtimeConfig.account).replace(/\./g, "_")}_redis_${r.slug}_data:/data`, + ], + }); + } + + return { + account: runtimeConfig.account, + gateway: runtimeConfig.domain ?? runtimeConfig.account, + project: runtimeConfig.account, + env, + services, + generatedAt: new Date().toISOString(), + }; +} + +function collectAllSecrets(runtimeConfig: RuntimeConfig): string[] { + const all: string[] = []; + all.push(...(runtimeConfig.host.secrets ?? [])); + all.push(...(runtimeConfig.api.secrets ?? [])); + if (runtimeConfig.auth) all.push(...(runtimeConfig.auth.secrets ?? [])); + if (runtimeConfig.plugins) { + for (const plugin of Object.values(runtimeConfig.plugins)) { + if (plugin.secrets) all.push(...plugin.secrets); + } + } + return all; +} + +function uniqueSlugs(entries: T[]): T[] { + const seen = new Set(); + const out: T[] = []; + for (const entry of entries) { + if (seen.has(entry.slug)) continue; + seen.add(entry.slug); + out.push(entry); + } + return out; +} diff --git a/packages/everything-dev/src/contract.meta.ts b/packages/everything-dev/src/contract.meta.ts index 71418b4b..2e403533 100644 --- a/packages/everything-dev/src/contract.meta.ts +++ b/packages/everything-dev/src/contract.meta.ts @@ -229,4 +229,24 @@ export const cliCommandMeta = { all: { description: "Kill processes across all config directories" }, }, }, + infraExport: { + commandPath: ["infra", "export"], + summary: "Emit the resolved CI infra plan (env + services) for the current runtime", + interactive: false, + fields: { + target: { description: "Export target: ci (default) or local" }, + network: { description: "NEAR network: mainnet or testnet" }, + configDir: { description: "Override config directory" }, + }, + }, + deployLockInspect: { + commandPath: ["deploy", "lock", "inspect"], + summary: "Inspect the FastKV deploy lock for the current account/gateway", + interactive: false, + }, + deployLockRelease: { + commandPath: ["deploy", "lock", "release"], + summary: "Force-release the FastKV deploy lock", + interactive: false, + }, } as const satisfies Record; diff --git a/packages/everything-dev/src/contract.ts b/packages/everything-dev/src/contract.ts index dfcab759..f6c54382 100644 --- a/packages/everything-dev/src/contract.ts +++ b/packages/everything-dev/src/contract.ts @@ -145,16 +145,35 @@ export const PublishOptionsSchema = z.object({ network: z.enum(["mainnet", "testnet"]).optional(), privateKey: z.string().optional(), env: z.enum(["production", "staging"]).default("production"), + noDeployLock: z.boolean().default(false), }); export const PublishResultSchema = z.object({ - status: z.enum(["published", "error", "dry-run"]), + status: z.enum(["published", "error", "dry-run", "locked"]), registryUrl: z.string(), txHash: z.string().optional(), error: z.string().optional(), built: z.array(z.string()).optional(), skipped: z.array(z.string()).optional(), deployResults: z.array(WorkspaceDeployResultSchema).optional(), + lockConflict: z + .object({ + active: z.boolean(), + expiresAt: z.number(), + reason: z.enum(["active", "verify-mismatch"]), + value: z + .object({ + owner: z.string(), + pid: z.union([z.number(), z.string()]), + startedAt: z.number(), + expiresAt: z.number(), + network: z.enum(["mainnet", "testnet"]), + nonce: z.string(), + txHash: z.string().optional(), + }) + .nullable(), + }) + .optional(), }); export const DeployOptionsSchema = z.object({ @@ -166,10 +185,11 @@ export const DeployOptionsSchema = z.object({ network: z.enum(["mainnet", "testnet"]).optional(), privateKey: z.string().optional(), service: z.string().optional(), + noDeployLock: z.boolean().default(false), }); export const DeployResultSchema = z.object({ - status: z.enum(["deployed", "published", "error", "dry-run"]), + status: z.enum(["deployed", "published", "error", "dry-run", "locked"]), registryUrl: z.string(), txHash: z.string().optional(), built: z.array(z.string()).optional(), @@ -178,6 +198,7 @@ export const DeployResultSchema = z.object({ service: z.string().optional(), error: z.string().optional(), deployResults: z.array(WorkspaceDeployResultSchema).optional(), + lockConflict: PublishResultSchema.shape.lockConflict, }); function parseNearAmount(value: string): number { @@ -441,6 +462,37 @@ export const TypecheckResultSchema = z.object({ error: z.string().optional(), }); +export const InfraExportServiceSchema = z.object({ + key: z.string(), + image: z.string(), + env: z.record(z.string(), z.string()).default({}), + ports: z.array(z.string()), + healthcheck: z + .object({ + test: z.array(z.string()), + interval: z.string(), + timeout: z.string(), + retries: z.number(), + }) + .optional(), + volumes: z.array(z.string()).default([]), +}); + +export const InfraExportOptionsSchema = z.object({ + target: z.enum(["ci", "local"]).default("ci"), + network: z.enum(["mainnet", "testnet"]).optional(), + configDir: z.string().optional(), +}); + +export const InfraExportResultSchema = z.object({ + env: z.record(z.string(), z.string()), + services: z.array(InfraExportServiceSchema), + generatedAt: z.string(), + project: z.string(), + account: z.string(), + gateway: z.string(), +}); + export const bosContract = oc.router({ dev: oc.route({ method: "POST", path: "/dev" }).input(DevOptionsSchema).output(DevResultSchema), start: oc @@ -518,6 +570,37 @@ export const bosContract = oc.router({ .route({ method: "POST", path: "/typecheck" }) .input(TypecheckOptionsSchema) .output(TypecheckResultSchema), + infraExport: oc + .route({ method: "POST", path: "/infra/export" }) + .input(InfraExportOptionsSchema) + .output(InfraExportResultSchema), + deployLockInspect: oc.route({ method: "GET", path: "/deploy/lock" }).output( + z.object({ + account: z.string(), + gateway: z.string(), + network: z.enum(["mainnet", "testnet"]), + configRegistryUrl: z.string(), + lockRegistryUrl: z.string(), + active: z.boolean(), + value: z + .object({ + owner: z.string(), + pid: z.union([z.number(), z.string()]), + startedAt: z.number(), + expiresAt: z.number(), + network: z.enum(["mainnet", "testnet"]), + nonce: z.string(), + txHash: z.string().optional(), + }) + .nullable(), + }), + ), + deployLockRelease: oc.route({ method: "POST", path: "/deploy/lock/release" }).output( + z.object({ + released: z.boolean(), + txHash: z.string().optional(), + }), + ), }); export type DevOptions = z.infer; @@ -566,3 +649,6 @@ export type KillResult = z.infer; export type TypecheckOptions = z.infer; export type TypecheckResult = z.infer; export type TypecheckWorkspaceResult = z.infer; +export type InfraExportOptions = z.infer; +export type InfraExportResult = z.infer; +export type InfraExportService = z.infer; diff --git a/packages/everything-dev/src/plugin.ts b/packages/everything-dev/src/plugin.ts index 9b192b53..d347c8c7 100644 --- a/packages/everything-dev/src/plugin.ts +++ b/packages/everything-dev/src/plugin.ts @@ -15,7 +15,10 @@ import { readJsonFile, selectWorkspaceTargets, } from "./build"; +import { inspectDeployLock, releaseDeployLock } from "./cli/deploy-lock"; import { + buildCiInfraPlan, + type CiInfraPlan, ensureEnvFile, loadProjectEnv, syncGeneratedInfra, @@ -1039,6 +1042,8 @@ export default createPlugin({ packages: input.packages, network: input.network, privateKey: input.privateKey, + skipDeployLock: input.noDeployLock, + deployLockTtlMs: resolveDeployLockTtlFromEnv(), }); if (result.publishConfig) { @@ -1057,6 +1062,7 @@ export default createPlugin({ built: result.built, skipped: result.skipped, deployResults: result.deployResults, + lockConflict: result.lockConflict, }; }), @@ -1081,6 +1087,8 @@ export default createPlugin({ packages: input.packages, network: input.network, privateKey: input.privateKey, + skipDeployLock: input.noDeployLock, + deployLockTtlMs: resolveDeployLockTtlFromEnv({ extended: true }), }); if (result.status === "error") { @@ -1093,6 +1101,7 @@ export default createPlugin({ redeployed: false, error: result.error, deployResults: result.deployResults, + lockConflict: result.lockConflict, }; } @@ -1106,6 +1115,20 @@ export default createPlugin({ }; } + if (result.status === "locked") { + return { + status: "locked" as const, + registryUrl: result.registryUrl, + txHash: result.txHash, + built: result.built, + skipped: result.skipped, + redeployed: false, + error: result.error, + deployResults: result.deployResults, + lockConflict: result.lockConflict, + }; + } + if (result.publishConfig) { const refreshed = await loadResolvedConfig({ cwd: deps.configDir }); if (refreshed?.config) { @@ -2130,9 +2153,74 @@ export default createPlugin({ }; } }), + + infraExport: builder.infraExport.handler(async ({ input }) => { + const configDir = input.configDir ?? deps.configDir; + const ci = deps.runtimeConfig ? buildCiInfraPlan(deps.runtimeConfig, { configDir }) : null; + if (!ci) { + const refreshed = await loadResolvedConfig({ cwd: configDir }); + if (!refreshed?.runtime) { + throw new Error("No resolved runtime config available for infra export"); + } + deps.runtimeConfig = refreshed.runtime; + return buildCiInfraPlan(refreshed.runtime, { configDir }); + } + const result: CiInfraPlan & { account: string; gateway: string } = { + ...ci, + account: deps.bosConfig?.account ?? ci.account, + gateway: ci.gateway ?? deps.bosConfig?.domain ?? deps.bosConfig?.account ?? ci.account, + }; + return result; + }), + + deployLockInspect: builder.deployLockInspect.handler(async () => { + if (!deps.bosConfig) { + throw new Error("No bos.config.json found"); + } + const account = deps.bosConfig.account; + const gateway = deps.bosConfig.staging?.domain ?? deps.bosConfig.domain ?? account; + if (!gateway) { + throw new Error("bos.config.json must define domain to inspect deploy lock"); + } + const network = getNetworkIdForAccount(account); + return inspectDeployLock({ account, gateway, network }); + }), + + deployLockRelease: builder.deployLockRelease.handler(async () => { + if (!deps.bosConfig) { + throw new Error("No bos.config.json found"); + } + const account = deps.bosConfig.account; + const gateway = deps.bosConfig.staging?.domain ?? deps.bosConfig.domain ?? account; + if (!gateway) { + throw new Error("bos.config.json must define domain to release deploy lock"); + } + const network = getNetworkIdForAccount(account); + const privateKey = process.env.NEAR_PRIVATE_KEY || process.env.BOS_NEAR_PRIVATE_KEY; + const result = await releaseDeployLock( + { account, gateway, network }, + { privateKey, force: true }, + ); + return { released: result.released, txHash: result.txHash }; + }), }), }); +const PUBLISH_LOCK_TTL_MS = 10 * 60 * 1000; +const DEPLOY_LOCK_TTL_MS = 25 * 60 * 1000; + +export function resolveDeployLockTtlFromEnv(opts: { extended?: boolean } = {}): number { + const raw = process.env.BOS_DEPLOY_LOCK_TTL_MS; + if (raw !== undefined && raw !== "") { + const parsed = Number(raw); + if (Number.isFinite(parsed) && parsed > 0) return parsed; + } + return opts.extended ? DEPLOY_LOCK_TTL_MS : PUBLISH_LOCK_TTL_MS; +} + +export const PUBLISH_LOCK_TTL_DEFAULT_MS = PUBLISH_LOCK_TTL_MS; +export const DEPLOY_LOCK_TTL_DEFAULT_MS = DEPLOY_LOCK_TTL_MS; + function computeAllowedWorkspaces(overrides: string[], plugins?: string[]): string[] { const workspaces: string[] = []; for (const section of overrides) { diff --git a/packages/everything-dev/src/publish.ts b/packages/everything-dev/src/publish.ts index 4f721102..545767fa 100644 --- a/packages/everything-dev/src/publish.ts +++ b/packages/everything-dev/src/publish.ts @@ -3,6 +3,12 @@ import { join } from "node:path"; import process from "node:process"; import { Effect } from "effect"; import { buildWorkspaceTargets, selectWorkspaceTargets } from "./build"; +import { + acquireDeployLock, + type DeployLockConflict, + fetchDeployLock, + releaseDeployLock, +} from "./cli/deploy-lock"; import { generateCodeArtifacts } from "./code-artifacts"; import { loadResolvedConfig } from "./config"; import type { WorkspaceDeployResult } from "./contract"; @@ -10,6 +16,7 @@ import { buildRegistryConfigUrlForNetwork, fetchBosConfigFromFastKv, getRegistryNamespaceForNetwork, + type NetworkId, } from "./fastkv"; import { ensureNearCli, executeTransaction, resolveNearSigningMode } from "./near-cli"; import { getNetworkIdForAccount } from "./network"; @@ -78,10 +85,12 @@ interface PublishToFastKvInput { packages: string; network?: "mainnet" | "testnet"; privateKey?: string; + skipDeployLock?: boolean; + deployLockTtlMs?: number; } interface PublishToFastKvResult { - status: "published" | "error" | "dry-run"; + status: "published" | "error" | "dry-run" | "locked"; registryUrl: string; txHash?: string; built?: string[]; @@ -89,6 +98,7 @@ interface PublishToFastKvResult { error?: string; publishConfig?: BosConfigInput; deployResults?: WorkspaceDeployResult[]; + lockConflict?: DeployLockConflict; } export async function publishToFastKv(input: PublishToFastKvInput): Promise { @@ -107,7 +117,7 @@ export async function publishToFastKv(input: PublishToFastKvInput): Promise !r.success); - if (failures.length > 0) { - const total = deployResults.length; - console.log(); - console.log( - colors.error( - ` ${icons.err} Deploy failed — ${failures.length} of ${total} workspace${total > 1 ? "s" : ""} failed`, - ), - ); - console.log(); - for (const f of failures) { - const errorLine = (f.error ?? "Failed").split("\n")[0]; - console.log(` ${colors.error(icons.err)} ${padRight(f.key, 28)} ${errorLine}`); - } - console.log(); - if (!input.verbose) { - console.log(colors.dim(" Run with --verbose for full build output.")); + try { + if (input.build) { + console.log(" Ensuring NEAR CLI..."); + await Effect.runPromise(ensureNearCli); + console.log(" NEAR CLI ready"); + + await generateCodeArtifacts(configDir, bosConfig, { + env: "production", + runtimeConfig: runtimeConfig ?? undefined, + }); + + const result = await buildWorkspaceTargets({ + configDir, + bosConfig, + runtimeConfig, + targets, + deploy: true, + verbose: input.verbose, + }); + built = result.built; + skipped = result.skipped; + deployResults = result.deployResults; + + if (deployResults) { + const failures = deployResults.filter((r) => !r.success); + if (failures.length > 0) { + const total = deployResults.length; + console.log(); + console.log( + colors.error( + ` ${icons.err} Deploy failed — ${failures.length} of ${total} workspace${total > 1 ? "s" : ""} failed`, + ), + ); console.log(); + for (const f of failures) { + const errorLine = (f.error ?? "Failed").split("\n")[0]; + console.log(` ${colors.error(icons.err)} ${padRight(f.key, 28)} ${errorLine}`); + } + console.log(); + if (!input.verbose) { + console.log(colors.dim(" Run with --verbose for full build output.")); + console.log(); + } + return { + status: "error" as const, + registryUrl, + built, + skipped, + deployResults, + error: `${failures.length} of ${total} workspaces failed to deploy`, + }; } + } + + const refreshed = await loadResolvedConfig({ cwd: configDir }); + if (!refreshed?.config) { return { - status: "error" as const, + status: "error", registryUrl, built, skipped, deployResults, - error: `${failures.length} of ${total} workspaces failed to deploy`, + error: "Failed to reload bos.config.json after build", }; } - } - const refreshed = await loadResolvedConfig({ cwd: configDir }); - if (!refreshed?.config) { - return { - status: "error", - registryUrl, - built, - skipped, - deployResults, - error: "Failed to reload bos.config.json after build", - }; + bosConfig = refreshed.config; } - bosConfig = refreshed.config; - } + const rawConfigPath = join(configDir, "bos.config.json"); + const rawConfig = JSON.parse(readFileSync(rawConfigPath, "utf-8")) as BosConfigInput; + const publishPayload: BosConfigInput = isStaging + ? { ...rawConfig, domain: gateway } + : rawConfig; - const rawConfigPath = join(configDir, "bos.config.json"); - const rawConfig = JSON.parse(readFileSync(rawConfigPath, "utf-8")) as BosConfigInput; - const publishPayload: BosConfigInput = isStaging ? { ...rawConfig, domain: gateway } : rawConfig; - - const registryEntries: Record = { - [`apps/${account}/${gateway}/bos.config.json`]: JSON.stringify(publishPayload), - }; + const registryEntries: Record = { + [`apps/${account}/${gateway}/bos.config.json`]: JSON.stringify(publishPayload), + }; - const payload = JSON.stringify(registryEntries); - const argsBase64 = Buffer.from(payload).toString("base64"); + const payload = JSON.stringify(registryEntries); + const argsBase64 = Buffer.from(payload).toString("base64"); - console.log(); - console.log(" Publishing to:"); - console.log(` ${colors.cyan(registryUrl)}`); + console.log(); + console.log(" Publishing to:"); + console.log(` ${colors.cyan(registryUrl)}`); - try { - let txHash: string | undefined; + try { + let txHash: string | undefined; - console.log(` Submitting transaction on ${network}...`); + console.log(` Submitting transaction on ${network}...`); - try { - const tx = await Effect.runPromise( - executeTransaction( - { + try { + const tx = await Effect.runPromise( + executeTransaction( + { + account, + contract: getRegistryNamespaceForNetwork(network), + method: "__fastdata_kv", + argsBase64, + network, + privateKey: signingMode._tag === "privateKey" ? signingMode.privateKey : undefined, + gas: "300Tgas", + deposit: "0NEAR", + verbose: input.verbose, + }, + signingMode, + ), + ); + txHash = tx.txHash; + if (txHash && !tx.output?.includes("CodeDoesNotExist")) { + console.log(` Transaction submitted: ${colors.dim(txHash)}`); + } + } catch (error) { + console.log(colors.dim(" Transaction reported an error — verifying publish...")); + try { + await waitForPublishedConfig({ account, - contract: getRegistryNamespaceForNetwork(network), - method: "__fastdata_kv", - argsBase64, - network, - privateKey: signingMode._tag === "privateKey" ? signingMode.privateKey : undefined, - gas: "300Tgas", - deposit: "0NEAR", - verbose: input.verbose, - }, - signingMode, - ), - ); - txHash = tx.txHash; - if (txHash && !tx.output?.includes("CodeDoesNotExist")) { - console.log(` Transaction submitted: ${colors.dim(txHash)}`); + gateway, + publishConfig: publishPayload, + timeoutMs: 30_000, + intervalMs: 2_000, + }); + txHash = extractTransactionHash(error); + } catch { + throw error; + } } + + console.log(" Waiting for publish confirmation..."); + await waitForPublishedConfig({ + account, + gateway, + publishConfig: publishPayload, + }); + + return { + status: "published", + registryUrl, + txHash, + built, + skipped, + deployResults, + publishConfig: publishPayload, + }; } catch (error) { - console.log(colors.dim(" Transaction reported an error — verifying publish...")); + return { + status: "error", + registryUrl, + error: formatNearError(error), + built, + skipped, + deployResults, + }; + } + } finally { + if (!input.skipDeployLock) { try { - await waitForPublishedConfig({ - account, - gateway, - publishConfig: publishPayload, - timeoutMs: 30_000, - intervalMs: 2_000, - }); - txHash = extractTransactionHash(error); - } catch { - throw error; + await releaseDeployLock(lockContext, { privateKey, signingMode }); + } catch (error) { + console.log( + colors.dim( + ` Failed to release deploy lock for ${account}/${gateway}: ${error instanceof Error ? error.message : String(error)}`, + ), + ); } } - - console.log(" Waiting for publish confirmation..."); - await waitForPublishedConfig({ - account, - gateway, - publishConfig: publishPayload, - }); - - return { - status: "published", - registryUrl, - txHash, - built, - skipped, - deployResults, - publishConfig: publishPayload, - }; - } catch (error) { - return { - status: "error", - registryUrl, - error: formatNearError(error), - built, - skipped, - deployResults, - }; } } diff --git a/packages/everything-dev/tests/unit/deploy-lock.test.ts b/packages/everything-dev/tests/unit/deploy-lock.test.ts new file mode 100644 index 00000000..40937e3b --- /dev/null +++ b/packages/everything-dev/tests/unit/deploy-lock.test.ts @@ -0,0 +1,115 @@ +import { afterEach, describe, expect, it } from "vitest"; +import { + buildDeployLockKey, + buildDeployLockUrl, + parseDeployLockValue, +} from "../../src/cli/deploy-lock"; +import { + DEPLOY_LOCK_TTL_DEFAULT_MS, + PUBLISH_LOCK_TTL_DEFAULT_MS, + resolveDeployLockTtlFromEnv, +} from "../../src/plugin"; + +describe("deploy-lock TTL resolution", () => { + const previous = process.env.BOS_DEPLOY_LOCK_TTL_MS; + + afterEach(() => { + if (previous === undefined) delete process.env.BOS_DEPLOY_LOCK_TTL_MS; + else process.env.BOS_DEPLOY_LOCK_TTL_MS = previous; + }); + + it("uses 10 min default for publish", () => { + delete process.env.BOS_DEPLOY_LOCK_TTL_MS; + expect(resolveDeployLockTtlFromEnv()).toBe(PUBLISH_LOCK_TTL_DEFAULT_MS); + expect(resolveDeployLockTtlFromEnv({ extended: false })).toBe(PUBLISH_LOCK_TTL_DEFAULT_MS); + }); + + it("uses 25 min default for deploy", () => { + delete process.env.BOS_DEPLOY_LOCK_TTL_MS; + expect(resolveDeployLockTtlFromEnv({ extended: true })).toBe(DEPLOY_LOCK_TTL_DEFAULT_MS); + }); + + it("honors BOS_DEPLOY_LOCK_TTL_MS override for both modes", () => { + process.env.BOS_DEPLOY_LOCK_TTL_MS = "1800000"; + expect(resolveDeployLockTtlFromEnv()).toBe(1_800_000); + expect(resolveDeployLockTtlFromEnv({ extended: true })).toBe(1_800_000); + }); + + it("falls back to default when env value is invalid", () => { + process.env.BOS_DEPLOY_LOCK_TTL_MS = "not-a-number"; + expect(resolveDeployLockTtlFromEnv()).toBe(PUBLISH_LOCK_TTL_DEFAULT_MS); + expect(resolveDeployLockTtlFromEnv({ extended: true })).toBe(DEPLOY_LOCK_TTL_DEFAULT_MS); + }); +}); + +describe("deploy-lock helpers", () => { + describe("parseDeployLockValue", () => { + it("returns null for empty/sentinel values", () => { + expect(parseDeployLockValue(null)).toBeNull(); + expect(parseDeployLockValue(undefined)).toBeNull(); + expect(parseDeployLockValue("{}")).toBeNull(); + }); + + it("parses stringified JSON values", () => { + const stored = JSON.stringify({ + owner: "alice", + pid: 4242, + startedAt: 1, + expiresAt: Date.now() + 60_000, + network: "mainnet", + nonce: "abc123", + }); + const result = parseDeployLockValue(stored); + expect(result).not.toBeNull(); + expect(result?.owner).toBe("alice"); + expect(result?.pid).toBe(4242); + expect(result?.nonce).toBe("abc123"); + expect(result?.network).toBe("mainnet"); + }); + + it("parses object values", () => { + const result = parseDeployLockValue({ + owner: "deployer", + pid: 99, + startedAt: 1000, + expiresAt: 2000, + network: "testnet", + nonce: "deadbeef", + }); + expect(result?.owner).toBe("deployer"); + expect(result?.network).toBe("testnet"); + }); + + it("returns null when nonce or expiresAt is missing", () => { + expect(parseDeployLockValue({ owner: "x" })).toBeNull(); + expect(parseDeployLockValue({ nonce: "abc" })).toBeNull(); + expect(parseDeployLockValue({ expiresAt: 1000 })).toBeNull(); + }); + }); + + describe("buildDeployLockKey", () => { + it("returns expected apps/.../lock/deploy.json path", () => { + expect(buildDeployLockKey("v1.foo.near", "citynode.app")).toBe( + "apps/v1.foo.near/citynode.app/lock/deploy.json", + ); + }); + }); + + describe("buildDeployLockUrl", () => { + it("builds the FastKV GET URL for mainnet", () => { + const url = buildDeployLockUrl("v1.foo.near", "citynode.app", "mainnet"); + expect(url).toContain("kv.main.fastnear.com"); + expect(url).toContain("v1.foo.near"); + expect(encodeURIComponent("apps/v1.foo.near/citynode.app/lock/deploy.json")).toBeTruthy(); + expect( + decodeURIComponent(encodeURIComponent("apps/v1.foo.near/citynode.app/lock/deploy.json")), + ).toBe("apps/v1.foo.near/citynode.app/lock/deploy.json"); + expect(url).toContain("deploy.json"); + }); + + it("builds the FastKV GET URL for testnet", () => { + const url = buildDeployLockUrl("v1.foo.near", "citynode.app", "testnet"); + expect(url).toContain("kv.test.fastnear.com"); + }); + }); +}); diff --git a/packages/everything-dev/tests/unit/infra-export.test.ts b/packages/everything-dev/tests/unit/infra-export.test.ts new file mode 100644 index 00000000..e3c5fffb --- /dev/null +++ b/packages/everything-dev/tests/unit/infra-export.test.ts @@ -0,0 +1,199 @@ +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; +import { + buildCiInfraPlan, + buildOriginMap, + ensureEnvFile, + syncGeneratedInfra, + writeGeneratedInfra, +} from "../../src/cli/infra"; +import type { RuntimeConfig, RuntimePluginConfig } from "../../src/types"; + +function buildRuntimeConfig(overrides?: Partial): RuntimeConfig { + return { + env: "development", + account: "dev.everything.near", + networkId: "mainnet", + domain: "dev.everything.dev", + host: { + name: "host", + url: "http://localhost:4100", + entry: "/mf-manifest.json", + port: 4100, + }, + ui: { name: "ui", url: "http://localhost:3003", entry: "/mf-manifest.json" }, + api: { + name: "api", + url: "http://localhost:3001", + entry: "/mf-manifest.json", + secrets: ["API_DATABASE_URL"], + }, + auth: { + name: "auth", + url: "http://localhost:3002", + entry: "/mf-manifest.json", + secrets: ["AUTH_DATABASE_URL", "BETTER_AUTH_SECRET", "CORS_ORIGIN"], + }, + plugins: { + example: { + name: "example", + url: "http://localhost:3010", + entry: "/mf-manifest.json", + source: "local" as const, + secrets: ["EXAMPLE_DATABASE_URL"], + } as RuntimePluginConfig, + }, + ...overrides, + } as RuntimeConfig; +} + +describe("buildCiInfraPlan", () => { + const tempDirs: string[] = []; + + afterEach(() => { + while (tempDirs.length > 0) { + const dir = tempDirs.pop(); + if (dir) rmSync(dir, { recursive: true, force: true }); + } + }); + + it("emits env vars and services for api+auth+plugin secrets", () => { + const dir = mkdtempSync(join(tmpdir(), "bos-ci-infra-")); + tempDirs.push(dir); + writeGeneratedInfra(dir, buildRuntimeConfig()); + ensureEnvFile(dir); + + const runtime = { + ...buildRuntimeConfig(), + env: "production" as const, + }; + const plan = buildCiInfraPlan(runtime, { configDir: dir }); + + expect(plan.env["API_DATABASE_URL"]).toBe( + "postgres://everythingdev:everythingdev@localhost:5432/api_db", + ); + expect(plan.env["AUTH_DATABASE_URL"]).toBe( + "postgres://everythingdev:everythingdev@localhost:5433/auth_db", + ); + expect(plan.env["EXAMPLE_DATABASE_URL"]).toBe( + "postgres://everythingdev:everythingdev@localhost:5434/example_db", + ); + expect(plan.env["BETTER_AUTH_SECRET"]).toBe(""); + expect(plan.env["CORS_ORIGIN"]).toBe("http://127.0.0.1:4100"); + + expect(plan.services.length).toBeGreaterThanOrEqual(3); + const serviceKeys = plan.services.map((s) => s.key); + expect(serviceKeys).toContain("api"); + expect(serviceKeys).toContain("auth"); + expect(serviceKeys).toContain("example"); + + const apiService = plan.services.find((s) => s.key === "api"); + expect(apiService?.image).toBe("postgres:17-alpine"); + expect(apiService?.ports).toEqual(["5432:5432"]); + expect(apiService?.database).toEqual({ + user: "everythingdev", + password: "everythingdev", + name: "api_db", + }); + }); + + it("honors hostPortOverride and BOS_CI_HOST_PORT env fallback", () => { + const previous = process.env.BOS_CI_HOST_PORT; + try { + process.env.BOS_CI_HOST_PORT = "5173"; + const plan = buildCiInfraPlan(buildRuntimeConfig(), {}); + expect(plan.env["CORS_ORIGIN"]).toBe("http://127.0.0.1:5173"); + + const override = buildCiInfraPlan(buildRuntimeConfig(), { hostPortOverride: 8080 }); + expect(override.env["CORS_ORIGIN"]).toBe("http://127.0.0.1:8080"); + } finally { + if (previous === undefined) delete process.env.BOS_CI_HOST_PORT; + else process.env.BOS_CI_HOST_PORT = previous; + } + }); + + it("tracks stable ports across calls (portMap persistence)", () => { + const dir = mkdtempSync(join(tmpdir(), "bos-ci-infra-ports-")); + tempDirs.push(dir); + const cfg = buildRuntimeConfig(); + syncGeneratedInfra(dir, cfg); + const first = buildCiInfraPlan(cfg, { configDir: dir }); + const second = buildCiInfraPlan(cfg, { configDir: dir }); + + const firstApi = first.services.find((s) => s.key === "api"); + const secondApi = second.services.find((s) => s.key === "api"); + expect(firstApi?.ports).toEqual(secondApi?.ports); + }); +}); + +describe("buildOriginMap from resolved RuntimeConfig", () => { + it("uses plugin extendsRef for plugin origins", () => { + const runtime: RuntimeConfig = { + env: "development", + account: "city.example.near", + networkId: "mainnet", + host: { name: "host", url: "http://localhost:3000", entry: "/mf-manifest.json" }, + ui: { name: "ui", url: "http://localhost:3003", entry: "/mf-manifest.json" }, + api: { + name: "api", + url: "http://localhost:3001", + entry: "/mf-manifest.json", + secrets: ["API_DATABASE_URL"], + }, + auth: { + name: "auth", + url: "http://localhost:3002", + entry: "/mf-manifest.json", + extendsRef: "bos://auth.everything.near/auth.everything.dev#app.auth", + secrets: ["AUTH_DATABASE_URL"], + }, + plugins: { + apps: { + name: "apps", + url: "http://localhost:3010", + entry: "/mf-manifest.json", + source: "local" as const, + extendsRef: "bos://something/near/gateway", + secrets: ["APPS_DATABASE_URL"], + } as RuntimePluginConfig, + }, + } as RuntimeConfig; + + const map = buildOriginMap("", runtime); + + expect(map.get("API_DATABASE_URL")).toBe("city.example.near"); + expect(map.get("AUTH_DATABASE_URL")).toBe("auth.everything.near"); + expect(map.get("APPS_DATABASE_URL")).toBe("something"); + }); + + it("falls back to runtime.account when extendsRef is absent", () => { + const runtime: RuntimeConfig = { + env: "development", + account: "city.example.near", + networkId: "mainnet", + host: { name: "host", url: "http://localhost:3000", entry: "/mf-manifest.json" }, + ui: { name: "ui", url: "http://localhost:3003", entry: "/mf-manifest.json" }, + api: { + name: "api", + url: "http://localhost:3001", + entry: "/mf-manifest.json", + secrets: ["API_DATABASE_URL"], + }, + plugins: { + localonly: { + name: "localonly", + url: "http://localhost:3010", + entry: "/mf-manifest.json", + source: "local" as const, + secrets: ["LOCALONLY_DATABASE_URL"], + } as RuntimePluginConfig, + }, + } as RuntimeConfig; + + const map = buildOriginMap("", runtime); + expect(map.get("API_DATABASE_URL")).toBe("city.example.near"); + expect(map.get("LOCALONLY_DATABASE_URL")).toBe("city.example.near"); + }); +}); diff --git a/ui/src/routes/_layout/_authenticated/stake.tsx b/ui/src/routes/_layout/_authenticated/stake.tsx index a48625c1..85481ed9 100644 --- a/ui/src/routes/_layout/_authenticated/stake.tsx +++ b/ui/src/routes/_layout/_authenticated/stake.tsx @@ -593,7 +593,7 @@ function CityNodeCard({ - ) + ); } function StatBlock({ label, value }: { label: string; value: string | number }) { From 409eae05285abe0f78d54a27649739dccc47a3dc Mon Sep 17 00:00:00 2001 From: Elliot Braem Date: Sat, 15 Aug 2026 22:37:03 -0500 Subject: [PATCH 16/17] feat: remove deploy lock, keep infra export Remove FastKV-backed deploy lock feature: - Delete bos deploy lock acquire/release/inspect commands - Remove lock logic from publishToFastKv - Remove lockConflict from PublishResult and DeployResult schemas - Concurrent deploys now follow last-write-wins semantics (harmless for Railway redeploy) Keep bos infra export command: - Emits {env, services, account, gateway, project, generatedAt} JSON - buildOriginMap reads runtimeConfig.*.extendsRef (no raw JSON re-parse) Tests: 263 pass, failures are pre-existing (auth types, template property) --- .changeset/deploy-lock-and-infra-export.md | 12 +- packages/everything-dev/src/cli.ts | 75 +----- .../everything-dev/src/cli/deploy-lock.ts | 229 ------------------ packages/everything-dev/src/contract.meta.ts | 10 - packages/everything-dev/src/contract.ts | 52 +--- packages/everything-dev/src/plugin.ts | 67 ----- packages/everything-dev/src/publish.ts | 74 +----- .../tests/unit/deploy-lock.test.ts | 115 --------- 8 files changed, 13 insertions(+), 621 deletions(-) delete mode 100644 packages/everything-dev/src/cli/deploy-lock.ts delete mode 100644 packages/everything-dev/tests/unit/deploy-lock.test.ts diff --git a/.changeset/deploy-lock-and-infra-export.md b/.changeset/deploy-lock-and-infra-export.md index c7c363da..8a435f5b 100644 --- a/.changeset/deploy-lock-and-infra-export.md +++ b/.changeset/deploy-lock-and-infra-export.md @@ -1,12 +1,10 @@ --- -"everything-dev": minor +"everything-dev": patch --- -Add a per-account/gateway FastKV-backed deploy lock, a `bos infra export` command that emits the resolved CI infra plan, and read plugin `extendsRef` from the resolved runtime config (instead of re-parsing raw `bos.config.json`). +Remove deploy lock feature and add `bos infra export` command. -- `bos publish --deploy` and `bos deploy` now acquire `apps///lock/deploy.json` before publishing. Stale or concurrent dispatches fail fast with `status: "locked"` and a conflict payload listing the owner, nonce, and txHash. Use `--no-deploy-lock` to opt out (deploy lock is on by default). The lock is released in a `finally` block after publish confirmation. - - `bos publish` holds the lock for 10 minutes by default (the FastKV write window). `bos deploy` holds it for 25 minutes by default to cover publish + Railway redeploy. Override either with `BOS_DEPLOY_LOCK_TTL_MS` (positive integer, milliseconds). -- `bos deploy lock inspect` reports the active lock owner/nonce/expires/txHash; `bos deploy lock release` force-clears a stuck lock. -- `bos infra export [--target ci|local] [--network mainnet|testnet]` emits `{env, services, account, gateway, project, generatedAt}` JSON. The deploy workflow now consumes this to populate `$GITHUB_ENV` instead of repeating `API_DATABASE_URL`, `AUTH_DATABASE_URL`, and `CORS_ORIGIN` literals. Host port comes from `BOS_CI_HOST_PORT` env or `runtimeConfig.host.port`. +- Removed `bos deploy lock acquire/release/inspect` commands and FastKV-backed deploy lock logic. Concurrent deploys now follow last-write-wins semantics (harmless redundancy for Railway redeploy). +- `bos infra export [--target ci|local] [--network mainnet|testnet]` emits `{env, services, account, gateway, project, generatedAt}` JSON. The deploy workflow consumes this to populate `$GITHUB_ENV` instead of repeating `API_DATABASE_URL`, `AUTH_DATABASE_URL`, and `CORS_ORIGIN` literals. Host port comes from `BOS_CI_HOST_PORT` env or `runtimeConfig.host.port`. - `buildOriginMap` now derives plugin origins from `runtimeConfig.plugins[id].extendsRef` / `runtimeConfig.auth?.extendsRef` (already populated by `loadResolvedConfig`), removing the duplicate raw-JSON read and the parent-runtime fallback logic. -- Three new routes on the `bos` contract: `infraExport`, `deployLockInspect`, `deployLockRelease`. New tests cover the lock helpers, the CI plan builder, and the resolved-config origin lookup. +- New tests cover the CI plan builder and the resolved-config origin lookup. diff --git a/packages/everything-dev/src/cli.ts b/packages/everything-dev/src/cli.ts index ed9de076..2c3bf892 100644 --- a/packages/everything-dev/src/cli.ts +++ b/packages/everything-dev/src/cli.ts @@ -1019,24 +1019,12 @@ async function main() { return; } - if (result.status === "error" || result.status === "locked") { + if (result.status === "error") { console.log(); - console.log( - colors.error( - `${icons.err} ${result.status === "locked" ? "Deploy lock blocks publish" : "Publish failed"}`, - ), - ); + console.log(colors.error(`${icons.err} Publish failed`)); if (result.error) { console.log(` ${colors.dim("Error:")} ${result.error}`); } - if (result.lockConflict?.value) { - const owner = result.lockConflict.value.owner; - const nonce = result.lockConflict.value.nonce; - const expiresAt = result.lockConflict.value.expiresAt; - console.log(` ${colors.dim("Lock owner:")} ${owner}`); - console.log(` ${colors.dim("Lock nonce:")} ${nonce}`); - console.log(` ${colors.dim("Lock expires:")} ${new Date(expiresAt).toISOString()}`); - } if (result.deployResults && result.deployResults.length > 0) { const failures = result.deployResults.filter((r: any) => !r.success); if (failures.length > 0) { @@ -1089,24 +1077,12 @@ async function main() { return; } - if (deployResult.status === "error" || deployResult.status === "locked") { + if (deployResult.status === "error") { console.log(); - console.log( - colors.error( - `${icons.err} ${deployResult.status === "locked" ? "Deploy lock blocks publish" : "Deploy failed"}`, - ), - ); + console.log(colors.error(`${icons.err} Deploy failed`)); if (deployResult.error) { console.log(` ${colors.dim("Error:")} ${deployResult.error}`); } - if (deployResult.lockConflict?.value) { - const owner = deployResult.lockConflict.value.owner; - const nonce = deployResult.lockConflict.value.nonce; - const expiresAt = deployResult.lockConflict.value.expiresAt; - console.log(` ${colors.dim("Lock owner:")} ${owner}`); - console.log(` ${colors.dim("Lock nonce:")} ${nonce}`); - console.log(` ${colors.dim("Lock expires:")} ${new Date(expiresAt).toISOString()}`); - } if (deployResult.deployResults && deployResult.deployResults.length > 0) { const failures = deployResult.deployResults.filter((r: any) => !r.success); if (failures.length > 0) { @@ -1174,49 +1150,6 @@ async function main() { process.stdout.write(`${JSON.stringify(result, null, 2)}\n`); return; } - - if (descriptor.key === "deployLockInspect") { - const inspect = result as any; - console.log(); - console.log(` ${colors.dim("Account:")} ${inspect.account}`); - console.log(` ${colors.dim("Gateway:")} ${inspect.gateway}`); - console.log(` ${colors.dim("Network:")} ${inspect.network}`); - console.log(` ${colors.dim("Config:")} ${inspect.configRegistryUrl}`); - console.log(` ${colors.dim("Lock:")} ${inspect.lockRegistryUrl}`); - console.log( - ` ${colors.dim("Status:")} ${inspect.active ? colors.yellow("ACTIVE") : colors.green("free")}`, - ); - if (inspect.value) { - console.log(` ${colors.dim("Owner:")} ${inspect.value.owner}`); - console.log(` ${colors.dim("Nonce:")} ${inspect.value.nonce}`); - console.log( - ` ${colors.dim("Started:")} ${new Date(inspect.value.startedAt).toISOString()}`, - ); - console.log( - ` ${colors.dim("Expires:")} ${new Date(inspect.value.expiresAt).toISOString()}`, - ); - if (inspect.value.txHash) { - console.log(` ${colors.dim("Tx:")} ${inspect.value.txHash}`); - } - } - console.log(); - return; - } - - if (descriptor.key === "deployLockRelease") { - const release = result as { released: boolean; txHash?: string }; - console.log(); - if (release.released) { - console.log(colors.green(`${icons.ok} Deploy lock released`)); - if (release.txHash) { - console.log(` ${colors.dim("Transaction:")} ${release.txHash}`); - } - } else { - console.log(colors.dim(" No active deploy lock to release.")); - } - console.log(); - return; - } } catch (error) { console.error(`[CLI] ${error instanceof Error ? error.message : String(error)}`); process.exit(1); diff --git a/packages/everything-dev/src/cli/deploy-lock.ts b/packages/everything-dev/src/cli/deploy-lock.ts deleted file mode 100644 index 2b2c212e..00000000 --- a/packages/everything-dev/src/cli/deploy-lock.ts +++ /dev/null @@ -1,229 +0,0 @@ -import { randomBytes } from "node:crypto"; -import { Effect } from "effect"; -import { - buildRegistryConfigUrl, - fetchBosConfigFromFastKv, - getFastKvBaseUrlForNetwork, - getRegistryNamespaceForNetwork, - type NetworkId, - parseBosUrl, -} from "../fastkv"; -import { fetchJsonOrNull } from "../http-client"; -import { executeTransaction, type NearSigningMode } from "../near-cli"; - -const LOCK_SUFFIX = "lock/deploy.json"; -const DEFAULT_TTL_MS = 10 * 60 * 1000; -const RELEASE_SENTINEL_VALUE = "{}"; - -export interface DeployLockValue { - owner: string; - pid: number | string; - startedAt: number; - expiresAt: number; - network: NetworkId; - nonce: string; - txHash?: string; -} - -export interface DeployLockConflict { - active: boolean; - expiresAt: number; - value: DeployLockValue | null; - reason: "active" | "verify-mismatch"; -} - -export type AcquireDeployLockResult = - | { acquired: true; nonce: string; txHash?: string } - | { acquired: false; conflict: DeployLockConflict }; - -export interface LockContext { - account: string; - gateway: string; - network: NetworkId; -} - -export interface AcquireOptions extends LockContext { - privateKey?: string; - signingMode?: NearSigningMode; - ttlMs?: number; - owner?: string; -} - -function buildDeployLockKey(account: string, gateway: string): string { - return `apps/${account}/${gateway}/${LOCK_SUFFIX}`; -} - -function buildDeployLockUrl(account: string, gateway: string, network: NetworkId): string { - const baseUrl = getFastKvBaseUrlForNetwork(network); - const namespace = getRegistryNamespaceForNetwork(network); - const key = buildDeployLockKey(account, gateway); - return `${baseUrl}/v0/latest/${encodeURIComponent(namespace)}/${encodeURIComponent(account)}/${encodeURIComponent(key)}`; -} - -function parseDeployLockValue(raw: unknown): DeployLockValue | null { - if (raw == null) return null; - let parsed: unknown = raw; - if (typeof raw === "string") { - if (raw === RELEASE_SENTINEL_VALUE) return null; - try { - parsed = JSON.parse(raw); - } catch { - return null; - } - } - if (!parsed || typeof parsed !== "object") return null; - const obj = parsed as Record; - if (typeof obj.nonce !== "string" || typeof obj.expiresAt !== "number") return null; - return { - owner: typeof obj.owner === "string" ? obj.owner : "unknown", - pid: typeof obj.pid === "number" || typeof obj.pid === "string" ? obj.pid : -1, - startedAt: typeof obj.startedAt === "number" ? obj.startedAt : 0, - expiresAt: obj.expiresAt, - network: - typeof obj.network === "string" && (obj.network === "mainnet" || obj.network === "testnet") - ? obj.network - : "mainnet", - nonce: obj.nonce, - txHash: typeof obj.txHash === "string" ? obj.txHash : undefined, - }; -} - -export async function fetchDeployLock( - ctx: LockContext, -): Promise<{ active: boolean; value: DeployLockValue | null }> { - const url = buildDeployLockUrl(ctx.account, ctx.gateway, ctx.network); - const payload = await fetchJsonOrNull<{ entries?: Array<{ value: unknown } | null> }>(url, { - retries: 0, - }); - const raw = payload?.entries?.find(Boolean)?.value; - const value = parseDeployLockValue(raw); - if (!value) return { active: false, value: null }; - return { active: value.expiresAt > Date.now(), value }; -} - -function buildLockArgsBase64(key: string, value: string): string { - const payload = JSON.stringify({ [key]: value }); - return Buffer.from(payload).toString("base64"); -} - -async function writeDeployLockValue( - ctx: LockContext, - value: string, - signingMode: NearSigningMode, - options: { privateKey?: string; verbose?: boolean }, -): Promise { - const tx = await Effect.runPromise( - executeTransaction( - { - account: ctx.account, - contract: getRegistryNamespaceForNetwork(ctx.network), - method: "__fastdata_kv", - argsBase64: buildLockArgsBase64(buildDeployLockKey(ctx.account, ctx.gateway), value), - network: ctx.network, - privateKey: signingMode._tag === "privateKey" ? signingMode.privateKey : undefined, - gas: "100Tgas", - deposit: "0NEAR", - verbose: options.verbose ?? false, - }, - signingMode, - ), - ); - return tx.txHash; -} - -export async function acquireDeployLock(options: AcquireOptions): Promise { - const signingMode = options.signingMode ?? { - _tag: "privateKey" as const, - privateKey: options.privateKey ?? "", - }; - const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS; - const owner = options.owner ?? "bos-publish"; - const nonce = randomBytes(8).toString("hex"); - const now = Date.now(); - const desired: DeployLockValue = { - owner, - pid: process.pid, - startedAt: now, - expiresAt: now + ttlMs, - network: options.network, - nonce, - }; - - const initial = await fetchDeployLock(options); - if (initial.active && initial.value) { - return { - acquired: false, - conflict: { - active: true, - expiresAt: initial.value.expiresAt, - value: initial.value, - reason: "active" as const, - }, - }; - } - - const txHash = await writeDeployLockValue(options, JSON.stringify(desired), signingMode, { - privateKey: options.privateKey, - }); - - const verify = await fetchDeployLock(options); - if (!verify.active || verify.value?.nonce !== nonce) { - return { - acquired: false, - conflict: { - active: verify.active, - expiresAt: verify.value?.expiresAt ?? 0, - value: verify.value, - reason: "verify-mismatch" as const, - }, - }; - } - - return { acquired: true, nonce, txHash }; -} - -export async function releaseDeployLock( - ctx: LockContext, - options: { privateKey?: string; signingMode?: NearSigningMode; force?: boolean }, -): Promise<{ released: boolean; txHash?: string }> { - const signingMode = options.signingMode ?? { - _tag: "privateKey" as const, - privateKey: options.privateKey ?? "", - }; - - const current = await fetchDeployLock(ctx); - - if (!options.force && !current.active) { - return { released: false }; - } - - const txHash = await writeDeployLockValue(ctx, RELEASE_SENTINEL_VALUE, signingMode, { - privateKey: options.privateKey, - }); - return { released: true, txHash }; -} - -export interface InspectDeployLockResult { - account: string; - gateway: string; - network: NetworkId; - configRegistryUrl: string; - lockRegistryUrl: string; - active: boolean; - value: DeployLockValue | null; -} - -export async function inspectDeployLock(ctx: LockContext): Promise { - const state = await fetchDeployLock(ctx); - return { - account: ctx.account, - gateway: ctx.gateway, - network: ctx.network, - configRegistryUrl: buildRegistryConfigUrl(ctx.account, ctx.gateway), - lockRegistryUrl: buildDeployLockUrl(ctx.account, ctx.gateway, ctx.network), - active: state.active, - value: state.value, - }; -} - -export { buildDeployLockKey, buildDeployLockUrl, parseDeployLockValue }; diff --git a/packages/everything-dev/src/contract.meta.ts b/packages/everything-dev/src/contract.meta.ts index 2e403533..46e5aa65 100644 --- a/packages/everything-dev/src/contract.meta.ts +++ b/packages/everything-dev/src/contract.meta.ts @@ -239,14 +239,4 @@ export const cliCommandMeta = { configDir: { description: "Override config directory" }, }, }, - deployLockInspect: { - commandPath: ["deploy", "lock", "inspect"], - summary: "Inspect the FastKV deploy lock for the current account/gateway", - interactive: false, - }, - deployLockRelease: { - commandPath: ["deploy", "lock", "release"], - summary: "Force-release the FastKV deploy lock", - interactive: false, - }, } as const satisfies Record; diff --git a/packages/everything-dev/src/contract.ts b/packages/everything-dev/src/contract.ts index f6c54382..857040a4 100644 --- a/packages/everything-dev/src/contract.ts +++ b/packages/everything-dev/src/contract.ts @@ -145,35 +145,16 @@ export const PublishOptionsSchema = z.object({ network: z.enum(["mainnet", "testnet"]).optional(), privateKey: z.string().optional(), env: z.enum(["production", "staging"]).default("production"), - noDeployLock: z.boolean().default(false), }); export const PublishResultSchema = z.object({ - status: z.enum(["published", "error", "dry-run", "locked"]), + status: z.enum(["published", "error", "dry-run"]), registryUrl: z.string(), txHash: z.string().optional(), error: z.string().optional(), built: z.array(z.string()).optional(), skipped: z.array(z.string()).optional(), deployResults: z.array(WorkspaceDeployResultSchema).optional(), - lockConflict: z - .object({ - active: z.boolean(), - expiresAt: z.number(), - reason: z.enum(["active", "verify-mismatch"]), - value: z - .object({ - owner: z.string(), - pid: z.union([z.number(), z.string()]), - startedAt: z.number(), - expiresAt: z.number(), - network: z.enum(["mainnet", "testnet"]), - nonce: z.string(), - txHash: z.string().optional(), - }) - .nullable(), - }) - .optional(), }); export const DeployOptionsSchema = z.object({ @@ -185,11 +166,10 @@ export const DeployOptionsSchema = z.object({ network: z.enum(["mainnet", "testnet"]).optional(), privateKey: z.string().optional(), service: z.string().optional(), - noDeployLock: z.boolean().default(false), }); export const DeployResultSchema = z.object({ - status: z.enum(["deployed", "published", "error", "dry-run", "locked"]), + status: z.enum(["deployed", "published", "error", "dry-run"]), registryUrl: z.string(), txHash: z.string().optional(), built: z.array(z.string()).optional(), @@ -198,7 +178,6 @@ export const DeployResultSchema = z.object({ service: z.string().optional(), error: z.string().optional(), deployResults: z.array(WorkspaceDeployResultSchema).optional(), - lockConflict: PublishResultSchema.shape.lockConflict, }); function parseNearAmount(value: string): number { @@ -574,33 +553,6 @@ export const bosContract = oc.router({ .route({ method: "POST", path: "/infra/export" }) .input(InfraExportOptionsSchema) .output(InfraExportResultSchema), - deployLockInspect: oc.route({ method: "GET", path: "/deploy/lock" }).output( - z.object({ - account: z.string(), - gateway: z.string(), - network: z.enum(["mainnet", "testnet"]), - configRegistryUrl: z.string(), - lockRegistryUrl: z.string(), - active: z.boolean(), - value: z - .object({ - owner: z.string(), - pid: z.union([z.number(), z.string()]), - startedAt: z.number(), - expiresAt: z.number(), - network: z.enum(["mainnet", "testnet"]), - nonce: z.string(), - txHash: z.string().optional(), - }) - .nullable(), - }), - ), - deployLockRelease: oc.route({ method: "POST", path: "/deploy/lock/release" }).output( - z.object({ - released: z.boolean(), - txHash: z.string().optional(), - }), - ), }); export type DevOptions = z.infer; diff --git a/packages/everything-dev/src/plugin.ts b/packages/everything-dev/src/plugin.ts index d347c8c7..95a3363f 100644 --- a/packages/everything-dev/src/plugin.ts +++ b/packages/everything-dev/src/plugin.ts @@ -15,7 +15,6 @@ import { readJsonFile, selectWorkspaceTargets, } from "./build"; -import { inspectDeployLock, releaseDeployLock } from "./cli/deploy-lock"; import { buildCiInfraPlan, type CiInfraPlan, @@ -1042,8 +1041,6 @@ export default createPlugin({ packages: input.packages, network: input.network, privateKey: input.privateKey, - skipDeployLock: input.noDeployLock, - deployLockTtlMs: resolveDeployLockTtlFromEnv(), }); if (result.publishConfig) { @@ -1062,7 +1059,6 @@ export default createPlugin({ built: result.built, skipped: result.skipped, deployResults: result.deployResults, - lockConflict: result.lockConflict, }; }), @@ -1087,8 +1083,6 @@ export default createPlugin({ packages: input.packages, network: input.network, privateKey: input.privateKey, - skipDeployLock: input.noDeployLock, - deployLockTtlMs: resolveDeployLockTtlFromEnv({ extended: true }), }); if (result.status === "error") { @@ -1101,7 +1095,6 @@ export default createPlugin({ redeployed: false, error: result.error, deployResults: result.deployResults, - lockConflict: result.lockConflict, }; } @@ -1115,20 +1108,6 @@ export default createPlugin({ }; } - if (result.status === "locked") { - return { - status: "locked" as const, - registryUrl: result.registryUrl, - txHash: result.txHash, - built: result.built, - skipped: result.skipped, - redeployed: false, - error: result.error, - deployResults: result.deployResults, - lockConflict: result.lockConflict, - }; - } - if (result.publishConfig) { const refreshed = await loadResolvedConfig({ cwd: deps.configDir }); if (refreshed?.config) { @@ -2172,55 +2151,9 @@ export default createPlugin({ }; return result; }), - - deployLockInspect: builder.deployLockInspect.handler(async () => { - if (!deps.bosConfig) { - throw new Error("No bos.config.json found"); - } - const account = deps.bosConfig.account; - const gateway = deps.bosConfig.staging?.domain ?? deps.bosConfig.domain ?? account; - if (!gateway) { - throw new Error("bos.config.json must define domain to inspect deploy lock"); - } - const network = getNetworkIdForAccount(account); - return inspectDeployLock({ account, gateway, network }); - }), - - deployLockRelease: builder.deployLockRelease.handler(async () => { - if (!deps.bosConfig) { - throw new Error("No bos.config.json found"); - } - const account = deps.bosConfig.account; - const gateway = deps.bosConfig.staging?.domain ?? deps.bosConfig.domain ?? account; - if (!gateway) { - throw new Error("bos.config.json must define domain to release deploy lock"); - } - const network = getNetworkIdForAccount(account); - const privateKey = process.env.NEAR_PRIVATE_KEY || process.env.BOS_NEAR_PRIVATE_KEY; - const result = await releaseDeployLock( - { account, gateway, network }, - { privateKey, force: true }, - ); - return { released: result.released, txHash: result.txHash }; - }), }), }); -const PUBLISH_LOCK_TTL_MS = 10 * 60 * 1000; -const DEPLOY_LOCK_TTL_MS = 25 * 60 * 1000; - -export function resolveDeployLockTtlFromEnv(opts: { extended?: boolean } = {}): number { - const raw = process.env.BOS_DEPLOY_LOCK_TTL_MS; - if (raw !== undefined && raw !== "") { - const parsed = Number(raw); - if (Number.isFinite(parsed) && parsed > 0) return parsed; - } - return opts.extended ? DEPLOY_LOCK_TTL_MS : PUBLISH_LOCK_TTL_MS; -} - -export const PUBLISH_LOCK_TTL_DEFAULT_MS = PUBLISH_LOCK_TTL_MS; -export const DEPLOY_LOCK_TTL_DEFAULT_MS = DEPLOY_LOCK_TTL_MS; - function computeAllowedWorkspaces(overrides: string[], plugins?: string[]): string[] { const workspaces: string[] = []; for (const section of overrides) { diff --git a/packages/everything-dev/src/publish.ts b/packages/everything-dev/src/publish.ts index 545767fa..a455eb66 100644 --- a/packages/everything-dev/src/publish.ts +++ b/packages/everything-dev/src/publish.ts @@ -3,12 +3,6 @@ import { join } from "node:path"; import process from "node:process"; import { Effect } from "effect"; import { buildWorkspaceTargets, selectWorkspaceTargets } from "./build"; -import { - acquireDeployLock, - type DeployLockConflict, - fetchDeployLock, - releaseDeployLock, -} from "./cli/deploy-lock"; import { generateCodeArtifacts } from "./code-artifacts"; import { loadResolvedConfig } from "./config"; import type { WorkspaceDeployResult } from "./contract"; @@ -85,12 +79,10 @@ interface PublishToFastKvInput { packages: string; network?: "mainnet" | "testnet"; privateKey?: string; - skipDeployLock?: boolean; - deployLockTtlMs?: number; } interface PublishToFastKvResult { - status: "published" | "error" | "dry-run" | "locked"; + status: "published" | "error" | "dry-run"; registryUrl: string; txHash?: string; built?: string[]; @@ -98,7 +90,6 @@ interface PublishToFastKvResult { error?: string; publishConfig?: BosConfigInput; deployResults?: WorkspaceDeployResult[]; - lockConflict?: DeployLockConflict; } export async function publishToFastKv(input: PublishToFastKvInput): Promise { @@ -142,55 +133,7 @@ export async function publishToFastKv(input: PublishToFastKvInput): Promise { - const previous = process.env.BOS_DEPLOY_LOCK_TTL_MS; - - afterEach(() => { - if (previous === undefined) delete process.env.BOS_DEPLOY_LOCK_TTL_MS; - else process.env.BOS_DEPLOY_LOCK_TTL_MS = previous; - }); - - it("uses 10 min default for publish", () => { - delete process.env.BOS_DEPLOY_LOCK_TTL_MS; - expect(resolveDeployLockTtlFromEnv()).toBe(PUBLISH_LOCK_TTL_DEFAULT_MS); - expect(resolveDeployLockTtlFromEnv({ extended: false })).toBe(PUBLISH_LOCK_TTL_DEFAULT_MS); - }); - - it("uses 25 min default for deploy", () => { - delete process.env.BOS_DEPLOY_LOCK_TTL_MS; - expect(resolveDeployLockTtlFromEnv({ extended: true })).toBe(DEPLOY_LOCK_TTL_DEFAULT_MS); - }); - - it("honors BOS_DEPLOY_LOCK_TTL_MS override for both modes", () => { - process.env.BOS_DEPLOY_LOCK_TTL_MS = "1800000"; - expect(resolveDeployLockTtlFromEnv()).toBe(1_800_000); - expect(resolveDeployLockTtlFromEnv({ extended: true })).toBe(1_800_000); - }); - - it("falls back to default when env value is invalid", () => { - process.env.BOS_DEPLOY_LOCK_TTL_MS = "not-a-number"; - expect(resolveDeployLockTtlFromEnv()).toBe(PUBLISH_LOCK_TTL_DEFAULT_MS); - expect(resolveDeployLockTtlFromEnv({ extended: true })).toBe(DEPLOY_LOCK_TTL_DEFAULT_MS); - }); -}); - -describe("deploy-lock helpers", () => { - describe("parseDeployLockValue", () => { - it("returns null for empty/sentinel values", () => { - expect(parseDeployLockValue(null)).toBeNull(); - expect(parseDeployLockValue(undefined)).toBeNull(); - expect(parseDeployLockValue("{}")).toBeNull(); - }); - - it("parses stringified JSON values", () => { - const stored = JSON.stringify({ - owner: "alice", - pid: 4242, - startedAt: 1, - expiresAt: Date.now() + 60_000, - network: "mainnet", - nonce: "abc123", - }); - const result = parseDeployLockValue(stored); - expect(result).not.toBeNull(); - expect(result?.owner).toBe("alice"); - expect(result?.pid).toBe(4242); - expect(result?.nonce).toBe("abc123"); - expect(result?.network).toBe("mainnet"); - }); - - it("parses object values", () => { - const result = parseDeployLockValue({ - owner: "deployer", - pid: 99, - startedAt: 1000, - expiresAt: 2000, - network: "testnet", - nonce: "deadbeef", - }); - expect(result?.owner).toBe("deployer"); - expect(result?.network).toBe("testnet"); - }); - - it("returns null when nonce or expiresAt is missing", () => { - expect(parseDeployLockValue({ owner: "x" })).toBeNull(); - expect(parseDeployLockValue({ nonce: "abc" })).toBeNull(); - expect(parseDeployLockValue({ expiresAt: 1000 })).toBeNull(); - }); - }); - - describe("buildDeployLockKey", () => { - it("returns expected apps/.../lock/deploy.json path", () => { - expect(buildDeployLockKey("v1.foo.near", "citynode.app")).toBe( - "apps/v1.foo.near/citynode.app/lock/deploy.json", - ); - }); - }); - - describe("buildDeployLockUrl", () => { - it("builds the FastKV GET URL for mainnet", () => { - const url = buildDeployLockUrl("v1.foo.near", "citynode.app", "mainnet"); - expect(url).toContain("kv.main.fastnear.com"); - expect(url).toContain("v1.foo.near"); - expect(encodeURIComponent("apps/v1.foo.near/citynode.app/lock/deploy.json")).toBeTruthy(); - expect( - decodeURIComponent(encodeURIComponent("apps/v1.foo.near/citynode.app/lock/deploy.json")), - ).toBe("apps/v1.foo.near/citynode.app/lock/deploy.json"); - expect(url).toContain("deploy.json"); - }); - - it("builds the FastKV GET URL for testnet", () => { - const url = buildDeployLockUrl("v1.foo.near", "citynode.app", "testnet"); - expect(url).toContain("kv.test.fastnear.com"); - }); - }); -}); From bf29af5ad6b06ad17d480f39313c8295a80cfa11 Mon Sep 17 00:00:00 2001 From: Elliot Braem Date: Tue, 18 Aug 2026 14:11:01 -0500 Subject: [PATCH 17/17] wip --- ...etter-near-auth-1-10-0-ephemeral-marker.md | 5 + .changeset/siwn-ephemeral-relayer.md | 8 + AGENTS.md | 15 + bos.config.json | 13 +- bun.lock | 604 ++++++++++-------- package.json | 2 +- 6 files changed, 365 insertions(+), 282 deletions(-) create mode 100644 .changeset/better-near-auth-1-10-0-ephemeral-marker.md create mode 100644 .changeset/siwn-ephemeral-relayer.md diff --git a/.changeset/better-near-auth-1-10-0-ephemeral-marker.md b/.changeset/better-near-auth-1-10-0-ephemeral-marker.md new file mode 100644 index 00000000..cb31e969 --- /dev/null +++ b/.changeset/better-near-auth-1-10-0-ephemeral-marker.md @@ -0,0 +1,5 @@ +--- +"everything-dev": patch +--- + +Bump `better-near-auth` from 1.9.0 to 1.10.0 across the catalog and pin it exactly. The new release adds an optional `ephemeral?: true` marker on `RelayerEphemeralConfig`; mark the SIWN relayer block in `bos.config.json → app.auth.variables.siwn` with that explicit flag while keeping the existing `whitelistedContracts`, `maxGasPerTransaction`, and `maxDepositPerTransaction` constraints. Runtime behavior is unchanged — the marker is documentation only and `getRelayerInfo().mode` still reports `"ephemeral"`. diff --git a/.changeset/siwn-ephemeral-relayer.md b/.changeset/siwn-ephemeral-relayer.md new file mode 100644 index 00000000..afd14429 --- /dev/null +++ b/.changeset/siwn-ephemeral-relayer.md @@ -0,0 +1,8 @@ +--- +--- + +Confirm the SIWN auth relayer is in `RelayerEphemeralConfig` ("Ephemeral with settings") mode: a rich-object `relayer` block in `bos.config.json → app.auth.variables.siwn` with `whitelistedContracts`, `maxGasPerTransaction`, and `maxDepositPerTransaction` and no `accountId` / `privateKey`. better-near-auth 1.9.0's `initRelayer` resolves this to an auto-generated ED25519 keypair on first startup, encrypted with `BETTER_AUTH_SECRET` (HKDF-SHA256 → AES-256-GCM) and persisted in the `relayerKey` table. + +The vestigial `NEAR_RELAYER_PRIVATE_KEY` line is removed from `.env.example` in favor of an inline comment pointing operators at `/admin/relayer` (which surfaces a "needs funding" prompt using `getRelayerInfo().enabled === false` once the auto-generated implicit account has zero balance). Operators funding the implicit account via `authClient.near.getNearClient().transfer()` enables relay without ever leaving the existing ephemeral-mode config. + +AGENTS.md gains a "SIWN Auth Relayer" subsection under "Common Patterns" documenting the operational rules (funding flow, parent-key requirement for sub-account creation, why the implicit relayer account can't own sub-accounts, and the path back to `RelayerExplicitConfig` if a named-account relayer is needed). diff --git a/AGENTS.md b/AGENTS.md index ddca75f8..8a174c20 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -351,6 +351,21 @@ const { runtimeConfig } = Route.useLoaderData(); const appName = getActiveRuntime(runtimeConfig)?.title ?? getAccount(runtimeConfig); ``` +### SIWN Auth Relayer (gasless NEP-366 relay) + +The auth plugin's `siwn({ relayer: ... })` block in `bos.config.json → app.auth.variables.siwn` is **ephemeral mode** — the rich-object shape with `whitelistedContracts`, `maxGasPerTransaction`, and `maxDepositPerTransaction` but no `accountId` / `privateKey`. From the better-near-auth skill: that's `RelayerEphemeralConfig` ("Ephemeral with settings"). + +**Operational rules:** + +- On first startup the server generates an ED25519 keypair per network, derives an implicit hex account from the public key, and encrypts the private key with `BETTER_AUTH_SECRET` (HKDF-SHA256 → AES-256-GCM) into the `relayerKey` table. Same keypair recovers on every restart. +- After first startup the server logs the implicit account id. **Fund that account with NEAR** to enable relay — otherwise every relay attempt fails with insufficient balance from the RPC. +- Funding workflow: admins hit `getRelayerInfo`; the `/admin/relayer` page surfaces a "needs funding" banner on `/admin` when `enabled === false` and `accountId` is set, then the admin's connected wallet transfers NEAR to the implicit account via `authClient.near.getNearClient().transfer()`. +- The implicit relayer account is *not* a `.near` named account, so it cannot own sub-accounts. `siwn.subAccount.parentAccount` must be a named account (this project uses `v1.citynode.near` / `v1.citynode.testnet`), and the parent key is supplied via `NEAR_SUB_ACCOUNT_PARENT_KEY_MAINNET` / `NEAR_SUB_ACCOUNT_PARENT_KEY_TESTNET` secrets. Without the parent key the sub-account endpoint explains why in the error message and returns a `not-configured` reason. +- `NEAR_RELAYER_PRIVATE_KEY` is vestigial in ephemeral mode and is omitted from `.env.example`. Only reintroduce (plus explicit `relayer: { accountId, privateKey }`) when moving to `RelayerExplicitConfig`. +- The mode is observable at runtime: `getRelayerInfo()` returns `{ accountId, mode: "ephemeral", publicKey, balance, enabled }`. + +To switch to `RelayerExplicitConfig`, replace the rich-object shape with `relayer: { accountId: "relayer..near", privateKey: process.env.RELAYER_PRIVATE_KEY, whitelistedContracts: [...], maxGasPerTransaction: "...", maxDepositPerTransaction: "0" }` and re-add the env var. The ephemeral key in the `relayerKey` table is ignored once an explicit key is provided. + ## Security ### Shared Singleton Trust Model diff --git a/bos.config.json b/bos.config.json index 07a09d83..92f467af 100644 --- a/bos.config.json +++ b/bos.config.json @@ -12,7 +12,7 @@ "app": { "host": { "development": "local:host", - "production": "https://elliot-braem-7380-host-everything-dev-nearbuilder-58d75d144-ze.zephyrcloud.app", + "production": "https://elliot-braem-7404-host-everything-dev-nearbuilder-171db3e27-ze.zephyrcloud.app", "secrets": [ "CORS_ORIGIN", "CSP_STRICT" @@ -21,14 +21,14 @@ }, "ui": { "development": "local:ui", - "production": "https://elliot-braem-7376-ui-everything-dev-nearbuilders-640fbb40f-ze.zephyrcloud.app", - "ssr": "https://elliot-braem-7379-ui-everything-dev-nearbuilders-9e2e0e650-ze.zephyrcloud.app", + "production": "https://elliot-braem-7400-ui-everything-dev-nearbuilders-1b6dbfe53-ze.zephyrcloud.app", + "ssr": "https://elliot-braem-7398-ui-everything-dev-nearbuilders-e8019531b-ze.zephyrcloud.app", "integrity": "sha384-UQX/yKubluHrdSEKhNtBs39QAbtXHs/iJ0aqkyPIW/ey/k26xRVvCEKsL2iK+cmb", - "ssrIntegrity": "sha384-V8ObgdJVdJ/jwU5xZbwX0R0+APdf2pli4B+ApUAfkB9vtzpxGVrFNDC3qS0FsL+i" + "ssrIntegrity": "sha384-99Rxrhwagd4U98ryETO5m987SDaqG2DBzRA76gh5bJHqP1x1rTJlapRhC6zASS6j" }, "api": { "development": "local:api", - "production": "https://elliot-braem-7377-api-everything-dev-nearbuilders-d0f1039d8-ze.zephyrcloud.app", + "production": "https://elliot-braem-7402-api-everything-dev-nearbuilders-79d1a502b-ze.zephyrcloud.app", "secrets": [ "API_DATABASE_URL" ], @@ -50,6 +50,7 @@ "testnet": "v1.citynode.testnet" }, "relayer": { + "ephemeral": true, "whitelistedContracts": [ "v1.citynode.near" ], @@ -75,7 +76,7 @@ "plugins": { "apps": { "development": "local:plugins/apps", - "production": "https://elliot-braem-7378-everything-dev-apps-plugin-ever-bd36fe158-ze.zephyrcloud.app", + "production": "https://elliot-braem-7401-everything-dev-apps-plugin-ever-e71038daa-ze.zephyrcloud.app", "variables": { "registryNamespace": "v1.citynode.near" }, diff --git a/bun.lock b/bun.lock index 58d69aae..46d876c9 100644 --- a/bun.lock +++ b/bun.lock @@ -391,7 +391,7 @@ "@types/react": "^19.2.7", "@types/react-dom": "^19.2.3", "better-auth": "1.6.25", - "better-near-auth": "1.8.3", + "better-near-auth": "1.10.0", "drizzle-kit": "^0.31.8", "drizzle-orm": "^0.45.1", "effect": "3.21.2", @@ -420,7 +420,7 @@ "@babel/core": ["@babel/core@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-module-transforms": "^7.29.7", "@babel/helpers": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA=="], - "@babel/generator": ["@babel/generator@7.29.7", "", { "dependencies": { "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ=="], + "@babel/generator": ["@babel/generator@7.29.8", "", { "dependencies": { "@babel/parser": "^7.29.8", "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg=="], "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.29.7", "", { "dependencies": { "@babel/compat-data": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g=="], @@ -440,7 +440,7 @@ "@babel/helpers": ["@babel/helpers@7.29.7", "", { "dependencies": { "@babel/template": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg=="], - "@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="], + "@babel/parser": ["@babel/parser@7.29.8", "", { "dependencies": { "@babel/types": "^7.29.8" }, "bin": "./bin/babel-parser.js" }, "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA=="], "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A=="], @@ -450,9 +450,9 @@ "@babel/template": ["@babel/template@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg=="], - "@babel/traverse": ["@babel/traverse@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/types": "^7.29.7", "debug": "^4.3.1" } }, "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw=="], + "@babel/traverse": ["@babel/traverse@7.29.8", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.8", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.8", "@babel/template": "^7.29.7", "@babel/types": "^7.29.8", "debug": "^4.3.1" } }, "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg=="], - "@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], + "@babel/types": ["@babel/types@7.29.8", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg=="], "@better-auth/api-key": ["@better-auth/api-key@1.6.25", "", { "dependencies": { "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/core": "^1.6.25", "@better-auth/utils": "0.4.2", "better-auth": "^1.6.25", "better-call": "1.3.7" } }, "sha512-A6f3YLN8Ve+D4R7f8jrSKh8Mpu9+bmc8PIb9BFgGgspcedM1PjpSEI5JKAWPUhiGgMeJdCGeCpRP+27jQmW9/w=="], @@ -558,67 +558,67 @@ "@electric-sql/pglite": ["@electric-sql/pglite@0.4.6", "", {}, "sha512-qmlmfN8UyKCee35qkV0r/MBp+Znl8FjBz7OpoglNvww3GJpw0/DLP0o1ZymvLNmcD5DTLOQdzKPtF8Hd3mdl1w=="], - "@emnapi/core": ["@emnapi/core@1.11.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA=="], + "@emnapi/core": ["@emnapi/core@1.11.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.3", "tslib": "^2.4.0" } }, "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg=="], "@emnapi/runtime": ["@emnapi/runtime@1.11.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="], - "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="], + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g=="], "@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="], "@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="], - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ=="], - "@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="], + "@esbuild/android-arm": ["@esbuild/android-arm@0.28.2", "", { "os": "android", "cpu": "arm" }, "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg=="], - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.1", "", { "os": "android", "cpu": "arm64" }, "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg=="], + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.2", "", { "os": "android", "cpu": "arm64" }, "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A=="], - "@esbuild/android-x64": ["@esbuild/android-x64@0.28.1", "", { "os": "android", "cpu": "x64" }, "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng=="], + "@esbuild/android-x64": ["@esbuild/android-x64@0.28.2", "", { "os": "android", "cpu": "x64" }, "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q=="], - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q=="], + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw=="], - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ=="], + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw=="], - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw=="], + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw=="], - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ=="], + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg=="], - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.1", "", { "os": "linux", "cpu": "arm" }, "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ=="], + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.2", "", { "os": "linux", "cpu": "arm" }, "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w=="], - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g=="], + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug=="], - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w=="], + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ=="], - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg=="], + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ=="], - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ=="], + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA=="], - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ=="], + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ=="], - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ=="], + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA=="], - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag=="], + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg=="], - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.1", "", { "os": "linux", "cpu": "x64" }, "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA=="], + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.2", "", { "os": "linux", "cpu": "x64" }, "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ=="], - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw=="], + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw=="], - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.1", "", { "os": "none", "cpu": "x64" }, "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg=="], + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.2", "", { "os": "none", "cpu": "x64" }, "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw=="], - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q=="], + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ=="], - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw=="], + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw=="], - "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg=="], + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q=="], - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ=="], + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g=="], - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA=="], + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ=="], - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="], + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA=="], - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="], + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g=="], "@every-plugin/template": ["@every-plugin/template@workspace:plugins/_template"], @@ -634,7 +634,7 @@ "@hexagon/base64": ["@hexagon/base64@1.1.28", "", {}, "sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw=="], - "@hono/node-server": ["@hono/node-server@2.0.12", "", { "peerDependencies": { "hono": "^4" } }, "sha512-eWpQYr67tqJLeaSUl0Q+TquuYfUdTibpOJlUMV2FfUP7+KqCC5TufnwnlXL6mobZBJbGAYRd7ZvEBDCbLInjhg=="], + "@hono/node-server": ["@hono/node-server@2.1.1", "", { "peerDependencies": { "hono": "^4" } }, "sha512-ELuehkj5VCBdgEw9zs+ivkKwyzzUCSQuE96YmiPvn1ECBoZCczbFXJLeEGMTYjphP6gydh4pHMqEYPVMYUVgQg=="], "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="], @@ -708,21 +708,21 @@ "@jsonjoy.com/codegen": ["@jsonjoy.com/codegen@1.0.0", "", { "peerDependencies": { "tslib": "2" } }, "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g=="], - "@jsonjoy.com/fs-core": ["@jsonjoy.com/fs-core@4.64.0", "", { "dependencies": { "@jsonjoy.com/fs-node-builtins": "4.64.0", "@jsonjoy.com/fs-node-utils": "4.64.0", "thingies": "^2.5.0" }, "peerDependencies": { "tslib": "2" } }, "sha512-zs2TAq7Six5jgMuoMNjpspAvOP3mhtgq/k1UyQodEzCtQi/N83y2/y+zcvnZSGp/Rxq96DBN+bValOBQAyn/ew=="], + "@jsonjoy.com/fs-core": ["@jsonjoy.com/fs-core@4.68.1", "", { "dependencies": { "@jsonjoy.com/fs-node-builtins": "4.68.1", "@jsonjoy.com/fs-node-utils": "4.68.1", "thingies": "^2.5.0" }, "peerDependencies": { "tslib": "2" } }, "sha512-V5oZ4Gt9WJKyQef0n9cAd0N9qjSkIBm3E4MYsgNIWBk5aINCDPKxMPo1i29rBxqiT4Ixf1epklqV9VJMKIxwlw=="], - "@jsonjoy.com/fs-fsa": ["@jsonjoy.com/fs-fsa@4.64.0", "", { "dependencies": { "@jsonjoy.com/fs-core": "4.64.0", "@jsonjoy.com/fs-node-builtins": "4.64.0", "@jsonjoy.com/fs-node-utils": "4.64.0", "thingies": "^2.5.0" }, "peerDependencies": { "tslib": "2" } }, "sha512-nMWOVbkLFyEgmXZih3wyvxA9XpgyyqyfrINMHvEFqhi7uqfRl7c9ERJt6yX7vgMPrB9Uo+OJO+Spa0cFzPD01w=="], + "@jsonjoy.com/fs-fsa": ["@jsonjoy.com/fs-fsa@4.68.1", "", { "dependencies": { "@jsonjoy.com/fs-core": "4.68.1", "@jsonjoy.com/fs-node-builtins": "4.68.1", "@jsonjoy.com/fs-node-utils": "4.68.1", "thingies": "^2.5.0" }, "peerDependencies": { "tslib": "2" } }, "sha512-HCG72UioncuO7Gw09XNVG+S85e3cq2hrUC/mexBrsWsa3mI7eePkkqWie3uVYbtsb64OR9YGQs5SqaufDRYBcg=="], - "@jsonjoy.com/fs-node": ["@jsonjoy.com/fs-node@4.64.0", "", { "dependencies": { "@jsonjoy.com/fs-core": "4.64.0", "@jsonjoy.com/fs-node-builtins": "4.64.0", "@jsonjoy.com/fs-node-utils": "4.64.0", "@jsonjoy.com/fs-print": "4.64.0", "@jsonjoy.com/fs-snapshot": "4.64.0", "glob-to-regex.js": "^1.0.0", "thingies": "^2.5.0" }, "peerDependencies": { "tslib": "2" } }, "sha512-dO+NNkODbUli4uV42bcNrrLvq5rE7SNpdZ5TNd0dtbLsAaNK3MDiIC9lUi+brboGoIjW6vd2fB1qao60nrk5xA=="], + "@jsonjoy.com/fs-node": ["@jsonjoy.com/fs-node@4.68.1", "", { "dependencies": { "@jsonjoy.com/fs-core": "4.68.1", "@jsonjoy.com/fs-node-builtins": "4.68.1", "@jsonjoy.com/fs-node-utils": "4.68.1", "@jsonjoy.com/fs-print": "4.68.1", "@jsonjoy.com/fs-snapshot": "4.68.1", "glob-to-regex.js": "^1.0.0", "thingies": "^2.5.0" }, "peerDependencies": { "tslib": "2" } }, "sha512-R5D9mWtqdURzcOWj1vdXr3APCwX0xchtFT+kmW7fXLNDifWdDrnh26jSID8pdnUfFBxTyfHtFtTL/NWKzIH7kQ=="], - "@jsonjoy.com/fs-node-builtins": ["@jsonjoy.com/fs-node-builtins@4.64.0", "", { "peerDependencies": { "tslib": "2" } }, "sha512-/o7WRFhUWaM/fOrslwLZGnzn4RmRILykn+lAL+mNObqqRNw+CQSiij6hpCeZ+C7buhdoVo7go/OYqzaSUfDYmA=="], + "@jsonjoy.com/fs-node-builtins": ["@jsonjoy.com/fs-node-builtins@4.68.1", "", { "peerDependencies": { "tslib": "2" } }, "sha512-HK1BTksysokNZxNspqDH0yPaqN9YgR/AYIlYiIaU2Ys4BOk5CdybI7r6BgiZuiiPiV8n4sK/kZdice7Znpy2Kw=="], - "@jsonjoy.com/fs-node-to-fsa": ["@jsonjoy.com/fs-node-to-fsa@4.64.0", "", { "dependencies": { "@jsonjoy.com/fs-fsa": "4.64.0", "@jsonjoy.com/fs-node-builtins": "4.64.0", "@jsonjoy.com/fs-node-utils": "4.64.0" }, "peerDependencies": { "tslib": "2" } }, "sha512-WDD9WVs0hb7UAEKTgZW2f66WDrbj7gIIWwpP3spbLyXa0rghtUaFTB8L4gdR3ZCWwiKIsj38/CNijpVmpnuPUw=="], + "@jsonjoy.com/fs-node-to-fsa": ["@jsonjoy.com/fs-node-to-fsa@4.68.1", "", { "dependencies": { "@jsonjoy.com/fs-fsa": "4.68.1", "@jsonjoy.com/fs-node-builtins": "4.68.1", "@jsonjoy.com/fs-node-utils": "4.68.1" }, "peerDependencies": { "tslib": "2" } }, "sha512-lpKmU4X9e/oh8GIuAI7EXaS5QiLNM3KD15CkdhfS6PYmrGvoJqKQcyEfnLgnnaGslh/PFUMYSIZBCf2ejJGw8g=="], - "@jsonjoy.com/fs-node-utils": ["@jsonjoy.com/fs-node-utils@4.64.0", "", { "dependencies": { "@jsonjoy.com/fs-node-builtins": "4.64.0", "glob-to-regex.js": "^1.0.1" }, "peerDependencies": { "tslib": "2" } }, "sha512-k5Indsx9hWW9xSF7Y6oSKKwtCUNhzZxadub3owhIlitc+iMRVlPPdX2duTKQWBL3qNWpXya8jykgaaWpheeS4w=="], + "@jsonjoy.com/fs-node-utils": ["@jsonjoy.com/fs-node-utils@4.68.1", "", { "dependencies": { "@jsonjoy.com/fs-node-builtins": "4.68.1", "glob-to-regex.js": "^1.0.1" }, "peerDependencies": { "tslib": "2" } }, "sha512-/GxfW1DWm9SCdkfbvqevLO/P5duobQfmKkHXxdMIDbcZMQeAgooAstIfZhkXpATzq9QbCQsnoWFM/dGHdZfndw=="], - "@jsonjoy.com/fs-print": ["@jsonjoy.com/fs-print@4.64.0", "", { "dependencies": { "@jsonjoy.com/fs-node-utils": "4.64.0", "tree-dump": "^1.1.0" }, "peerDependencies": { "tslib": "2" } }, "sha512-PHZFccchvkhWrwPWHjmVAhbC3vSHCtyZvlZfJJ3ho2bnzl450hXri6/8e6pbkWdH+SkmLXNml0sV8e5HDAfxKw=="], + "@jsonjoy.com/fs-print": ["@jsonjoy.com/fs-print@4.68.1", "", { "dependencies": { "@jsonjoy.com/fs-node-utils": "4.68.1", "tree-dump": "^1.1.0" }, "peerDependencies": { "tslib": "2" } }, "sha512-oGeZOGPYKK9v1CgeVeEDsLomH1lCnslSpqUN5GmPzrmAVGQlsmsdcXNA2O4lV8Y4xkuSuynx2ITBkUHJVaTbow=="], - "@jsonjoy.com/fs-snapshot": ["@jsonjoy.com/fs-snapshot@4.64.0", "", { "dependencies": { "@jsonjoy.com/buffers": "^17.65.0", "@jsonjoy.com/fs-node-utils": "4.64.0", "@jsonjoy.com/json-pack": "^17.65.0", "@jsonjoy.com/util": "^17.65.0" }, "peerDependencies": { "tslib": "2" } }, "sha512-oM7UDeL83q6NBzzsfKAsYKXKVXlykKFqqOLh4xZZKAzzROTlInkPbc6LTDGThEOnPiFiUzA7tYziHG9xavd76Q=="], + "@jsonjoy.com/fs-snapshot": ["@jsonjoy.com/fs-snapshot@4.68.1", "", { "dependencies": { "@jsonjoy.com/buffers": "^17.65.0", "@jsonjoy.com/fs-node-utils": "4.68.1", "@jsonjoy.com/json-pack": "^17.65.0", "@jsonjoy.com/util": "^17.65.0" }, "peerDependencies": { "tslib": "2" } }, "sha512-XZfP0FDZN32bbc4t2bZN2qRrYHg5AktJnzk22HRoKGK4BprrbNRH2k5ceSNS/kupKYcofCs+O841+xaAbjnxwQ=="], "@jsonjoy.com/json-pack": ["@jsonjoy.com/json-pack@1.21.0", "", { "dependencies": { "@jsonjoy.com/base64": "^1.1.2", "@jsonjoy.com/buffers": "^1.2.0", "@jsonjoy.com/codegen": "^1.0.0", "@jsonjoy.com/json-pointer": "^1.0.2", "@jsonjoy.com/util": "^1.9.0", "hyperdyperid": "^1.2.0", "thingies": "^2.5.0", "tree-dump": "^1.1.0" }, "peerDependencies": { "tslib": "2" } }, "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg=="], @@ -770,39 +770,39 @@ "@module-federation/automatic-vendor-federation": ["@module-federation/automatic-vendor-federation@1.2.1", "", { "dependencies": { "find-package-json": "^1.2.0" }, "peerDependencies": { "webpack": "^5.0.0-beta.16" } }, "sha512-73wxkXM7pbRZ6GGM90JP5IPTPvY3fvrhQyTVdMCUx85cQRWqnbzbibcsz3pkOMOeXyYAO4tXXsG13yNaEEGhJA=="], - "@module-federation/bridge-react-webpack-plugin": ["@module-federation/bridge-react-webpack-plugin@2.8.0", "", { "dependencies": { "@module-federation/sdk": "2.8.0" } }, "sha512-7AaaiE4YOXFb+st6xlVDK65aNKZYR9S9ykH0q2mkHAHTgquXciAjypS8JuhmKn7Lob/2rkplMOc4YsGxG3Ng9Q=="], + "@module-federation/bridge-react-webpack-plugin": ["@module-federation/bridge-react-webpack-plugin@2.8.2", "", { "dependencies": { "@module-federation/sdk": "2.8.2" } }, "sha512-cEhnpCsWHqUndQC6WKtwat5BGz+IU0UdCjzyXrZtx1UqHX1jRB0+DZxB4DKYKtxTko8fsV0FUCJ/0FjKZY6z8g=="], - "@module-federation/cli": ["@module-federation/cli@2.8.0", "", { "dependencies": { "@module-federation/dts-plugin": "2.8.0", "@module-federation/sdk": "2.8.0", "commander": "11.1.0", "jiti": "2.4.2" }, "bin": { "mf": "bin/mf.js" } }, "sha512-yTxdWkCJPPo+IGASz+NqdW13cw3DhjSBEn9r85aYn1ahckDwB1WcZe0OjDWMqCcR6yi0BMLedk0SWrUeUj0fWw=="], + "@module-federation/cli": ["@module-federation/cli@2.8.2", "", { "dependencies": { "@module-federation/dts-plugin": "2.8.2", "@module-federation/sdk": "2.8.2", "commander": "11.1.0", "jiti": "2.4.2" }, "bin": { "mf": "bin/mf.js" } }, "sha512-SrRe2UOzjYux/9Zf7AYymGGsYpJgrIHUPF+T9JQ+rZ7MC9Uiy5rNUtSYdxNrdFpVaRZYXMrI4b82iUy66CU6UA=="], - "@module-federation/dts-plugin": ["@module-federation/dts-plugin@2.8.0", "", { "dependencies": { "@module-federation/error-codes": "2.8.0", "@module-federation/managers": "2.8.0", "@module-federation/sdk": "2.8.0", "@module-federation/third-party-dts-extractor": "2.8.0", "adm-zip": "0.5.10", "isomorphic-ws": "5.0.0", "undici": "7.28.0", "ws": "8.21.0" }, "peerDependencies": { "typescript": "^4.9.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", "vue-tsc": ">=1.0.24" }, "optionalPeers": ["vue-tsc"] }, "sha512-defjq4jOWMEfeejezPWLP5sc8kw0O6FqTT7/E5rbZPEVyjB1A0U3ynhW6GDE5/6hk9/TzdbWS+fBNi4MqUOY6Q=="], + "@module-federation/dts-plugin": ["@module-federation/dts-plugin@2.8.2", "", { "dependencies": { "@module-federation/error-codes": "2.8.2", "@module-federation/managers": "2.8.2", "@module-federation/sdk": "2.8.2", "@module-federation/third-party-dts-extractor": "2.8.2", "adm-zip": "0.6.0", "isomorphic-ws": "5.0.0", "undici": "7.29.0", "ws": "8.21.0" }, "peerDependencies": { "typescript": "^4.9.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", "vue-tsc": ">=1.0.24" }, "optionalPeers": ["vue-tsc"] }, "sha512-pwZFW8b2LZTymMMC+o2M9xMXDIQKAHGtCRqj/IkOp0jHRYrKjK9cma9xoUNst8/R3sEn878/i9wgv/43BnCEyg=="], - "@module-federation/enhanced": ["@module-federation/enhanced@2.8.0", "", { "dependencies": { "@module-federation/bridge-react-webpack-plugin": "2.8.0", "@module-federation/cli": "2.8.0", "@module-federation/dts-plugin": "2.8.0", "@module-federation/error-codes": "2.8.0", "@module-federation/inject-external-runtime-core-plugin": "2.8.0", "@module-federation/managers": "2.8.0", "@module-federation/manifest": "2.8.0", "@module-federation/rspack": "2.8.0", "@module-federation/runtime-tools": "2.8.0", "@module-federation/sdk": "2.8.0", "@module-federation/webpack-bundler-runtime": "2.8.0", "schema-utils": "4.3.0", "tapable": "2.3.0" }, "peerDependencies": { "typescript": "^4.9.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", "vue-tsc": ">=1.0.24", "webpack": "^5.0.0" }, "optionalPeers": ["typescript", "vue-tsc", "webpack"], "bin": { "mf": "bin/mf.js" } }, "sha512-h8vkLdhK7tlcSPmyYNGfGyt0pSzfDB0tYVYdyUt2tXwQRfaJAi3bsIpujMXElw3MXtOfSHESa6M/hPKrtWTHBw=="], + "@module-federation/enhanced": ["@module-federation/enhanced@2.8.2", "", { "dependencies": { "@module-federation/bridge-react-webpack-plugin": "2.8.2", "@module-federation/cli": "2.8.2", "@module-federation/dts-plugin": "2.8.2", "@module-federation/error-codes": "2.8.2", "@module-federation/inject-external-runtime-core-plugin": "2.8.2", "@module-federation/managers": "2.8.2", "@module-federation/manifest": "2.8.2", "@module-federation/rspack": "2.8.2", "@module-federation/runtime-tools": "2.8.2", "@module-federation/sdk": "2.8.2", "@module-federation/webpack-bundler-runtime": "2.8.2", "schema-utils": "4.3.0", "tapable": "2.3.0" }, "peerDependencies": { "typescript": "^4.9.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", "vue-tsc": ">=1.0.24", "webpack": "^5.0.0" }, "optionalPeers": ["typescript", "vue-tsc", "webpack"], "bin": { "mf": "bin/mf.js" } }, "sha512-XVCp1dz7ADd2YgjuvGVsS7IVJ8ViuAUCEz9gAb4M0qMJCjcPfzXQ1CzV1/Me1lKaPPwNj/cJ3NXTRvXjMHanGg=="], - "@module-federation/error-codes": ["@module-federation/error-codes@2.8.0", "", {}, "sha512-Gaog9904EmxYOQV0hli3XQ7jXeFaADfh5bnBtTCtbZ37Qd/Sz9kQfd+gYQRyIj7RGmkv9DPiN/SsmrTMrTymKw=="], + "@module-federation/error-codes": ["@module-federation/error-codes@2.8.2", "", {}, "sha512-8inlDv48QOjA//CLQ3epjoHEiMQGsz1Pmtu2N+s7gQVggn6AYHpjnMe8AsyGxtpaPg3wbX0HmBZtRFggpXUB9A=="], - "@module-federation/inject-external-runtime-core-plugin": ["@module-federation/inject-external-runtime-core-plugin@2.8.0", "", { "peerDependencies": { "@module-federation/runtime-tools": "2.8.0" } }, "sha512-fW3jD1ZVds6r/Ul8TtUA42RsB0LfT1yjo5KjqgirH9QrmEMH21x44e3e6BC6IN820XKawRDSmz2kFA5YHIQp7Q=="], + "@module-federation/inject-external-runtime-core-plugin": ["@module-federation/inject-external-runtime-core-plugin@2.8.2", "", { "peerDependencies": { "@module-federation/runtime-tools": "2.8.2" } }, "sha512-RetbaupJGiT2FtmA3WWGm72xZkNSJ6Xyy53jSgtu9HRnR/5xt/wiQl+Pe/iEfzgfLsUnUAkOsSfiPBXrDtNizw=="], - "@module-federation/managers": ["@module-federation/managers@2.8.0", "", { "dependencies": { "@module-federation/sdk": "2.8.0" } }, "sha512-SnVBCwmi962WGg6hLFElxZUCnrRJdR6glE2ZKPBY/iK07AHUN2ZxuaBCBsVzyws+xLZGHZxBHmVstijTh8dSUA=="], + "@module-federation/managers": ["@module-federation/managers@2.8.2", "", { "dependencies": { "@module-federation/sdk": "2.8.2" } }, "sha512-OQfnoUwy1IUfn6DaI2S/DPKgnElkYlP+gG8mbUQlJVEW1Zg/8V/dZbNgJUKikTJly6yR1r08PK59g7239ITYMw=="], - "@module-federation/manifest": ["@module-federation/manifest@2.8.0", "", { "dependencies": { "@module-federation/dts-plugin": "2.8.0", "@module-federation/managers": "2.8.0", "@module-federation/sdk": "2.8.0" } }, "sha512-wfVeBXc4/C2F70nRFSPqJhkcwbDgo+wQyEn3jbjJTDoUqxxhYBfHFs1ACBYOk3Qm97L7hHclHGtUG0/nvDEfAA=="], + "@module-federation/manifest": ["@module-federation/manifest@2.8.2", "", { "dependencies": { "@module-federation/dts-plugin": "2.8.2", "@module-federation/managers": "2.8.2", "@module-federation/sdk": "2.8.2" } }, "sha512-mJUZo7QFL46NXoEWNby3CfPFFm2235J7LO6bXAAMrEIT4qF8QHLiqfoo5dZmrisRgM6e+muriWtGvPFMprnXyA=="], - "@module-federation/node": ["@module-federation/node@2.7.47", "", { "dependencies": { "@module-federation/enhanced": "2.8.0", "@module-federation/runtime": "2.8.0", "@module-federation/sdk": "2.8.0", "encoding": "0.1.13", "node-fetch": "2.7.0", "tapable": "2.3.0" }, "peerDependencies": { "webpack": "^5.40.0" }, "optionalPeers": ["webpack"] }, "sha512-mifMvCjWmLl53GS+badQws0j2bsu1ICpdGzCbez4I6kSpaYA8v86L6dwcHtVHIZtkUC6cjAZBDcgpxs4fK3nFQ=="], + "@module-federation/node": ["@module-federation/node@2.7.49", "", { "dependencies": { "@module-federation/enhanced": "2.8.2", "@module-federation/runtime": "2.8.2", "@module-federation/sdk": "2.8.2", "encoding": "0.1.13", "node-fetch": "2.7.0", "tapable": "2.3.0" }, "peerDependencies": { "webpack": "^5.40.0" }, "optionalPeers": ["webpack"] }, "sha512-xNGYfhA2aqFpogb/uq6lwBeEbnmDLV6PwHzSe97mRrSSr00eUKAMwlLG6PcQP6ynbkPeDG86RYj/YUC7EWgLMA=="], - "@module-federation/rsbuild-plugin": ["@module-federation/rsbuild-plugin@2.8.0", "", { "dependencies": { "@module-federation/enhanced": "2.8.0", "@module-federation/node": "2.7.47", "@module-federation/sdk": "2.8.0" }, "peerDependencies": { "@rsbuild/core": "^1.3.21 || ^2.0.0-0" }, "optionalPeers": ["@rsbuild/core"] }, "sha512-rul5OPvLx599rWoAhCtKJ3UYqyM3Dxg0RWEfad3JdnJFqkcSLBojZXgHJE1vbF7DRdGQNmNscKw34iEyn89NwQ=="], + "@module-federation/rsbuild-plugin": ["@module-federation/rsbuild-plugin@2.8.2", "", { "dependencies": { "@module-federation/enhanced": "2.8.2", "@module-federation/node": "2.7.49", "@module-federation/sdk": "2.8.2" }, "peerDependencies": { "@rsbuild/core": "^1.3.21 || ^2.0.0-0" }, "optionalPeers": ["@rsbuild/core"] }, "sha512-rUzx5quE/pqEiZk0ESyPj4QCDCvSpqhgoq/+zjuz9vyHAuMvXL/U0si6bOclBg4UGGFlLmq5+XVIJR43iAADJg=="], - "@module-federation/rspack": ["@module-federation/rspack@2.8.0", "", { "dependencies": { "@module-federation/bridge-react-webpack-plugin": "2.8.0", "@module-federation/dts-plugin": "2.8.0", "@module-federation/inject-external-runtime-core-plugin": "2.8.0", "@module-federation/managers": "2.8.0", "@module-federation/manifest": "2.8.0", "@module-federation/runtime-tools": "2.8.0", "@module-federation/sdk": "2.8.0" }, "peerDependencies": { "@rspack/core": "^0.7.0 || ^1.0.0 || ^2.0.0-0", "typescript": "^4.9.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", "vue-tsc": ">=1.0.24" }, "optionalPeers": ["typescript", "vue-tsc"] }, "sha512-TPcrkHpaZgL25Vx3c8oSNwyv7/KktC7uo6HTQdVWlFzbq5RSoMMGkWoir5pY5124isae2/p6v5xAuqICi4r0Zg=="], + "@module-federation/rspack": ["@module-federation/rspack@2.8.2", "", { "dependencies": { "@module-federation/bridge-react-webpack-plugin": "2.8.2", "@module-federation/dts-plugin": "2.8.2", "@module-federation/inject-external-runtime-core-plugin": "2.8.2", "@module-federation/managers": "2.8.2", "@module-federation/manifest": "2.8.2", "@module-federation/runtime-tools": "2.8.2", "@module-federation/sdk": "2.8.2" }, "peerDependencies": { "@rspack/core": "^0.7.0 || ^1.0.0 || ^2.0.0-0", "typescript": "^4.9.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", "vue-tsc": ">=1.0.24" }, "optionalPeers": ["typescript", "vue-tsc"] }, "sha512-HEDirYhVYvx7IzP9jes6KLPMqSoSQwuLfBzPSOgBqY7sIH/e9zRSRO1qh5C8OXYKHi23WcQpGY+EeecIK9wxWw=="], - "@module-federation/runtime": ["@module-federation/runtime@2.8.0", "", { "dependencies": { "@module-federation/error-codes": "2.8.0", "@module-federation/runtime-core": "2.8.0", "@module-federation/sdk": "2.8.0" } }, "sha512-cGtUBQ1/TVy7KrXy6xPgy3FEmOGyIYkBA2T4iGH3ZH5PNPPTmqN9jF2AfneTSOj0RtBr7Pxq3CUt81E/UCvK1A=="], + "@module-federation/runtime": ["@module-federation/runtime@2.8.2", "", { "dependencies": { "@module-federation/error-codes": "2.8.2", "@module-federation/runtime-core": "2.8.2", "@module-federation/sdk": "2.8.2" } }, "sha512-SUoP+PD5EjSPSi6FxEPGIZoRkFifxdeYcVQbJE9mO0VEjF51gAk3/TgX8k0vzUryOBPmXekLr9SfQXU6DqUtvA=="], - "@module-federation/runtime-core": ["@module-federation/runtime-core@2.8.0", "", { "dependencies": { "@module-federation/error-codes": "2.8.0", "@module-federation/sdk": "2.8.0" } }, "sha512-Tf98+epGGiPSHqmQHuXa2uXZMMvjGf1IqJDR1/FpXfmobv5ECN0mGZCjUHGNSyxvoDyXKIkKwJu7IwEoh0ouQA=="], + "@module-federation/runtime-core": ["@module-federation/runtime-core@2.8.2", "", { "dependencies": { "@module-federation/error-codes": "2.8.2", "@module-federation/sdk": "2.8.2" } }, "sha512-PEkkK9MUp+nUCeQMS4ox3QGZfwwxgfjGA7P4umEnr5c3y8DLNDR+26tyHf/Gkjen2VsjlcyL+mEAQo1Zi4IE3g=="], "@module-federation/runtime-tools": ["@module-federation/runtime-tools@0.22.0", "", { "dependencies": { "@module-federation/runtime": "0.22.0", "@module-federation/webpack-bundler-runtime": "0.22.0" } }, "sha512-4ScUJ/aUfEernb+4PbLdhM/c60VHl698Gn1gY21m9vyC1Ucn69fPCA1y2EwcCB7IItseRMoNhdcWQnzt/OPCNA=="], - "@module-federation/sdk": ["@module-federation/sdk@2.8.0", "", {}, "sha512-yBP+9+0Z8nlvKEXAZS3AsQVy7bFbZf8eMivGk4q4ZdwG3TsLMlsPjb1dQb2i7gcAG6ux9y2LWLkj/0LVk74cnQ=="], + "@module-federation/sdk": ["@module-federation/sdk@2.8.2", "", {}, "sha512-OPS/lbQjraLXoWniQpCwQ/vqgURHTrhsackSNcOPmcJHM3LyR+DabxUc0pl8jAqExsW2l+uepQq7+/Gkei871w=="], - "@module-federation/third-party-dts-extractor": ["@module-federation/third-party-dts-extractor@2.8.0", "", {}, "sha512-nAMlr74OKIylkfRwlunOhytQbmsgb3gCqdXWnPQhG+ZtqWXGELLfMT4a1Q1ht3cS+sRpWj2SZRqK2M7GadI6tA=="], + "@module-federation/third-party-dts-extractor": ["@module-federation/third-party-dts-extractor@2.8.2", "", {}, "sha512-Xf3iZ4iDi972XMOMbmUm/c5Vwwb6cTsU+Jpoz96lUom6Yps9FQX48elIdhgcQsL7/K64PqdOONsrlZVo8zphKA=="], - "@module-federation/webpack-bundler-runtime": ["@module-federation/webpack-bundler-runtime@2.8.0", "", { "dependencies": { "@module-federation/error-codes": "2.8.0", "@module-federation/runtime": "2.8.0", "@module-federation/sdk": "2.8.0" } }, "sha512-82fDy9v+7qV5fiN8TKVhOdrxhmAZnUIX/IKivYX5ulCt8aoOzVFTiwm/P1GQUDD8z6dqR48xgJdZdf0548Mc9w=="], + "@module-federation/webpack-bundler-runtime": ["@module-federation/webpack-bundler-runtime@2.8.2", "", { "dependencies": { "@module-federation/error-codes": "2.8.2", "@module-federation/runtime": "2.8.2", "@module-federation/sdk": "2.8.2" } }, "sha512-g4xQgfgMMCKgJjVMBh7nIYGjLGNDYwSZ4lfpTdkVyWDnxmR3SL6VPQTJEZHRYPyqvrTtZE1zdDhDd++yNRvLdA=="], "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ=="], @@ -844,13 +844,17 @@ "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.7", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@tybys/wasm-util": "^0.10.1" } }, "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw=="], + "@neodrag/core": ["@neodrag/core@3.0.0-next.11", "", {}, "sha512-3WQWxyrbxiaK9zS5JU2wJsW2gpoQlZBXVghduBh61JpqaeE0T0cte8R0qYK2RuJo3J2TYQYqxO19CpG/C1i5eg=="], + + "@neodrag/solid": ["@neodrag/solid@3.0.0-next.11", "", { "peerDependencies": { "@neodrag/core": "3.0.0-next.11", "solid-js": "^1.0.0" } }, "sha512-vCBIn/pimjWMQ6vhTS2/O1XNAwzVtc4eUhdbQ91WykbZWWqQ5NocDXt/1OdYrEkeRzJcpCv8wEz5PnMkgKP81Q=="], + "@neon-rs/load": ["@neon-rs/load@0.0.4", "", {}, "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw=="], - "@noble/ciphers": ["@noble/ciphers@2.2.0", "", {}, "sha512-Z6pjIZ/8IJcCGzb2S/0Px5J81yij85xASuk1teLNeg75bfT07MV3a/O2Mtn1I2se43k3lkVEcFaR10N4cgQcZA=="], + "@noble/ciphers": ["@noble/ciphers@2.3.0", "", {}, "sha512-Clu/xdfgVTf9o7ngLOURaxePwR0j8sjclKEtVij10/jGulwFsPWCvvRgG/XjUVf8Nei+jLG6uwyXzUTGY1DQrw=="], - "@noble/curves": ["@noble/curves@2.2.0", "", { "dependencies": { "@noble/hashes": "2.2.0" } }, "sha512-T/BoHgFXirb0ENSPBquzX0rcjXeM6Lo892a2jlYJkqk83LqZx0l1Of7DzlKJ6jkpvMrkHSnAcgb5JegL8SeIkQ=="], + "@noble/curves": ["@noble/curves@2.3.0", "", { "dependencies": { "@noble/hashes": "2.3.0" } }, "sha512-v7cY+4oWYPQszRj6ZFGzTVL7uP2TaLo1xMhWHzYC5wj0ZhOXQ5x+sBre8rF3hi8cAoi0bh1qXoovoOkdFtvqEg=="], - "@noble/hashes": ["@noble/hashes@2.2.0", "", {}, "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg=="], + "@noble/hashes": ["@noble/hashes@2.3.0", "", {}, "sha512-oN+QwyX7VSHotibwubG3kpzbwKrfnyR6OOO+3Nk/53ADL7FmgHHz4TgrbaYKvvOw09u6QTx0oiH1cNCIOuN0CQ=="], "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], @@ -862,7 +866,7 @@ "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.43.0", "", {}, "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg=="], - "@orpc/client": ["@orpc/client@1.14.3", "", { "dependencies": { "@orpc/shared": "1.14.3", "@orpc/standard-server": "1.14.3", "@orpc/standard-server-fetch": "1.14.3", "@orpc/standard-server-peer": "1.14.3" } }, "sha512-0HzeD/BgPctvFnd6ltjuQvx4/POXo0K01Tee/3whAm3ohXnlGqCfhzR2VMN8zBaGs1SYe6AFzjmGG928Ej3pAg=="], + "@orpc/client": ["@orpc/client@1.15.0", "", { "dependencies": { "@orpc/shared": "1.15.0", "@orpc/standard-server": "1.15.0", "@orpc/standard-server-fetch": "1.15.0", "@orpc/standard-server-peer": "1.15.0" } }, "sha512-Qt0FdPSGySdQwy83iUWOq+Iqhw2gM9qHtyxfBbcw1mwOz1s4O2BwUFA3ymVTLIRRNYRgPrQLaBZhp8CacqeePg=="], "@orpc/contract": ["@orpc/contract@1.14.3", "", { "dependencies": { "@orpc/client": "1.14.3", "@orpc/shared": "1.14.3", "@standard-schema/spec": "^1.1.0", "openapi-types": "^12.1.3" } }, "sha512-docXs4ALK3TADAnscEywjqvV1Dy+4+B6ihfo33hayvJdxZdpVmxjHOf7pcAYaJFJ6+LgKYoskaVVKad6LLxFlg=="], @@ -872,9 +876,9 @@ "@orpc/json-schema": ["@orpc/json-schema@1.14.3", "", { "dependencies": { "@orpc/contract": "1.14.3", "@orpc/interop": "1.14.3", "@orpc/openapi": "1.14.3", "@orpc/server": "1.14.3", "@orpc/shared": "1.14.3", "json-schema-typed": "^8.0.2" } }, "sha512-Qcz2PzyZG2etpfB8ywy4Upf4SaI2x6x4fA8utVQXf5GcTPWfbTVz78MDDWnNtYEXSPj1BS95HqHC0AeGvYIB+g=="], - "@orpc/openapi": ["@orpc/openapi@1.14.3", "", { "dependencies": { "@orpc/client": "1.14.3", "@orpc/contract": "1.14.3", "@orpc/interop": "1.14.3", "@orpc/openapi-client": "1.14.3", "@orpc/server": "1.14.3", "@orpc/shared": "1.14.3", "@orpc/standard-server": "1.14.3", "json-schema-typed": "^8.0.2", "rou3": "^0.7.12" } }, "sha512-0tZN91VoT6MEkOfw+ERKyozsnDXzmDSsBeMgEHN3Hl1WVU97T9l4aZzLlZILSNl3fat3HmduEDy/boNGmAWJkQ=="], + "@orpc/openapi": ["@orpc/openapi@1.15.0", "", { "dependencies": { "@orpc/client": "1.15.0", "@orpc/contract": "1.15.0", "@orpc/interop": "1.15.0", "@orpc/openapi-client": "1.15.0", "@orpc/server": "1.15.0", "@orpc/shared": "1.15.0", "@orpc/standard-server": "1.15.0", "json-schema-typed": "^8.0.2", "rou3": "^0.7.12" } }, "sha512-nwdsVkIRTVhW+2kjtEwHQqfkYnmbJN6HTHvBfnt0KYrPftJ7qvwDo4WnoxfS7Q1jdUlVu8griT5cKZ1gAIYcpQ=="], - "@orpc/openapi-client": ["@orpc/openapi-client@1.14.3", "", { "dependencies": { "@orpc/client": "1.14.3", "@orpc/contract": "1.14.3", "@orpc/shared": "1.14.3", "@orpc/standard-server": "1.14.3" } }, "sha512-1vp+hi858XDrCwYdhONl15YKNlHtj5F5gI3dq/McRRZ45tZ9/Ma03hxzABOOcayaT1L9nX7gPxhX7l5EuRf2zw=="], + "@orpc/openapi-client": ["@orpc/openapi-client@1.15.0", "", { "dependencies": { "@orpc/client": "1.15.0", "@orpc/contract": "1.15.0", "@orpc/shared": "1.15.0", "@orpc/standard-server": "1.15.0" } }, "sha512-MGyMAC6dvQ/YoUARpTN4L1MAnK4L2T3+Z7LA8PVva2rZ8XpKABZRcfqufyPfAC0nu/0uPeebnxHaXZ/OeUIOFg=="], "@orpc/server": ["@orpc/server@1.14.3", "", { "dependencies": { "@orpc/client": "1.14.3", "@orpc/contract": "1.14.3", "@orpc/interop": "1.14.3", "@orpc/shared": "1.14.3", "@orpc/standard-server": "1.14.3", "@orpc/standard-server-aws-lambda": "1.14.3", "@orpc/standard-server-fastify": "1.14.3", "@orpc/standard-server-fetch": "1.14.3", "@orpc/standard-server-node": "1.14.3", "@orpc/standard-server-peer": "1.14.3", "cookie": "^1.1.1" }, "peerDependencies": { "crossws": ">=0.3.4", "ws": ">=8.18.1" }, "optionalPeers": ["crossws", "ws"] }, "sha512-VQG1sgruPhWdzT/ChltJ5Ju9v1A8F+s8EQ1MMSI33z0AthZ3IuuMZdqMIOo5YSuHROoFxzMJgCShOWYR9qXhQA=="], @@ -896,7 +900,7 @@ "@orpc/zod": ["@orpc/zod@1.14.3", "", { "dependencies": { "@orpc/json-schema": "1.14.3", "@orpc/openapi": "1.14.3", "@orpc/shared": "1.14.3", "escape-string-regexp": "^5.0.0", "wildcard-match": "^5.1.4" }, "peerDependencies": { "@orpc/contract": "1.14.3", "@orpc/server": "1.14.3", "zod": ">=3.25.0" } }, "sha512-+SIDmqfkTLCeeZVN6Cic4aWeiBqf2O9F4Vto9npqOEXT1szIpHKJtCmdZBsTOSD46LV5Tcg1emOde9eUeY2EBg=="], - "@oxc-project/types": ["@oxc-project/types@0.140.0", "", {}, "sha512-h5LUOzGArYemnW1NMz/DuuQhBi96J6JL2Bk8zE4kvqxB5Sg3jxmCiH4uyOWHDkiKSt5vWlG4FIwCR/DbstcNRQ=="], + "@oxc-project/types": ["@oxc-project/types@0.144.0", "", {}, "sha512-nuhZIOLuI6TFQ32I/WnUx+SCPY7SdSKwgnFHydAuoS1+Z4BRcaP+RRJmGzl9lw+0OFF7UmaESf7KQRXaNLHypg=="], "@parcel/watcher": ["@parcel/watcher@2.6.0", "", { "dependencies": { "detect-libc": "^2.0.3", "is-glob": "^4.0.3", "node-addon-api": "^7.0.0", "picomatch": "^4.0.4" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.6.0", "@parcel/watcher-darwin-arm64": "2.6.0", "@parcel/watcher-darwin-x64": "2.6.0", "@parcel/watcher-freebsd-x64": "2.6.0", "@parcel/watcher-linux-arm-glibc": "2.6.0", "@parcel/watcher-linux-arm-musl": "2.6.0", "@parcel/watcher-linux-arm64-glibc": "2.6.0", "@parcel/watcher-linux-arm64-musl": "2.6.0", "@parcel/watcher-linux-x64-glibc": "2.6.0", "@parcel/watcher-linux-x64-musl": "2.6.0", "@parcel/watcher-win32-arm64": "2.6.0", "@parcel/watcher-win32-x64": "2.6.0" } }, "sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w=="], @@ -924,27 +928,27 @@ "@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.6.0", "", { "os": "win32", "cpu": "x64" }, "sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A=="], - "@peculiar/asn1-android": ["@peculiar/asn1-android@2.8.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.8.0", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-skLbS+IOGv1lUgDqtChr8xvtvEr3HMse/JGBaL2r1J1o/n7a8wqOrovMtlRq/UXLhxvmLaONP67hwtshgzwfzA=="], + "@peculiar/asn1-android": ["@peculiar/asn1-android@2.9.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.9.0", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-ErGsrDbWYhMu/H43L3qohwCwPrjdinn8GGm4Q52evA1u2juJxqkPXv+26vBC35hhMoUkzYIOTLrFzSbXanRU+A=="], - "@peculiar/asn1-cms": ["@peculiar/asn1-cms@2.8.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.8.0", "@peculiar/asn1-x509": "^2.8.0", "@peculiar/asn1-x509-attr": "^2.8.0", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-NgekZOrSJFSBFLFoLfwePguAWAx7z1+f2TEsWFUMyiqqfntZ4+S/S5hzqME3q4pCA0iOsFKdwiQ35dwY24eVqA=="], + "@peculiar/asn1-cms": ["@peculiar/asn1-cms@2.9.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.9.0", "@peculiar/asn1-x509": "^2.9.0", "@peculiar/asn1-x509-attr": "^2.9.0", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-VKQz6sJYgSxtGaK6UdnNBUx7hmSdg0K331qrEWh5qxpQsyZGWBjxbq05AJ2bTWWjd7d+nJIxFzwvsR5T7DWC/A=="], - "@peculiar/asn1-csr": ["@peculiar/asn1-csr@2.8.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.8.0", "@peculiar/asn1-x509": "^2.8.0", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-akbF8+uvleHs8sejNPQxwmVFuInAg6FMNHOwMILXfP518YfFJwdR3jr6oNUPOaEJfuEhn/vkNOCIT6ASUd4mbg=="], + "@peculiar/asn1-csr": ["@peculiar/asn1-csr@2.9.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.9.0", "@peculiar/asn1-x509": "^2.9.0", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-SbxRzHiWnRdiDuiiji/RLsJxu2au4hmSZSKK7GQOgNr2BVvheAlFQST9qqzRchUcZ6wvcyRuPXIfYXVzLoZ/5g=="], - "@peculiar/asn1-ecc": ["@peculiar/asn1-ecc@2.8.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.8.0", "@peculiar/asn1-x509": "^2.8.0", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-ohwlk+u9Rv2NOAY1c6MfHj45ATVF8R1DUN/WCgABiRtLi2ZftlZWZX7KvpAbU8v9xPcmoILfELeEABj/rn18AQ=="], + "@peculiar/asn1-ecc": ["@peculiar/asn1-ecc@2.9.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.9.0", "@peculiar/asn1-x509": "^2.9.0", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-vNspHtTd9h6e8c2lMW+B/VHEUD+HRFV0fj/Gvz7SaJbwiecA8dxd96UTFPYI1PR8k7qwjjW9AFEyI+LuyVi4Kw=="], - "@peculiar/asn1-pfx": ["@peculiar/asn1-pfx@2.8.0", "", { "dependencies": { "@peculiar/asn1-cms": "^2.8.0", "@peculiar/asn1-pkcs8": "^2.8.0", "@peculiar/asn1-rsa": "^2.8.0", "@peculiar/asn1-schema": "^2.8.0", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-5yof1ytoB++RQtaFbqSUJ8pxDJtZT6vbVqZ8XoJ61ph7UjNVvfFwAilnCodqkNsAodpy13gDhoxZXw00pghnyg=="], + "@peculiar/asn1-pfx": ["@peculiar/asn1-pfx@2.9.0", "", { "dependencies": { "@peculiar/asn1-cms": "^2.9.0", "@peculiar/asn1-pkcs8": "^2.9.0", "@peculiar/asn1-rsa": "^2.9.0", "@peculiar/asn1-schema": "^2.9.0", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-A6bX+gZr69U38Pg1mWvPrM1eRba6L6kLR8iVG+bJtKj3qSv2rSNmlXLtej7ZOkEWt6xL6PiJD73uFEdQI3BXCg=="], - "@peculiar/asn1-pkcs8": ["@peculiar/asn1-pkcs8@2.8.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.8.0", "@peculiar/asn1-x509": "^2.8.0", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-qAKXtLpBEw9LqhKpjw3ajZSXlBur+ipW+y2ivVBQAG6F6qRx94yO+1ZR4mvw+YaCfKSaOzLeYEzsPaBp4SJELA=="], + "@peculiar/asn1-pkcs8": ["@peculiar/asn1-pkcs8@2.9.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.9.0", "@peculiar/asn1-x509": "^2.9.0", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-1JH4FliKQ3trkMD17X+bKGsph5TsiM+AiiqnVKr1wxA/GoUSDHiWG/zROzLAbDIA7eclBCjQsp8hrBEJk7LRUQ=="], - "@peculiar/asn1-pkcs9": ["@peculiar/asn1-pkcs9@2.8.0", "", { "dependencies": { "@peculiar/asn1-cms": "^2.8.0", "@peculiar/asn1-pfx": "^2.8.0", "@peculiar/asn1-pkcs8": "^2.8.0", "@peculiar/asn1-schema": "^2.8.0", "@peculiar/asn1-x509": "^2.8.0", "@peculiar/asn1-x509-attr": "^2.8.0", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-b5nDWCnkV60+cQ141D6sVVwK9nz64R5n3zSVnklGd+ECdkW2Ol3U1a6yYFlalpSOaD557yuJB64A+q42jG7lUQ=="], + "@peculiar/asn1-pkcs9": ["@peculiar/asn1-pkcs9@2.9.0", "", { "dependencies": { "@peculiar/asn1-cms": "^2.9.0", "@peculiar/asn1-pfx": "^2.9.0", "@peculiar/asn1-pkcs8": "^2.9.0", "@peculiar/asn1-schema": "^2.9.0", "@peculiar/asn1-x509": "^2.9.0", "@peculiar/asn1-x509-attr": "^2.9.0", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-igArY6bpCI6tOPm2EU9QPrXuKq2s1iraLCTym4UlooYdzcRDIPCRUN9TAEcqZ5rZhA+HiPdFKTbDP9vneN/tsg=="], - "@peculiar/asn1-rsa": ["@peculiar/asn1-rsa@2.8.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.8.0", "@peculiar/asn1-x509": "^2.8.0", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-zHEUlCqB2mk7x2lxDwHHJy7hWZOPdGHVlsmITWKB5/PbQo61atbu9PJ/0r9dQNMwFzbKPXZ8uK8/91eUhRznSg=="], + "@peculiar/asn1-rsa": ["@peculiar/asn1-rsa@2.9.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.9.0", "@peculiar/asn1-x509": "^2.9.0", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-vOD7Q4UmQWhlMYuWJawS2sD+/JcPKJNtyQuFZx09r+KFhm5/HxfGak63y/m56cpBjmb5k6PeRlQf1fvLQAieUA=="], - "@peculiar/asn1-schema": ["@peculiar/asn1-schema@2.8.0", "", { "dependencies": { "@peculiar/utils": "^2.0.2", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-7YT0U/ze0tF2QOBbE15gKZwy5tvgGyLRiRHLzhlbOpf7BT032oBSd0haZqXn5W6l26WLlu3dyxzjM+2638/z2Q=="], + "@peculiar/asn1-schema": ["@peculiar/asn1-schema@2.9.0", "", { "dependencies": { "@peculiar/utils": "^2.0.2", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-AKvPMOM7LfK0uFe1m7o7+veOa8xQGPqsqOrKi3QKgCElzwjGp39mbhr2g7mt/v/mXQHiIvJmDj5cJS173x8Q9Q=="], - "@peculiar/asn1-x509": ["@peculiar/asn1-x509@2.8.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.8.0", "@peculiar/utils": "^2.0.2", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-N0CMuhWUzsWEVq6F1q9X6+VKUnWzSW+cSVg+aPaGGwDdbFoFWTYgin5MHwXgpWd6y9COMBxnfy/Qc+Xc7F0Zwg=="], + "@peculiar/asn1-x509": ["@peculiar/asn1-x509@2.9.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.9.0", "@peculiar/utils": "^2.0.2", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-b9Na83rhRFQBd5CuMmuEqokYhmyWUbG7mNZl/thGhBwLpCdiZ85TZ3WFhF7OVgOekC5uKhLk4C3HKtdiScCMdQ=="], - "@peculiar/asn1-x509-attr": ["@peculiar/asn1-x509-attr@2.8.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.8.0", "@peculiar/asn1-x509": "^2.8.0", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-tHjkfS/qhMnmrlB2J9NhflQlQ7In3khO3CfmVrriOlpTeErY9ZIKOso1hQ5JQiyrJ7ShvqVPk7E5fQmbclkSKA=="], + "@peculiar/asn1-x509-attr": ["@peculiar/asn1-x509-attr@2.9.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.9.0", "@peculiar/asn1-x509": "^2.9.0", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-f+u+EyGjfPvPzvr0+rlh/TFEO7LWt84mEwQynCUYr4F6urf/8s6+ESJ23qfSn1aamkZR6AuBNaC62iEsGvp0+w=="], "@peculiar/utils": ["@peculiar/utils@2.0.3", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ=="], @@ -954,7 +958,7 @@ "@pingpay/onramp-types": ["@pingpay/onramp-types@0.0.3", "", { "dependencies": { "zod": "^3.25.67" }, "peerDependencies": { "typescript": "^5" } }, "sha512-qo1BCCgd9vCd3RXkE34Sl1XP2yD8opbsf3r/cgWjM6/JBXzdpcN9p2ysec4xjo2SoHNUQ+ZfEH49eYLjpbmHOQ=="], - "@playwright/test": ["@playwright/test@1.62.0", "", { "dependencies": { "playwright": "1.62.0" }, "bin": { "playwright": "cli.js" } }, "sha512-9zOJ6ZQRAena31MpOH9VSzIz8Ou3YJ/wtY/eQm5T2uhfhG7/U3COrMS8xOtUrZrp9OgdmzEnIYODye3nY1VqzA=="], + "@playwright/test": ["@playwright/test@1.62.1", "", { "dependencies": { "playwright": "1.62.1" }, "bin": { "playwright": "cli.js" } }, "sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ=="], "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="], @@ -1082,35 +1086,33 @@ "@radix-ui/rect": ["@radix-ui/rect@1.1.3", "", {}, "sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw=="], - "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.0", "", { "os": "android", "cpu": "arm64" }, "sha512-9yB1l95IrJuNGDFdOYe79vdApdz6WWBCObE+rQ2LUliYUlcyFwSYIb2xb5/Ifw7dAtMy2ZqNyd8QTSOc7duAKw=="], - - "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-pexNaW9ACLUOaBITOpU6qVu4VrsOFIjTv6bzgu0YUATo4eUJx0V605PxwZfndpPOn0ilqGqvGQ0M8UW0IE24jg=="], + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.4", "", { "os": "android", "cpu": "arm64" }, "sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA=="], - "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.2.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-NqKYaq0355ZmNMG4QGpxtEDxsc7tGDhjhCm4PpE0cwnBW+5Il95LJyq414niEiaKLVjnVHBEjSo1wngKxJNiFw=="], + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Dc5mPD8F5F/FS8i01syd7FTF6yB2fVthH/TRkjwJkzUK6EpoxHtqvZQP5Zwq80/5z19TWYHIg1KOHboCgVx/aQ=="], - "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.2.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-3vPoHzh6eBTz9IbB0/qZdSr0Qeks2echn+I4cHu2joV74VriPDdldswksEDzrl1mBB+oPRi+67+3Ib59paxIPQ=="], + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-fpDm4oBo6SqLvWUYCmFhdde3U9KH2fRNNMeAnAPAIwxRL345xutL0EtEUcuoxsoazdJGv/MuDBQHlCDrtbvqOg=="], - "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.2.0", "", { "os": "linux", "cpu": "arm" }, "sha512-E6NNefZ1bUVmKJq2tJkf45J4Zyczj7qm9rUT7NY+Xo2474Y13qWAwc2tvBt0BAVbmtXR1llkxXg0Ou1jbDf2SQ=="], + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.2.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-rSJoreDE/HoIzoaib6MTp5jQtCTdMHKIvItAKT/ImS6Y6Ww76oUaeMyp4Vc/fAgd/ehji068IxetHXAnqUwN9A=="], - "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.2.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-D+TgkdgM1vu+7/Fpf8+v0ARW+RXEP9Ccazgm8zQ4JFFd9Q7SrYQ2TakU5S5ihazQDgpKyAgZDOcIFsvoHmTZ8w=="], + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-/jm8OGHgn7oGaJu3i/qZI9spUGcJ+y/lk43ttQ/iO1tOd9NissG6o97bighBCiL+BKRngmcDuR6ikfwYdJmVuQ=="], - "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.2.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-wUqdwJBbAv0APN87GecstdMUtLjjNTs0hBALpxETD73mccFxdmt/XeizXDtN5RAlBwNKmI+Tg+blect2G+8IeQ=="], + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-tIP06BeD9EqvECBrPZ+sqdPlYrT+aYaAiu1wYziVx5elRK/ftm33JxVDy2bXGbr6J0CrtirCkR87/X5a2euEng=="], - "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.2.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9DtF35qR9/NrfhM4oxLplCzVVjE+KKm8Pjemi0i/sdhAWkUasjmSo8WTTubNJClhSHCfyk2yeyoXDQEDPtDAAw=="], + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-Ql1Q0EQqVThvn9VAVlwNzsUvbSFtCMGjLpRRi4pk5i7NZZ4n5ISiLMjHYtus4VQ2PvkSw24zyaCVsiS+sXPj1w=="], - "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.2.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-RzuHrBh8X8Hntd2N4VR02QGEciq/9JhcZoTpR/Cee6otRrlILGCf3cg2ygHuih+ZebUnWmMrDX6ITI85btO6rQ=="], + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-GjbjXD4XXfN19D0LZNbmiCBUoDiRACsYHr0yaIbbn8aFsXjHZifcYqu/W5Er5X2X990WjHXFrxarn5chzItorQ=="], - "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.2.0", "", { "os": "linux", "cpu": "x64" }, "sha512-MK7L0018jjh1jR3mh21G2j1zAVcpscJBlPo2z19pRjv2XOYGRhaV4LyiD8HO6nCDdZln9IFgCMIV5yt4E3klGQ=="], + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-p5WR0NOwaRmJ/B1b6IjEFLLivwEsf3PrdBIhRbhTCQisbo2SvHHpG4ELB/+FgQNnB88LTOF86upmJmbvZdQ2lw=="], - "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.2.0", "", { "os": "linux", "cpu": "x64" }, "sha512-gyrxLQ9NfGb/9LoVnC4kb9miUghw1mghnkfYvNHSnVIXriabnfgGPUP4RLcJm87q3KgYz4FYUG8IDiWUT+CpSw=="], + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-4/GyVjmhR+Tc6HLJvwc1sOhPqAZtySiSMesOZyX6JQ5XBxoTDEMKQzvo07NIK6nTon/SivlZqvhzvuVBNQhObQ=="], - "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.2.0", "", { "os": "none", "cpu": "arm64" }, "sha512-/6VFMQGRmrhP77KXDC+StIxGzcNp5JOIyYtw0CQ8gPlzhpiIRucYfoM5FaFamHd5BJYIdH86yfP46l1p3WdrFA=="], + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-l9eeLsCNvPpmSXUej0etw/J1eqV0Jj1D5G/xG6YTijmE6dkv6E2QezgWbTfQk63v952DPqrjOCoiqxq7Bw0YUQ=="], - "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.2.0", "", { "dependencies": { "@emnapi/core": "1.11.2", "@emnapi/runtime": "1.11.2", "@napi-rs/wasm-runtime": "^1.1.6" }, "cpu": "none" }, "sha512-rwdbUL465kisF24WEJLvP3JrEG6E5GRuIHt5wpMwHGERtHe4Wm2CIvtf5gTBgr2tGOHKh5NdKEAFS2VkOPE91g=="], + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.2.4", "", { "os": "none", "cpu": "arm64" }, "sha512-e0F355MSTMm3+UOqtV3L24gFUp2N5m1f8L/7d56deik6va+AXdrt9F8LbzGpeWGWRbZEDq4m8NVnJDeBtf9DZg=="], - "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.2.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-+5suHwRiKGmhwyUaNT8a5QbrBvLFh2DbO910TEmGRH1aSxwrCezodvGQnulv4uiWEIv1Kq4ypRsJ5+O+ry1DiA=="], + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.2.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-AWLi0uBRYh6QlE7OKhiz+phZC0qwtij2QZmhmOdsLdFn64m7oMpooE9ICE3lhm9xMb4SpDo2WbHcxX1iFLFtqw=="], - "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.0", "", { "os": "win32", "cpu": "x64" }, "sha512-WfFv6/qGufotqBSBzBYwgpCkJBk8Nj7697LL9vTz/XWc67e0r3oewu8iMRwQj3AUL45GVD7wVsPjCsAAtW66Wg=="], + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.4", "", { "os": "win32", "cpu": "x64" }, "sha512-UwSDJOg3dqCAejWdxclJjCsh3Qq4vLYMDxmyHqo1btz3stK2VqgwNd3mm5tuIwzSlGIQ/1H9Hr+Zn09mrezNqQ=="], "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], @@ -1150,11 +1152,11 @@ "@rspack/plugin-react-refresh": ["@rspack/plugin-react-refresh@1.6.2", "", { "dependencies": { "error-stack-parser": "^2.1.4" }, "peerDependencies": { "react-refresh": ">=0.10.0 <1.0.0", "webpack-hot-middleware": "2.x" }, "optionalPeers": ["webpack-hot-middleware"] }, "sha512-k+/VrfTNgo+KirjI6V+8CWRj6y+DH9jOUWv8JorYY4vKf/9xfnZ8xHzuB4iqCwTtoZl9YnxOaOuoyjJipc2tiQ=="], - "@scure/base": ["@scure/base@2.2.0", "", {}, "sha512-b8XEupJibegiXV+tDUseI8oLQc8ei3d/4Jkb2RpbHh3MfE054ov3uIz2dhFkB3FI8iwYkEh0gGCApkrYggkPNg=="], + "@scure/base": ["@scure/base@2.3.0", "", {}, "sha512-NsG6Y03tY6R5BUis4FdVtHVkur0U6FOzskgs9ZXNl78CUc9fkZ78HmENUle1nSOkCasDmbubmWD9qwB7mm4PZA=="], - "@scure/bip32": ["@scure/bip32@2.2.0", "", { "dependencies": { "@noble/curves": "2.2.0", "@noble/hashes": "2.2.0", "@scure/base": "2.2.0" } }, "sha512-zFr7t2F+a9+5tB7QbarF2HQNYrgjCNaoLAupZdKkrFMYMozJf5zqH2WJCQibMzm1qQ0QogrxVGO3qXfQDYMaQg=="], + "@scure/bip32": ["@scure/bip32@2.3.0", "", { "dependencies": { "@noble/curves": "2.3.0", "@noble/hashes": "2.3.0", "@scure/base": "2.3.0" } }, "sha512-mMPjNcXxJsvNveIgRIXrnwd4omu0wdXo4JowAUZO7/82+/bpAwVltclx6MG6nv2M3dA6IbfVv4xwWxaREm3OcA=="], - "@scure/bip39": ["@scure/bip39@2.2.0", "", { "dependencies": { "@noble/hashes": "2.2.0", "@scure/base": "2.2.0" } }, "sha512-T/Bj/YvYMNkIPq6EENO6/rcs2e7qTNuyoUXf0KBFDmp0ZDu0H2X4Lq6yC3i0c8PcWkov5EbW+yQZZbdMmk154A=="], + "@scure/bip39": ["@scure/bip39@2.3.0", "", { "dependencies": { "@noble/hashes": "2.3.0" } }, "sha512-qdyWuxoYwi3+YmqIsfkpz1I029m980WkVPilj+kG7VxSm+gKQ2BmQru3nv3LbMtpSUXuyZwdFT65JkpKu5OHOQ=="], "@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="], @@ -1210,7 +1212,7 @@ "@tailwindcss/postcss": ["@tailwindcss/postcss@4.3.3", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.3.3", "@tailwindcss/oxide": "4.3.3", "postcss": "^8.5.16", "tailwindcss": "4.3.3" } }, "sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg=="], - "@tanstack/devtools": ["@tanstack/devtools@0.13.0", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/keyboard": "^1.3.3", "@solid-primitives/resize-observer": "^2.1.3", "@tanstack/devtools-client": "0.0.8", "@tanstack/devtools-event-bus": "0.4.2", "@tanstack/devtools-ui": "0.6.0", "clsx": "^2.1.1", "goober": "^2.1.16", "solid-js": "^1.9.9" }, "bin": { "intent": "./bin/intent.js" } }, "sha512-p/nOH9bS/OO/u3402zPjoGu+Mz6Fzi/iRqJuYghuuYRUY32kZt+C0/d+pP/bi6/2JTi1FdT6oEXI2lWlA5tXxw=="], + "@tanstack/devtools": ["@tanstack/devtools@0.14.0", "", { "dependencies": { "@neodrag/solid": "3.0.0-next.11", "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/keyboard": "^1.3.3", "@solid-primitives/resize-observer": "^2.1.3", "@tanstack/devtools-client": "0.0.8", "@tanstack/devtools-event-bus": "0.4.2", "@tanstack/devtools-ui": "0.7.0", "clsx": "^2.1.1", "goober": "^2.1.16", "solid-js": "^1.9.9" }, "bin": { "intent": "./bin/intent.js" } }, "sha512-tN0SEi1BVaJYtkZbgYpvLsInjwNRAZkR3r6slSvz9Jm+bfkhcdjuOSrZp0cAwOGwdnauHZ1mYgtfe2AfC/V1NQ=="], "@tanstack/devtools-client": ["@tanstack/devtools-client@0.0.8", "", { "dependencies": { "@tanstack/devtools-event-client": "^0.5.0" } }, "sha512-cG3iZkGWCwN330bLBKa8+9r4Of2AXNoz2zUqcsy/4XsD3105ghVBx78cGyvJj9fSclNomPxoqAnDGXXhg1WLvA=="], @@ -1218,9 +1220,9 @@ "@tanstack/devtools-event-client": ["@tanstack/devtools-event-client@0.4.4", "", { "bin": { "intent": "./bin/intent.js" } }, "sha512-6T5Yop/793YI+H+5J8Hsyj4kCih9sl4t3ElLgKioW5hk3ocn+ZdSJ94tT7vL7uabxSugWYBZlOTMPzEw2puvQw=="], - "@tanstack/devtools-ui": ["@tanstack/devtools-ui@0.6.0", "", { "dependencies": { "clsx": "^2.1.1", "dayjs": "^1.11.19", "goober": "^2.1.16", "solid-js": "^1.9.9" } }, "sha512-CVaM6rT6Nl5ijo83vJYFa2SjofvpuOl/uOvbYGhBrRgUhhelNHhx8zZX+hnZCHmIr0/lzM65hsocnZ72592Rvg=="], + "@tanstack/devtools-ui": ["@tanstack/devtools-ui@0.7.0", "", { "dependencies": { "clsx": "^2.1.1", "dayjs": "^1.11.19", "goober": "^2.1.16", "solid-js": "^1.9.9" } }, "sha512-px/a+JgRSVHDj/hID1cwfsIi+ly5l7t3Yw7fLw4G556HXCNgDri36fupt9h7oBeEKntpsx3ep/XtZn51rrCv2g=="], - "@tanstack/form-core": ["@tanstack/form-core@1.33.2", "", { "dependencies": { "@tanstack/devtools-event-client": "^0.4.1", "@tanstack/pacer-lite": "^0.1.1", "@tanstack/store": "^0.11.0" } }, "sha512-F60zJd15bGrXKonc1kpRYnNRNfiES7F+hgvrPMrsZznPLqZtO2DIg76OU6R25kCYkqYQY5xvuKteuWcUsc587A=="], + "@tanstack/form-core": ["@tanstack/form-core@1.33.5", "", { "dependencies": { "@tanstack/devtools-event-client": "^0.4.1", "@tanstack/pacer-lite": "^0.1.1", "@tanstack/store": "^0.11.0" } }, "sha512-3dfx9MBP0aq5sXKteikG629X9oviptrQj0IFRk9YGcb+lB7Kv5x8S17oOSk1wUWgjQZ4xVJEMbKwOAODymocgA=="], "@tanstack/history": ["@tanstack/history@1.154.14", "", {}, "sha512-xyIfof8eHBuub1CkBnbKNKQXeRZC4dClhmzePHVOEel4G7lk/dW+TQ16da7CFdeNLv6u6Owf5VoBQxoo6DFTSA=="], @@ -1232,9 +1234,9 @@ "@tanstack/query-devtools": ["@tanstack/query-devtools@5.101.4", "", {}, "sha512-z5IPHnDX3aUWeTWlRKLyooBQekaCAw4xRpZqPQ390RiWTDBcTynjpPT221BArw0u2+pnQMdGvPQI9YNNubBcmA=="], - "@tanstack/react-devtools": ["@tanstack/react-devtools@0.10.9", "", { "dependencies": { "@tanstack/devtools": "0.13.0" }, "peerDependencies": { "@types/react": ">=16.8", "@types/react-dom": ">=16.8", "react": ">=16.8", "react-dom": ">=16.8" } }, "sha512-lS6mtccEmUaodsWiRORGM/MGKT0jgzcy5v+eY6pzOPxEgzTHUDhca+WGxShFqKxmF4oneRxXjww1gkvMrWq6uw=="], + "@tanstack/react-devtools": ["@tanstack/react-devtools@0.10.10", "", { "dependencies": { "@tanstack/devtools": "0.14.0" }, "peerDependencies": { "@types/react": ">=16.8", "@types/react-dom": ">=16.8", "react": ">=16.8", "react-dom": ">=16.8" } }, "sha512-hYH34MSVbajs1pUc22ftSapyKQp2gOh0w34a7ouYOdIcbXD6YPckSR2YpAWGq1rrs6N0l/rvV6LM/G1pgN5ARg=="], - "@tanstack/react-form": ["@tanstack/react-form@1.33.2", "", { "dependencies": { "@tanstack/form-core": "1.33.2", "@tanstack/react-store": "^0.11.0" }, "peerDependencies": { "@tanstack/react-start": "*", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@tanstack/react-start"] }, "sha512-nEfayOu+27q5cZ5E0G5dmnddqLcLjdFCatbL/LCs/iLD469a1o1yYJlr8RISV3GfnqsBpm0hf+8kM4okh5fPCw=="], + "@tanstack/react-form": ["@tanstack/react-form@1.33.5", "", { "dependencies": { "@tanstack/form-core": "1.33.5", "@tanstack/react-store": "^0.11.0" }, "peerDependencies": { "@tanstack/react-start": "*", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@tanstack/react-start"] }, "sha512-LlRB28qJwO/QCGaHvWnbdh4haBgTFiZVmzA2uzxSBS3YA7/IqrQ6HOBK70CkFQ+DbflZ7NawsmSln13h5iIdTA=="], "@tanstack/react-query": ["@tanstack/react-query@5.90.20", "", { "dependencies": { "@tanstack/query-core": "5.90.20" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-vXBxa+qeyveVO7OA0jX1z+DeyCA4JKnThKv411jd5SORpBKgkcVnYKCiBgECvADvniBX7tobwBmg01qq9JmMJw=="], @@ -1320,15 +1322,17 @@ "@types/node-forge": ["@types/node-forge@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw=="], - "@types/pg": ["@types/pg@8.20.0", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow=="], + "@types/parse-path": ["@types/parse-path@7.1.0", "", { "dependencies": { "parse-path": "*" } }, "sha512-EULJ8LApcVEPbrfND0cRQqutIOdiIgJ1Mgrhpy755r14xMohPTEpkV/k28SJvuOs9bHRFW8x+KeDAEPiGQPB9Q=="], + + "@types/pg": ["@types/pg@8.23.0", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-gPGYzOgqj8wcPJ3GSojYWD1i3wjym3ezWgis/Zk2gY8u29x9tEsP3oaBYFwqsFYmKOm6sawDu3IHNwladQc0ig=="], "@types/qs": ["@types/qs@6.15.1", "", {}, "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw=="], "@types/range-parser": ["@types/range-parser@1.2.7", "", {}, "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="], - "@types/react": ["@types/react@19.2.17", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw=="], + "@types/react": ["@types/react@19.2.18", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w=="], - "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], + "@types/react-dom": ["@types/react-dom@19.2.4", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw=="], "@types/retry": ["@types/retry@0.12.2", "", {}, "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow=="], @@ -1402,51 +1406,55 @@ "@xtuc/long": ["@xtuc/long@4.2.2", "", {}, "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="], - "@yuku-codegen/binding-darwin-arm64": ["@yuku-codegen/binding-darwin-arm64@0.8.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-7cSJH6PaKLRBdCfiB4pM6EukvgOk5xV4tyuLOIOEqrHsbnV7brtyff7CjhZbeGozdIHoOnKOi5R7rrmCWN3QSw=="], + "@yuku-codegen/binding-android-arm64": ["@yuku-codegen/binding-android-arm64@0.8.7", "", { "os": "android", "cpu": "arm64" }, "sha512-C/0zV5IhgVdYhGJTwrY0v8dknxlhiKwtVJkMUaexu9/QvRmzlV4vfU3hZlUSgqc2BxQHntL1mCVbDq8j0FRFDw=="], + + "@yuku-codegen/binding-darwin-arm64": ["@yuku-codegen/binding-darwin-arm64@0.8.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-/u+REDMI4a0/lsJXTM4c53/w31OGZLOReZIyg62uhgLs0kc8NHsj/nOcxTdlQjq5gi0zhdkccD9LaLTXcdzPvw=="], - "@yuku-codegen/binding-darwin-x64": ["@yuku-codegen/binding-darwin-x64@0.8.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-mhooLL+L5ytMxgz4ueXCIirU796X2xj97d4KSQW1HxZGzX6h8wOk5bIAhGqcmOL2bqAmMZ+UkBTbPC9VpzKb/g=="], + "@yuku-codegen/binding-darwin-x64": ["@yuku-codegen/binding-darwin-x64@0.8.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-sMMzFOwCo4WXR+/6zIBThOocSC50iIIZZdfiIDbaLvj0Ax/rWt/iavyfEAqajyvzydLyCqR/ZItdLWSRlu1umw=="], - "@yuku-codegen/binding-freebsd-x64": ["@yuku-codegen/binding-freebsd-x64@0.8.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-MHLOAlgGhdOh0ZfnWWnno4ljlFB04/Lox/7MIGYIvISMKFvejfC9Atb1t9G7pTVBvy+l5uqxzHGBSJUsDOOkTA=="], + "@yuku-codegen/binding-freebsd-x64": ["@yuku-codegen/binding-freebsd-x64@0.8.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-MpdpKXix9P+Y1rKgjvcNeNtGjXeL1CmttNhYINrWls8kRpm4xM/oBGTmn6w7to8lAlwj5jm8q03dQPl5mRv4Qw=="], - "@yuku-codegen/binding-linux-arm-gnu": ["@yuku-codegen/binding-linux-arm-gnu@0.8.0", "", { "os": "linux", "cpu": "arm" }, "sha512-Ur3Awo45Sc5/Fglr8WN5XIf4IwAsq8wLd917Du8ow6mStxsBTHqFiH+tT7d5jV0FqcJnwy8EHVczmgde0zTo1A=="], + "@yuku-codegen/binding-linux-arm-gnu": ["@yuku-codegen/binding-linux-arm-gnu@0.8.7", "", { "os": "linux", "cpu": "arm" }, "sha512-rr1srFLlPAmC1vtxfc9C1YLDe3iH09YjfSeeIidBqKhzx1MATjOAq4mjlRUOnhr/L27MotWIYOFKwVsd5JZFOg=="], - "@yuku-codegen/binding-linux-arm-musl": ["@yuku-codegen/binding-linux-arm-musl@0.8.0", "", { "os": "linux", "cpu": "arm" }, "sha512-FIy7Ttx8oeUCd/8Y6IjnOsu+lRc6En+V/H67BlVphOeCySZAo5LU8VWrb4tv0DvjaSOzdm3DmdmRzmzN7NCqWQ=="], + "@yuku-codegen/binding-linux-arm-musl": ["@yuku-codegen/binding-linux-arm-musl@0.8.7", "", { "os": "linux", "cpu": "arm" }, "sha512-eAufXh8qBRpiSO6ueaMDL+yyoXIGLhpUce72YbcACtZU2qhExwBIJyEtQf5kH2Ki0X2aqkSjSfog4OPtKXan3Q=="], - "@yuku-codegen/binding-linux-arm64-gnu": ["@yuku-codegen/binding-linux-arm64-gnu@0.8.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-s+25wl1TLvf+7LzasPEi1RR1sDfVAU0i1QH601mn3vj+HudFYBYNZtUBKFaZvl645QR6vcaDaGHnOaMZhBR3ig=="], + "@yuku-codegen/binding-linux-arm64-gnu": ["@yuku-codegen/binding-linux-arm64-gnu@0.8.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-gw4w6wPoHObBrdIC4duVWLmJOvpdE25j5D7yrM5mACNlK4klRz/lv8hK+ssQk9EJHBgZjSaqZIJVFgqNYbfv7A=="], - "@yuku-codegen/binding-linux-arm64-musl": ["@yuku-codegen/binding-linux-arm64-musl@0.8.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-pRha3Cjm4AnA5wEuhpg+8XXoGfwz5X61/9a/VNxeau47+kH6xjJspkEloIy/HbHgUnvVRqVHoEHczdGZT2J9NA=="], + "@yuku-codegen/binding-linux-arm64-musl": ["@yuku-codegen/binding-linux-arm64-musl@0.8.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-L68N6Y4XkqcIaKo3Ra88JEvBEH4AHff44A4INcrxeVWZ8CZtu2tCpfVxe3hR8qQQMcvBSQlDn24KpkCKEhVvfA=="], - "@yuku-codegen/binding-linux-x64-gnu": ["@yuku-codegen/binding-linux-x64-gnu@0.8.0", "", { "os": "linux", "cpu": "x64" }, "sha512-GySXmiw5Dw99Ba3GMV2ExQVckihuangnrIpSJagPx8RFUNtwfmzpr2ibf8k31eEAE4YUofV7mEfJN5tN6+POnQ=="], + "@yuku-codegen/binding-linux-x64-gnu": ["@yuku-codegen/binding-linux-x64-gnu@0.8.7", "", { "os": "linux", "cpu": "x64" }, "sha512-dzyAbltJmf3Cqlb8HcFuYIf5Yn0fl1vTr3XJ9HiVNNvOlhqPSArOqtw9vI1p6/VTXTjrMLXlU+s+/kNHiIy/Cw=="], - "@yuku-codegen/binding-linux-x64-musl": ["@yuku-codegen/binding-linux-x64-musl@0.8.0", "", { "os": "linux", "cpu": "x64" }, "sha512-8YFfcPZz44v8YyekWVKkNz/cD4t6DW62/dr03OVtMfwUtHfmo/8wpby0JKMleAOXbgWItSGw81LtwqM4I9oS0A=="], + "@yuku-codegen/binding-linux-x64-musl": ["@yuku-codegen/binding-linux-x64-musl@0.8.7", "", { "os": "linux", "cpu": "x64" }, "sha512-Otw4MH3404q0Bbvl+YTdW9aoUV5vXmUw8260bWvt1XlaoIX/ceSgI4ygheRDMfBPoHt6FDayQaO9OLVUZAgkFA=="], - "@yuku-codegen/binding-win32-arm64": ["@yuku-codegen/binding-win32-arm64@0.8.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-YhENbgkuzjsil+zDNV35oU3PQMDg2RXh8BPt915WCNAbIIHcqgYupHJF3564206+DbPkZtcDvcoa34Wb5B8tbQ=="], + "@yuku-codegen/binding-win32-arm64": ["@yuku-codegen/binding-win32-arm64@0.8.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-qo/jyrzryiBuKEsFiuWaBCBe3tRMynQ0qFWFgOEjcCMQeZfBm+wKiVEUEFXXLc7bh8YezguAWp0Mtnhq4ARNyA=="], - "@yuku-codegen/binding-win32-x64": ["@yuku-codegen/binding-win32-x64@0.8.0", "", { "os": "win32", "cpu": "x64" }, "sha512-qvzSRABXe6/ndubx+RNwgbFVbs7Pqroz/q/UR6vm++xsmfcpkMF43B23jgNl2xi2IUrEt8C/L1bBslXp+LtgnA=="], + "@yuku-codegen/binding-win32-x64": ["@yuku-codegen/binding-win32-x64@0.8.7", "", { "os": "win32", "cpu": "x64" }, "sha512-D5lDsVDx6m00E6bWySlWdH72Ca4TPSaphDqB6QjU6MpuNLIJqoGoatYyq2rOmBE8Zv/kunot/o58KGL03P3eiA=="], - "@yuku-parser/binding-darwin-arm64": ["@yuku-parser/binding-darwin-arm64@0.8.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-04AakSJhI4mPrqhZzXdFyaEDh0YkfeqbnyYY3aCrmxeWfR/Xr8+kFn5sh+wZYN/5HatPniELKHixJuUCUyfBvg=="], + "@yuku-parser/binding-android-arm64": ["@yuku-parser/binding-android-arm64@0.8.7", "", { "os": "android", "cpu": "arm64" }, "sha512-eGKYiUDX7Y0V7tDTmg+JTVnXnjMqfXXsorZ+EDf5kxwchQ3Or1HS14MzI2fw+jFhHR85fCWt+mtX33Yao73hIQ=="], - "@yuku-parser/binding-darwin-x64": ["@yuku-parser/binding-darwin-x64@0.8.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-BQJGI9bDeyb/X2rwhtXoBTQ9EtbkJtteX6C7cZ9jow0pqqmoOufgHPP2+m65GLk2eVNCBcfl3xlTGpJ7RFcviw=="], + "@yuku-parser/binding-darwin-arm64": ["@yuku-parser/binding-darwin-arm64@0.8.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re0RHelKLnjEURulY2/KxW+Ngb8zuNA4BRZuMwgGQNzVumT6u4U2N2hc01oeYVNVof0i7GrXE4UCNBgbpRRnjQ=="], - "@yuku-parser/binding-freebsd-x64": ["@yuku-parser/binding-freebsd-x64@0.8.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-04hmgnU152wya88raI+RQhxZPgwXcHbfdNZLu3x5ggKnJVHLD8xZZLcWIKSs59CiEXL6PKVX1/cx8GoTYlaCaQ=="], + "@yuku-parser/binding-darwin-x64": ["@yuku-parser/binding-darwin-x64@0.8.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-Hn8DROtQkjlA1ACbPgj4a7eP9IuVOI504oiTwpkWPbpaDWD9KdmnVYCqW+1LfenNK/g7O9NhWGpXEdaCNX7lIA=="], - "@yuku-parser/binding-linux-arm-gnu": ["@yuku-parser/binding-linux-arm-gnu@0.8.0", "", { "os": "linux", "cpu": "arm" }, "sha512-+cuJWUK13lwce721XGRjz7izSr2Q1U0RlHkDzdkohWpRWlttTqRdxNnaW13nDc47GBDrNsXMHx+KLcebeOeeGg=="], + "@yuku-parser/binding-freebsd-x64": ["@yuku-parser/binding-freebsd-x64@0.8.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-bAP2OV8wRuzplX/jYxv9+vvqQT8JxyNphI8fLfXGL054Xs+4/J5u33cIm3y4rxY8rdoLmmdiJs2Tq7r7lrDRfA=="], - "@yuku-parser/binding-linux-arm-musl": ["@yuku-parser/binding-linux-arm-musl@0.8.0", "", { "os": "linux", "cpu": "arm" }, "sha512-rOVPRqt9cm1YP/wPV+yyZh0FYr6UR/wm/BXslvLuge0LDewVvDrm/AxcDrwWuPAu61Nzdg2rMVfcNnrplCbC1w=="], + "@yuku-parser/binding-linux-arm-gnu": ["@yuku-parser/binding-linux-arm-gnu@0.8.7", "", { "os": "linux", "cpu": "arm" }, "sha512-kTYwJQQgmZeAWdDIWabiReIZMpmfLueIj1tCmjStUtFGhR1Z0qwxonKVfUC4N7h/VhGGzLZ//7O1kgt1QKqgCg=="], - "@yuku-parser/binding-linux-arm64-gnu": ["@yuku-parser/binding-linux-arm64-gnu@0.8.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-pAoIozKr6E+ptpaQz4CZv1O7cay2f4m7kbd+DSQug5MKdUBTZZ18GdWSPvq0fwQYraH9hZlyLqrMaeP5W/ncrA=="], + "@yuku-parser/binding-linux-arm-musl": ["@yuku-parser/binding-linux-arm-musl@0.8.7", "", { "os": "linux", "cpu": "arm" }, "sha512-uL4jE8HPT2BLlxAXyD10LqgPuXa9eDa0BKpCdSANmzIJghq/2eZo3/gQNtaxPZMupWoxjYzSad9IXrwu7aYPXQ=="], - "@yuku-parser/binding-linux-arm64-musl": ["@yuku-parser/binding-linux-arm64-musl@0.8.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-JCAg50ahXuYlrsIi1jmymX9X/9B0JYBRroAHnYttN44tAvCo1PFqukHrw1up6HvEOoLA0OjlM0Zwh60u3Gc/Zg=="], + "@yuku-parser/binding-linux-arm64-gnu": ["@yuku-parser/binding-linux-arm64-gnu@0.8.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-3gVN4pWSKZmXiNX7cU164dR9MPvesCHnlH6nPfpK+yQsCuYphjKKplcb4SnZBrhiqmXbgb2HR0c2TS0IZUPhgA=="], - "@yuku-parser/binding-linux-x64-gnu": ["@yuku-parser/binding-linux-x64-gnu@0.8.0", "", { "os": "linux", "cpu": "x64" }, "sha512-tEeVQ14etp7lpUqXzq+X5AlQzFH+m3TVDiCKIq17zxnxJ117DOVvoveWSkSMt/lj68z48SvZUMHe7xLvqtO1lg=="], + "@yuku-parser/binding-linux-arm64-musl": ["@yuku-parser/binding-linux-arm64-musl@0.8.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-S0mwfEjoLpxzXeZw802Wa4RaELsQiPtWqG6INcy8j4GtvNFtl4LCX3eGO1XLn9pyLAISLzTRyU3zUCBUPin8lg=="], - "@yuku-parser/binding-linux-x64-musl": ["@yuku-parser/binding-linux-x64-musl@0.8.0", "", { "os": "linux", "cpu": "x64" }, "sha512-+UGRYnF37nnbZNMsMjSGDXKUTIxYUmbbk3Lzib88sLK7kg2y80vjchYYoYsDUK9kAgqPWyA9hKGURHy/Kk9Few=="], + "@yuku-parser/binding-linux-x64-gnu": ["@yuku-parser/binding-linux-x64-gnu@0.8.7", "", { "os": "linux", "cpu": "x64" }, "sha512-lnbWdPmerE5D1uH1G4IEZKnPzCrWCStRGrtgpSIe1RibAo5bZIjDbbbPYXmMHCEh4F+x/JaJpElh26a3r+BPbg=="], - "@yuku-parser/binding-win32-arm64": ["@yuku-parser/binding-win32-arm64@0.8.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-l+7Va9/sX1ccRjzjJj6MR0arKsvHB5b419+pKbwzn+/18A/xfccWqmxXrn0C0NyVLlbEb3AV9Is+AbF72O85yA=="], + "@yuku-parser/binding-linux-x64-musl": ["@yuku-parser/binding-linux-x64-musl@0.8.7", "", { "os": "linux", "cpu": "x64" }, "sha512-769uwndMvMzUvATWbAcEvyLHKA+DzhHSCl/obBUrRdYfRo26yxui6S8y3z7uJ+Naup7UKrDxrpK7OnQkxkl9KQ=="], - "@yuku-parser/binding-win32-x64": ["@yuku-parser/binding-win32-x64@0.8.0", "", { "os": "win32", "cpu": "x64" }, "sha512-dolKDTJv2xrWowDBEkvSaDvCsVFZOA7DCUuD+7DatglS68WbuxS4LudU5bzSeAOL5vgPpyGm82469cMLnL+6vQ=="], + "@yuku-parser/binding-win32-arm64": ["@yuku-parser/binding-win32-arm64@0.8.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-mEB/9PlaAkisJ6KWGz0zvywXoU6+80dTlR2LwS7s/jcXXoU6fm2+sitBZXtqu3+Q4DcDgPxM45uWMCzPs0TSRw=="], - "@yuku-toolchain/types": ["@yuku-toolchain/types@0.8.0", "", {}, "sha512-hL/raFM5V9UT2lVE/lIWDTWvANqSB8TvMVp+PgICehBa96KhTl/UpGm370JXaacukR+QnaHM2Yv6O/UcrzAFGg=="], + "@yuku-parser/binding-win32-x64": ["@yuku-parser/binding-win32-x64@0.8.7", "", { "os": "win32", "cpu": "x64" }, "sha512-8vNB2DP0ou61nGb8tc/qfi41gfyDXz1MHr2zqL3nR+cJ6CEbiuWV/l/a/vv151gCgiZLLAyGkQGENpozdg716w=="], + + "@yuku-toolchain/types": ["@yuku-toolchain/types@0.8.7", "", {}, "sha512-2Z53dNxAJL6UvFoIrDZvYf3zlO8s4VJK4O2hhaB4mXVwwpX/7ajtss3cmfqKvamlNLWyt9FSWs4eoYdlbxpnHA=="], "@zorsh/zorsh": ["@zorsh/zorsh@0.5.0", "", {}, "sha512-aQ3jO0uoFfgPLkg68iP2nVzt9yFpBs/vrSiZ5G2TRmpNuhSW0lL1hMvszbalogf4JSuU7lcVFcPiiu/xC8/5AA=="], @@ -1454,11 +1462,11 @@ "accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="], - "acorn": ["acorn@8.17.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg=="], + "acorn": ["acorn@8.18.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ=="], "acorn-walk": ["acorn-walk@8.3.5", "", { "dependencies": { "acorn": "^8.11.0" } }, "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw=="], - "adm-zip": ["adm-zip@0.5.10", "", {}, "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ=="], + "adm-zip": ["adm-zip@0.6.0", "", {}, "sha512-XleryMhbuksdKtofnWZ9Sk+4CUTbms4Mb/EU32SZwToAyZ5RgVos/ki8n+yr0LWHOGKuakbXTuuYNHLQjhddgg=="], "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], @@ -1504,7 +1512,7 @@ "auto-bind": ["auto-bind@5.0.1", "", {}, "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg=="], - "axios": ["axios@1.18.1", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g=="], + "axios": ["axios@1.19.0", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.6", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw=="], "axios-retry": ["axios-retry@4.5.0", "", { "dependencies": { "is-retry-allowed": "^2.2.0" }, "peerDependencies": { "axios": "0.x || 1.x" } }, "sha512-aR99oXhpEDGo0UuAlYcn2iGRds30k366Zfa05XWScR9QaQD4JYiP3/1Qt1u7YlefUOK+cn0CcwoL1oefavQUlQ=="], @@ -1514,7 +1522,7 @@ "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - "baseline-browser-mapping": ["baseline-browser-mapping@2.11.5", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-xJo6a6YZnwZfnyGmQKWMbVOcii7XRibjOskRh+WJ9UHQoX16xrQrcIgAMQOzfvs8XiLMx6ih/fsLPF73iY2D1A=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.11.15", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-FwMjJJ7HnyZpWe+oWxegG0fezZyBZUagI5LZEoO3GCbtbKNwRfMH9Ue5d5v01PNePBy1QSfPSDTTeVL0Hb9EzA=="], "batch": ["batch@0.6.1", "", {}, "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw=="], @@ -1522,7 +1530,7 @@ "better-call": ["better-call@1.3.7", "", { "dependencies": { "@better-auth/utils": "^0.4.0", "@better-fetch/fetch": "^1.1.21", "rou3": "^0.7.12", "set-cookie-parser": "^3.0.1" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-Al51/hjp2SSp6CRTa3F2ptcx4yQVS1xWKoY6jcVXqNYOap6mHFP2jUBn5EwIL4iIed1/Sq4hlQ+Umm6EflZG+w=="], - "better-near-auth": ["better-near-auth@1.8.3", "", { "dependencies": { "@scure/base": "^2.2.0", "nanostores": "^1.1.0", "near-kit": "^0.14.0", "zod": "^4.4.3" }, "peerDependencies": { "@better-auth/core": "^1.6.25", "better-auth": "^1.6.25", "typescript": "^5.9.3" } }, "sha512-RKr1UYk6Xs6wPCIRq3oRHLKmxOEuDBo8ofaoD66DKBI1qJZTT/rZbcc5hj0O29JJ8+QiUgQfsQ/Qa67kEziP/A=="], + "better-near-auth": ["better-near-auth@1.10.0", "", { "dependencies": { "@scure/base": "^2.2.0", "nanostores": "^1.1.0", "near-kit": "^0.14.0", "zod": "^4.4.3" }, "peerDependencies": { "@better-auth/core": "^1.6.25", "better-auth": "^1.6.25", "typescript": "^5.9.3" } }, "sha512-qLMm+pM+3wUHewLvEwpgT0LDFAIXhRwoKr0IgNcKC0Eu+DALaqe6x8KGX7FmIffY9ETevGubNjth2cmG1AaOMg=="], "better-path-resolve": ["better-path-resolve@1.0.0", "", { "dependencies": { "is-windows": "^1.0.0" } }, "sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g=="], @@ -1532,13 +1540,13 @@ "body-parser": ["body-parser@1.20.6", "", { "dependencies": { "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "~1.2.0", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", "qs": "~6.15.1", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" } }, "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g=="], - "bonjour-service": ["bonjour-service@1.4.3", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "multicast-dns": "^7.2.5" } }, "sha512-2Kd5UYlFUVgAKMTyuBLl6w49wqfOnbxHqmuH0oCl/n7TfAikR0zoowNOP5BU4dfXmm+Vr9JyEN370auSMx+CNg=="], + "bonjour-service": ["bonjour-service@1.4.4", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "multicast-dns": "^7.2.5" } }, "sha512-jCZcVv7eoc4QesRscwEZtSROBen+6LpKAmBIsQYQrsAeVHLyMXWX/t6eIV5KiRZYNUBl8eVqImEEMQ8L5+c/Kw=="], - "brace-expansion": ["brace-expansion@5.0.8", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg=="], + "brace-expansion": ["brace-expansion@5.0.9", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg=="], "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], - "browserslist": ["browserslist@4.28.7", "", { "dependencies": { "baseline-browser-mapping": "^2.10.44", "caniuse-lite": "^1.0.30001806", "electron-to-chromium": "^1.5.393", "node-releases": "^2.0.51", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw=="], + "browserslist": ["browserslist@4.28.8", "", { "dependencies": { "baseline-browser-mapping": "^2.11.12", "caniuse-lite": "^1.0.30001809", "electron-to-chromium": "^1.5.402", "node-releases": "^2.0.53", "update-browserslist-db": "^1.3.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA=="], "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], @@ -1556,7 +1564,7 @@ "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], - "caniuse-lite": ["caniuse-lite@1.0.30001806", "", {}, "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw=="], + "caniuse-lite": ["caniuse-lite@1.0.30001809", "", {}, "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ=="], "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], @@ -1636,15 +1644,15 @@ "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], - "dayjs": ["dayjs@1.11.21", "", {}, "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA=="], + "dayjs": ["dayjs@1.11.23", "", {}, "sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ=="], "debounce": ["debounce@1.2.1", "", {}, "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug=="], - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="], - "default-browser": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="], + "default-browser": ["default-browser@5.5.1", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw=="], "default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="], @@ -1696,7 +1704,7 @@ "effect": ["effect@3.21.2", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-rXd2FGDM8KdjSIrc+mqEELo7ScW7xTVxEf1iInmPSpIde9/nyGuFM710cjTo7/EreGXiUX2MOonPpprbz2XHCg=="], - "electron-to-chromium": ["electron-to-chromium@1.5.396", "", {}, "sha512-yHiw2Y3C3H9U6TMbOfoWK/BPreiOPXRfTWPBwQBoZG6/8TB6eOPnsy5oaRYuatR7Fw2SJ4kKforgufeo7fq0EQ=="], + "electron-to-chromium": ["electron-to-chromium@1.5.408", "", {}, "sha512-SLoprcYpJ/OH2v2ps0+N5biv9H4/KBT3+YmmDew64TwK5y9j2wv7pMOFY7IorVkyMtEyLSCRlXKLsNlakeAlPw=="], "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], @@ -1706,7 +1714,7 @@ "encoding": ["encoding@0.1.13", "", { "dependencies": { "iconv-lite": "^0.6.2" } }, "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A=="], - "enhanced-resolve": ["enhanced-resolve@5.24.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-PwKooW9JUzh5chmYfHM3IQl5OkK2u2Nm011MgeZrss3JmFraUx/fqrf78kk8GUMYoibx/14MdwTl/1WKkG7TpQ=="], + "enhanced-resolve": ["enhanced-resolve@5.24.5", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A=="], "enquirer": ["enquirer@2.4.1", "", { "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" } }, "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ=="], @@ -1718,15 +1726,15 @@ "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], - "es-module-lexer": ["es-module-lexer@2.3.1", "", {}, "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA=="], + "es-module-lexer": ["es-module-lexer@2.3.2", "", {}, "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw=="], "es-object-atoms": ["es-object-atoms@1.1.2", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw=="], "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], - "es-toolkit": ["es-toolkit@1.50.0", "", {}, "sha512-OyZKhUVvEep9ITEiwHn8GKnMRQIVqoSIX7WnRbkWgJkllCujilqP2rD0u979tkl8wqyc8ICwlc1UBVv/Sl1G6w=="], + "es-toolkit": ["es-toolkit@1.51.0", "", {}, "sha512-zC2lQGkM7QX+Gm6iM3+WIdZJzthsEd14LvRNJneSO2hzyz/zNBENR8+YXWo1cKxgPBtV6ksPYHELbcwBRzmdCw=="], - "esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="], + "esbuild": ["esbuild@0.28.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.2", "@esbuild/android-arm": "0.28.2", "@esbuild/android-arm64": "0.28.2", "@esbuild/android-x64": "0.28.2", "@esbuild/darwin-arm64": "0.28.2", "@esbuild/darwin-x64": "0.28.2", "@esbuild/freebsd-arm64": "0.28.2", "@esbuild/freebsd-x64": "0.28.2", "@esbuild/linux-arm": "0.28.2", "@esbuild/linux-arm64": "0.28.2", "@esbuild/linux-ia32": "0.28.2", "@esbuild/linux-loong64": "0.28.2", "@esbuild/linux-mips64el": "0.28.2", "@esbuild/linux-ppc64": "0.28.2", "@esbuild/linux-riscv64": "0.28.2", "@esbuild/linux-s390x": "0.28.2", "@esbuild/linux-x64": "0.28.2", "@esbuild/netbsd-arm64": "0.28.2", "@esbuild/netbsd-x64": "0.28.2", "@esbuild/openbsd-arm64": "0.28.2", "@esbuild/openbsd-x64": "0.28.2", "@esbuild/openharmony-arm64": "0.28.2", "@esbuild/sunos-x64": "0.28.2", "@esbuild/win32-arm64": "0.28.2", "@esbuild/win32-ia32": "0.28.2", "@esbuild/win32-x64": "0.28.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA=="], "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], @@ -1752,9 +1760,9 @@ "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], - "eventsource": ["eventsource@4.1.0", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-2GuF51iuHX6A9xdTccMTsNb7VO0lHZihApxhvQzJB5A03DvHDd2FQepodbMaztPBmBcE/ox7o2gqaxGhYB9LhQ=="], + "eventsource": ["eventsource@4.1.1", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-D6bTRWh6KahHTK/m4WnjPQyEinNPf9eFLEZSEoj7d6fTibspnAVYfzHvirL7u/aoX5d9YYfIkBVAhmigUELk9w=="], - "eventsource-parser": ["eventsource-parser@3.1.0", "", {}, "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg=="], + "eventsource-parser": ["eventsource-parser@3.1.1", "", {}, "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ=="], "every-plugin": ["every-plugin@workspace:packages/every-plugin"], @@ -1768,7 +1776,7 @@ "express": ["express@4.22.2", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.5", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "~1.3.1", "fresh": "~0.5.2", "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", "qs": "~6.15.1", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", "serve-static": "~1.16.2", "setprototypeof": "1.2.0", "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q=="], - "exsolve": ["exsolve@1.1.0", "", {}, "sha512-D+42+T12DdIlJM3uepa55qGiL3sYdLBOxIl2ifQCzCHz4c7eiolaHsi3BIqEr7JxBzxv2pYZQX9kw16ziMcEmw=="], + "exsolve": ["exsolve@1.1.1", "", {}, "sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g=="], "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], @@ -1780,7 +1788,7 @@ "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], - "fast-uri": ["fast-uri@3.1.4", "", {}, "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw=="], + "fast-uri": ["fast-uri@3.1.5", "", {}, "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw=="], "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], @@ -1804,7 +1812,7 @@ "find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], - "flatted": ["flatted@3.4.3", "", {}, "sha512-/zipXxyO6rGvuNGDiULY9MvEGSkb2gaG4GGH4ygMi0ZZzyMHdUZBmntJmx5x1G2VuPytCwGN4xsJP6cw+sK+vQ=="], + "flatted": ["flatted@3.4.4", "", {}, "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q=="], "follow-redirects": ["follow-redirects@1.16.0", "", {}, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="], @@ -1814,7 +1822,7 @@ "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], - "framer-motion": ["framer-motion@12.42.2", "", { "dependencies": { "motion-dom": "^12.42.2", "motion-utils": "^12.39.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-5XY9luDiu0oHfHBjpDthFMh0ES+122w6p/papSJBweMkO8Sn+PW2QaEgRblQBpWFnuvZS5qvarpt/hO2pjGmnw=="], + "framer-motion": ["framer-motion@12.43.0", "", { "dependencies": { "motion-dom": "^12.43.0", "motion-utils": "^12.39.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-1eaL3RvR/kAlbG7UYcpMptEyzPoENO0c6w7ZnB3/hh2vSAz/6uGAFn6fdoqTBguNstf3MsFhJHsD/0DHiclG+g=="], "fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="], @@ -1836,13 +1844,13 @@ "get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="], - "get-tsconfig": ["get-tsconfig@4.14.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA=="], + "get-tsconfig": ["get-tsconfig@4.14.3", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA=="], "giget": ["giget@3.3.1", "", { "bin": { "giget": "dist/cli.mjs" } }, "sha512-r+mvuDjrjMpsdw46Kmeydb8bdHm7wOKw8wNBtTndkjbPjgAp5oUJUxRE76wZFknxIPokfWvep2qSXK37aXE6zg=="], - "git-up": ["git-up@7.0.0", "", { "dependencies": { "is-ssh": "^1.4.0", "parse-url": "^8.1.0" } }, "sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ=="], + "git-up": ["git-up@8.1.1", "", { "dependencies": { "is-ssh": "^1.4.0", "parse-url": "^9.2.0" } }, "sha512-FDenSF3fVqBYSaJoYy1KSc2wosx0gCvKP+c+PRBht7cAaiCeQlBtfBDX9vgnNOHmdePlSFITVcn4pFfcgNvx3g=="], - "git-url-parse": ["git-url-parse@15.0.0", "", { "dependencies": { "git-up": "^7.0.0" } }, "sha512-5reeBufLi+i4QD3ZFftcJs9jC26aULFLBU23FeKM/b1rI0K6ofIeAblmDVO7Ht22zTDE9+CkJ3ZVb0CgJmz3UQ=="], + "git-url-parse": ["git-url-parse@16.1.0", "", { "dependencies": { "git-up": "^8.1.0" } }, "sha512-cPLz4HuK86wClEW7iDdeAKcCVlWXmrLpb2L+G9goW0Z1dtpNS6BXXSOckUTlJT/LDQViE1QZKstNORzHsLnobw=="], "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="], @@ -1890,9 +1898,9 @@ "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="], - "highlight.js": ["highlight.js@11.11.1", "", {}, "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w=="], + "highlight.js": ["highlight.js@11.11.2", "", {}, "sha512-oaXMACAU0kzOMXBjWpNcX+vlwSBCIAiZ9BHa7gA15NOTtT2L/l8OSZDuqS2XppOhZBPJ7hm4o8ep2kyuip2uEQ=="], - "hono": ["hono@4.12.32", "", {}, "sha512-XcuyW9qE2kJn07PkecMOBd5Vq/hMy7mmGw+idz1yblbg9N17ijJODrvPkn7/dwL3Kulj8LcRJ69DLOWf91dRUg=="], + "hono": ["hono@4.13.2", "", {}, "sha512-JydRilDRkYBQMt9qR9U92mXxmbGqsqSn/IKOrh4e7/gEbn+0zSr8igTu0obwJoNGN4sez28DIql7FBHWydoJpA=="], "hono-rate-limiter": ["hono-rate-limiter@0.5.3", "", { "peerDependencies": { "hono": "^4.10.8", "unstorage": "^1.17.3" }, "optionalPeers": ["unstorage"] }, "sha512-M0DxbVMpPELEzLi0AJg1XyBHLGJXz7GySjsPoK+gc5YeeBsdGDGe+2RvVuCAv8ydINiwlbxqYMNxUEyYfRji/A=="], @@ -1938,7 +1946,7 @@ "inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="], - "ipaddr.js": ["ipaddr.js@2.4.0", "", {}, "sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ=="], + "ipaddr.js": ["ipaddr.js@2.5.0", "", {}, "sha512-aq+t5NAc+cS6rZQQVWC2x98CPqGtKKTMDd4Gaodv0wShnItdKg/51djkGJ1hqH+Oy0ivDftCbSLCQob8zso01w=="], "is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="], @@ -1998,13 +2006,13 @@ "jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="], - "jose": ["jose@6.2.4", "", {}, "sha512-N8acGzVsQy6M/fjFcxtysNc4Q379TcM5dM/qKkNtsHFji88yANnXTr7BLeP75iPnFwBfQzM/jg2BZ9+HZrHCZA=="], + "jose": ["jose@6.2.9", "", {}, "sha512-XrchZOFZUl/T3vTwRe8XK+cJrGtMF4th1ARnDfwbBXFKThGhlsxEE4Zu03AD/bjJSt/9jT/mxrOCkJWOg77aPA=="], - "js-base64": ["js-base64@3.9.2", "", {}, "sha512-6zayE8QlUdiweYI6cETD/XBSqFcoCUlufn/29PJR99r82x1yDnIprRca0YvAYpAW+ez0GuQkVBC6xG5QkD7OjA=="], + "js-base64": ["js-base64@3.9.3", "", {}, "sha512-uwYQp+VJ38FVvtim6qNbit6e9uT6dwWQ4Y1+H9TxhW5hcHjpHwoxlR0nMpqUmIFOmu4VqMxwdJA88gIVuZJQ/g=="], "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], - "js-yaml": ["js-yaml@4.3.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q=="], + "js-yaml": ["js-yaml@4.3.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ=="], "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], @@ -2020,35 +2028,35 @@ "kubernetes-types": ["kubernetes-types@1.30.0", "", {}, "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q=="], - "kysely": ["kysely@0.28.17", "", {}, "sha512-nbD8lB9EB3wNdMhOCdx5Li8DxnLbvKByylRLcJ1h+4SkrowVeECAyZlyiKMThF7xFdRz0jSQ2MoJr+wXux2y0Q=="], + "kysely": ["kysely@0.29.5", "", {}, "sha512-ooa+eSbBNPTo3MycPEuW5jdrxQdQwdtB3LC3h43FiXQbIry5tR0C5lDG7eealK0E4D7XjrnOP5DIUg/LyjRMYQ=="], "launch-editor": ["launch-editor@2.14.1", "", { "dependencies": { "picocolors": "^1.1.1", "shell-quote": "^1.8.4" } }, "sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA=="], "libsql": ["libsql@0.5.29", "", { "dependencies": { "@neon-rs/load": "^0.0.4", "detect-libc": "2.0.2" }, "optionalDependencies": { "@libsql/darwin-arm64": "0.5.29", "@libsql/darwin-x64": "0.5.29", "@libsql/linux-arm-gnueabihf": "0.5.29", "@libsql/linux-arm-musleabihf": "0.5.29", "@libsql/linux-arm64-gnu": "0.5.29", "@libsql/linux-arm64-musl": "0.5.29", "@libsql/linux-x64-gnu": "0.5.29", "@libsql/linux-x64-musl": "0.5.29", "@libsql/win32-x64-msvc": "0.5.29" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "arm", "x64", "arm64", ] }, "sha512-8lMP8iMgiBzzoNbAPQ59qdVcj6UaE/Vnm+fiwX4doX4Narook0a4GPKWBEv+CR8a1OwbfkgL18uBfBjWdF0Fzg=="], - "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], + "lightningcss": ["lightningcss@1.33.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.33.0", "lightningcss-darwin-arm64": "1.33.0", "lightningcss-darwin-x64": "1.33.0", "lightningcss-freebsd-x64": "1.33.0", "lightningcss-linux-arm-gnueabihf": "1.33.0", "lightningcss-linux-arm64-gnu": "1.33.0", "lightningcss-linux-arm64-musl": "1.33.0", "lightningcss-linux-x64-gnu": "1.33.0", "lightningcss-linux-x64-musl": "1.33.0", "lightningcss-win32-arm64-msvc": "1.33.0", "lightningcss-win32-x64-msvc": "1.33.0" } }, "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA=="], - "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.33.0", "", { "os": "android", "cpu": "arm64" }, "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg=="], - "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.33.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg=="], - "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.33.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ=="], - "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.33.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg=="], - "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.33.0", "", { "os": "linux", "cpu": "arm" }, "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ=="], - "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg=="], - "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ=="], - "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg=="], - "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw=="], - "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.33.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA=="], - "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.33.0", "", { "os": "win32", "cpu": "x64" }, "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA=="], "locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], @@ -2060,7 +2068,7 @@ "lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="], - "lucide-react": ["lucide-react@1.27.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-rJicGl/3Fly/E0rOH1YmPZ6e49JCnKknh1ox1vpHnkfjujAkKA6sqUZvH3MTAaXXjgexyUwgNwTJzTtYuAFYJw=="], + "lucide-react": ["lucide-react@1.31.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-G8u2eEtoHUnUa9f8lbvqDhCiORMnYLdUEo06EEG9MQvHQrInKcX3Pa2TH39MM5qyzRcWETxB0+aOwAPI1g1kEg=="], "lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="], @@ -2102,7 +2110,7 @@ "media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="], - "memfs": ["memfs@4.64.0", "", { "dependencies": { "@jsonjoy.com/fs-core": "4.64.0", "@jsonjoy.com/fs-fsa": "4.64.0", "@jsonjoy.com/fs-node": "4.64.0", "@jsonjoy.com/fs-node-builtins": "4.64.0", "@jsonjoy.com/fs-node-to-fsa": "4.64.0", "@jsonjoy.com/fs-node-utils": "4.64.0", "@jsonjoy.com/fs-print": "4.64.0", "@jsonjoy.com/fs-snapshot": "4.64.0", "@jsonjoy.com/json-pack": "^1.11.0", "@jsonjoy.com/util": "^1.9.0", "glob-to-regex.js": "^1.0.1", "thingies": "^2.5.0", "tree-dump": "^1.0.3", "tslib": "^2.0.0" } }, "sha512-Kw72fgY7Wn+sD8KmtNWSafl1dz0UvAsE/PHs3YVfLiaZuA3HxNm9sRLqAu0ATiBGJvME1PxZXbBZPv5GycDeAw=="], + "memfs": ["memfs@4.68.1", "", { "dependencies": { "@jsonjoy.com/fs-core": "4.68.1", "@jsonjoy.com/fs-fsa": "4.68.1", "@jsonjoy.com/fs-node": "4.68.1", "@jsonjoy.com/fs-node-builtins": "4.68.1", "@jsonjoy.com/fs-node-to-fsa": "4.68.1", "@jsonjoy.com/fs-node-utils": "4.68.1", "@jsonjoy.com/fs-print": "4.68.1", "@jsonjoy.com/fs-snapshot": "4.68.1", "@jsonjoy.com/json-pack": "^1.11.0", "@jsonjoy.com/util": "^1.9.0", "glob-to-regex.js": "^1.0.1", "thingies": "^2.5.0", "tree-dump": "^1.0.3", "tslib": "^2.0.0" } }, "sha512-OD+IDRUvIxu3QHL+nFm9gdyugInD27FDJ+sl4B5QgomPHXMlbw+GP918P8VNKu2FkNlVeqBkpzkwROpamVifRw=="], "merge-descriptors": ["merge-descriptors@1.0.3", "", {}, "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="], @@ -2180,7 +2188,7 @@ "minimalistic-assert": ["minimalistic-assert@1.0.1", "", {}, "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="], - "minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], + "minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="], "minimizer-webpack-plugin": ["minimizer-webpack-plugin@5.6.1", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", "schema-utils": "^4.3.0", "terser": "^5.31.1" }, "peerDependencies": { "@minify-html/node": "*", "@swc/core": "*", "@swc/css": "*", "@swc/html": "*", "clean-css": "*", "cssnano": "*", "csso": "*", "esbuild": "*", "html-minifier-terser": "*", "lightningcss": "*", "postcss": "*", "uglify-js": "*", "webpack": "^5.1.0" }, "optionalPeers": ["@minify-html/node", "@swc/core", "@swc/css", "@swc/html", "clean-css", "cssnano", "csso", "esbuild", "html-minifier-terser", "lightningcss", "postcss", "uglify-js"] }, "sha512-DoeAZz8Q1C1znwsUzej1fdoi4jCf7/+Em27ouLqfK/+3m8G+D7yDhUwrc3CNhjSzGUN1kn7Iv4sWmjflQHenpw=="], @@ -2190,7 +2198,7 @@ "mipd": ["mipd@0.0.7", "", { "peerDependencies": { "typescript": ">=5.0.4" }, "optionalPeers": ["typescript"] }, "sha512-aAPZPNDQ3uMTdKbuO2YmAw2TxLHO0moa4YKAyETM/DTj5FloZo+a+8tU+iv4GmW+sOxKLSRwcSFuczk+Cpt6fg=="], - "motion-dom": ["motion-dom@12.42.2", "", { "dependencies": { "motion-utils": "^12.39.0" } }, "sha512-5gIMWLp/PycBtJRJWRgjxke5n8dlvkSn2DrYW+tr3XcqAZY1xZh6BJyooJXCM8wdfM7wfMjkBJNLge1CKPUIRA=="], + "motion-dom": ["motion-dom@12.43.0", "", { "dependencies": { "motion-utils": "^12.39.0" } }, "sha512-azKON4d9S65PEoFUiQTMTgPheEmzf2QngdRc50AKfJp9Q9mmcBVw22c8eMq9k8kxOFHdL7+WZY7N/5F/lwiDag=="], "motion-utils": ["motion-utils@12.39.0", "", {}, "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ=="], @@ -2210,7 +2218,7 @@ "nanoid": ["nanoid@5.1.16", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ=="], - "nanostores": ["nanostores@1.4.1", "", {}, "sha512-PGd3uPojJB9Z07d5NX3Db/SOSBbyy3wLMUGq0GpnEEJfVzY9mq7daPMAZ3jObV5D3Jn+YKND636eI5ULg7F80Q=="], + "nanostores": ["nanostores@1.5.1", "", {}, "sha512-DNIX+HyFpo14fKGe0NsX9/aPzdKGiSZwX5xEMpDwQrDdo2iTiUYunOCCQLYwJrziKVe8ZOtVvcv0dEVI8lMx2g=="], "near-kit": ["near-kit@0.14.0", "", { "dependencies": { "@napi-rs/keyring": "^1.3.0", "@noble/curves": "^2.2.0", "@noble/hashes": "^2.0.1", "@scure/base": "^2.2.0", "@scure/bip32": "^2.2.0", "@scure/bip39": "^2.2.0", "@zorsh/zorsh": "^0.5.0", "tar": "^7.5.13", "zod": "^4.4.3" }, "peerDependencies": { "@hot-labs/near-connect": ">=0.11.0", "@near-wallet-selector/core": ">=8.0.0" }, "optionalPeers": ["@hot-labs/near-connect", "@near-wallet-selector/core"] }, "sha512-RWv/CNhctWJeIxIFDgBXbxym24U93huJYy8zK0u5uEVtrqsb8gwkJ2nRz80tJuoBT16xQbTJi3dzhk6mrWoZuw=="], @@ -2234,7 +2242,7 @@ "node-persist": ["node-persist@4.0.4", "", { "dependencies": { "p-limit": "^3.1.0" } }, "sha512-8sPAz/7tw1mCCc8xBG4f0wi+flHkSSgQeX998iQ75Pu27evA6UUWCjSE7xnrYTg2q33oU5leJ061EKPDv6BocQ=="], - "node-releases": ["node-releases@2.0.51", "", {}, "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ=="], + "node-releases": ["node-releases@2.0.53", "", {}, "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ=="], "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], @@ -2246,7 +2254,7 @@ "obug": ["obug@2.1.4", "", {}, "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA=="], - "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], + "ohash": ["ohash@2.0.12", "", {}, "sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw=="], "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], @@ -2284,7 +2292,7 @@ "parse-path": ["parse-path@7.1.0", "", { "dependencies": { "protocols": "^2.0.0" } }, "sha512-EuCycjZtfPcjWk7KTksnJ5xPMvWGA/6i4zrLYhRG0hGvC3GPU/jGUj3Cy+ZR0v30duV3e23R95T1lE2+lsndSw=="], - "parse-url": ["parse-url@8.1.0", "", { "dependencies": { "parse-path": "^7.0.0" } }, "sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w=="], + "parse-url": ["parse-url@9.2.0", "", { "dependencies": { "@types/parse-path": "^7.0.0", "parse-path": "^7.0.0" } }, "sha512-bCgsFI+GeGWPAvAiUv63ZorMeif3/U0zaXABGJbOWt5OH2KCaPHF6S+0ok4aqM9RuIPGyZdx9tR9l13PsW4AYQ=="], "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], @@ -2304,7 +2312,7 @@ "perfect-debounce": ["perfect-debounce@2.1.0", "", {}, "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g=="], - "pg": ["pg@8.22.0", "", { "dependencies": { "pg-connection-string": "^2.14.0", "pg-pool": "^3.14.0", "pg-protocol": "^1.15.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.4.0" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-8wih1vVIBMxoUM2oB4soJsD9tDnDpLv4OXBJ+EJzFsvycD+lfyIreC2gGHq78f8jbLLt+bvlPTFdFZfJkOuzAA=="], + "pg": ["pg@8.23.0", "", { "dependencies": { "pg-connection-string": "^2.14.0", "pg-pool": "^3.14.0", "pg-protocol": "^1.16.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.4.0" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-Ip2EQCngowJLGOfCwkFhPXU7/ljlhn6Rxlmy4XYfL2Y+vyRM59+8uR2xqRWKdYmbXmxCFOAmKxBuSUCdF34qLg=="], "pg-cloudflare": ["pg-cloudflare@1.4.0", "", {}, "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A=="], @@ -2314,7 +2322,7 @@ "pg-pool": ["pg-pool@3.14.0", "", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw=="], - "pg-protocol": ["pg-protocol@1.15.0", "", {}, "sha512-cq9sECI5s0+uPUXjbz8ioyPJni6RzsRib0US67i5IoTZKw8fNeYlVE7u8F4dG7vEJJtc5wdD1K189lCCUwqWTQ=="], + "pg-protocol": ["pg-protocol@1.16.0", "", {}, "sha512-sILXutLVjCLjcDuOmvhX5e2Z4cS5qG/6Bu3VkpFwdf/633ElGLpEh9bgmuI5I4sqKqkifQiGyiCcx1HdtrK7tg=="], "pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="], @@ -2328,11 +2336,11 @@ "pkg-types": ["pkg-types@2.3.1", "", { "dependencies": { "confbox": "^0.2.4", "exsolve": "^1.0.8", "pathe": "^2.0.3" } }, "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg=="], - "playwright": ["playwright@1.62.0", "", { "dependencies": { "playwright-core": "1.62.0" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-Z14dG305dgaLu6foB1TXQagFiW8JfSUIUaUuPaKQ6NtBPKF1P/qXcqfh6c6K/icPqdy37JmjbiBXf6JNg6Sylw=="], + "playwright": ["playwright@1.62.1", "", { "dependencies": { "playwright-core": "1.62.1" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg=="], - "playwright-core": ["playwright-core@1.62.0", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-nsNRyq0r2zsG8AcRHWknc9QRA5XCueC7gWMrs+Gx2tlZn9hcl8zudfh00lhJPY1DE7NmZ6bDsT9g2yey8mXljA=="], + "playwright-core": ["playwright-core@1.62.1", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw=="], - "postcss": ["postcss@8.5.23", "", { "dependencies": { "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg=="], + "postcss": ["postcss@8.5.26", "", { "dependencies": { "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ=="], "postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="], @@ -2366,7 +2374,7 @@ "pvtsutils": ["pvtsutils@1.3.6", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg=="], - "pvutils": ["pvutils@1.1.5", "", {}, "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA=="], + "pvutils": ["pvutils@1.2.0", "", {}, "sha512-BbubeCEyTuQjVMakvJQ/Sxbc93F2pwmbsxONT/ZRrwU7Ua38d8unYTwXpTVLAKJ4BDuH9IGztCjQcd/N/39Dvg=="], "qs": ["qs@6.15.3", "", { "dependencies": { "es-define-property": "^1.0.1", "side-channel": "^1.1.1" } }, "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A=="], @@ -2410,7 +2418,7 @@ "readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], - "recast": ["recast@0.23.12", "", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-dEWRjcINDu/F4l2dYx57ugBtD7HV9KXESyxhzw/MqWLeglJrsjJKqACPyUPg+6AF8mIgm+Zi0dZ3ACoIg+QtpA=="], + "recast": ["recast@0.23.21", "", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-mFAyJq9vUbSTARLZUvAEf1z3YxlvAwswbmxMx2mPA/MSm4KmpwvwvhsH/NIrZhyOuwD60Lzyw2qh83uCbgTPYw=="], "reflect-metadata": ["reflect-metadata@0.2.2", "", {}, "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q=="], @@ -2442,7 +2450,7 @@ "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], - "rolldown": ["rolldown@1.2.0", "", { "dependencies": { "@oxc-project/types": "=0.140.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.2.0", "@rolldown/binding-darwin-arm64": "1.2.0", "@rolldown/binding-darwin-x64": "1.2.0", "@rolldown/binding-freebsd-x64": "1.2.0", "@rolldown/binding-linux-arm-gnueabihf": "1.2.0", "@rolldown/binding-linux-arm64-gnu": "1.2.0", "@rolldown/binding-linux-arm64-musl": "1.2.0", "@rolldown/binding-linux-ppc64-gnu": "1.2.0", "@rolldown/binding-linux-s390x-gnu": "1.2.0", "@rolldown/binding-linux-x64-gnu": "1.2.0", "@rolldown/binding-linux-x64-musl": "1.2.0", "@rolldown/binding-openharmony-arm64": "1.2.0", "@rolldown/binding-wasm32-wasi": "1.2.0", "@rolldown/binding-win32-arm64-msvc": "1.2.0", "@rolldown/binding-win32-x64-msvc": "1.2.0" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-u7tgm5l4Yw1iTqUL4EcYOAt7fFvCgQMLeidrnD4GALlC6aOznCjezYajgxeyKw27u0Q5N7fwgCzjVyPIWzwuBA=="], + "rolldown": ["rolldown@1.2.4", "", { "dependencies": { "@oxc-project/types": "=0.144.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.2.4", "@rolldown/binding-darwin-arm64": "1.2.4", "@rolldown/binding-darwin-x64": "1.2.4", "@rolldown/binding-freebsd-x64": "1.2.4", "@rolldown/binding-linux-arm-gnueabihf": "1.2.4", "@rolldown/binding-linux-arm64-gnu": "1.2.4", "@rolldown/binding-linux-arm64-musl": "1.2.4", "@rolldown/binding-linux-ppc64-gnu": "1.2.4", "@rolldown/binding-linux-s390x-gnu": "1.2.4", "@rolldown/binding-linux-x64-gnu": "1.2.4", "@rolldown/binding-linux-x64-musl": "1.2.4", "@rolldown/binding-openharmony-arm64": "1.2.4", "@rolldown/binding-win32-arm64-msvc": "1.2.4", "@rolldown/binding-win32-x64-msvc": "1.2.4" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-rSr7irW0K7QRWzjdJXqZowkcRdDtjRduh43rBltnVKd0VFq839l1lJoDvGJb6gl7+4rTTCrPWu+YfujUL8Ug7w=="], "rolldown-plugin-dts": ["rolldown-plugin-dts@0.27.14", "", { "dependencies": { "dts-resolver": "^3.0.0", "get-tsconfig": "5.0.0-beta.5", "obug": "^2.1.4", "yuku-ast": "^0.8.0", "yuku-codegen": "^0.8.0", "yuku-parser": "^0.8.0" }, "peerDependencies": { "@typescript/native-preview": "*", "@volar/typescript": "~2.4.0", "rolldown": "^1.0.0", "typescript": "^5.0.0 || ^6.0.0 || ~7.0.0", "vue-tsc": "~3.2.0 || ~3.3.0" }, "optionalPeers": ["@typescript/native-preview", "@volar/typescript", "typescript", "vue-tsc"] }, "sha512-ZvuDDwoIpRK9RPxDXratCpklFO9QZZWndf/sd0VBFb4LEj0jj07UcHK9OCh7V4XiFz2Z89ziyBC2K6tJiDjrbw=="], @@ -2468,9 +2476,9 @@ "send": ["send@0.19.2", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "~0.5.2", "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "~2.4.1", "range-parser": "~1.2.1", "statuses": "~2.0.2" } }, "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg=="], - "seroval": ["seroval@1.5.6", "", {}, "sha512-rVQVWjjSvlINzaQPZH5JFqsqEsIWdTxY3iJZCnTL/5gQbXIRooVZKI60tVCkOVfzcRPejboxO2t0P89dg5mQaA=="], + "seroval": ["seroval@1.6.2", "", {}, "sha512-mPT+SD2TrlB6wvte1KkYOYUkubaTbd6pZ/6Kk3C9nxzrHmCZyhxOO7XGAeL7f+yLKZglzGtM9odUVvg/EhO+vQ=="], - "seroval-plugins": ["seroval-plugins@1.5.6", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-HXuLAX2pu/UByPpaeo/TaMfvMIi+1QqIoPJYCcAtU8QkVNwgR6MPlGuCQTErV1JwraaMbYaWVIBX7mppzGLATQ=="], + "seroval-plugins": ["seroval-plugins@1.6.2", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-TfxuUjlbBESzUOWdTkTKqvSmav0ABym+itetDXLK6mDz8SmrpdI30aF8RTXE8Bvq+tH/1yIDkvy3W0lfQb1ipQ=="], "serve-index": ["serve-index@1.9.2", "", { "dependencies": { "accepts": "~1.3.8", "batch": "0.6.1", "debug": "2.6.9", "escape-html": "~1.0.3", "http-errors": "~1.8.0", "mime-types": "~2.1.35", "parseurl": "~1.3.3" } }, "sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ=="], @@ -2512,7 +2520,7 @@ "solid-js": ["solid-js@1.9.14", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.5.4", "seroval-plugins": "~1.5.4" } }, "sha512-sAEXC0Kk0S1EDg+8ysEWJDbYhA3RRoEjwuySUGlKIemeo0I5YZfOyumNjNs9Sv3y2nmhD+0rW66ag2HsMuQiGQ=="], - "sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="], + "sonner": ["sonner@2.0.8", "", { "peerDependencies": { "@types/react": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-UM/ByIoFra8yzV75n1o0Puu0bw5U/9UNnDacrJNspekBewIfsQ3D6ez1nvlWpt7aTsO6rujQtifBpycwIivqlg=="], "source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], @@ -2574,7 +2582,7 @@ "terminal-size": ["terminal-size@4.0.1", "", {}, "sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ=="], - "terser": ["terser@5.49.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-SNiDnXyHSrxVcIOtVbULzcTmniUiwcV7Nwdyj1twVubeTmbjoa8p69KKDpfkdoOavuM4/GRm1+ykI8qqnavHoA=="], + "terser": ["terser@5.50.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-CN9BVxWhgS/hRxtUMjtC2uRWSTcSfQFHMDWma6sKKfIivCD91sM+FOPfvwoaRMqCSrUpe1nv3jDamd9eEQ4y+w=="], "thingies": ["thingies@2.6.1", "", { "peerDependencies": { "tslib": "^2" } }, "sha512-cV/CMGTK3M4MlnJ/0At6ismOw/A0EEniDNScajjz/Br3c1sqE72YD01rGpPTKwd27wAxI5Pr+6+0w8yofzFRYw=="], @@ -2588,13 +2596,13 @@ "tinycolor2": ["tinycolor2@1.6.0", "", {}, "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="], - "tinyexec": ["tinyexec@1.2.4", "", {}, "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg=="], + "tinyexec": ["tinyexec@1.3.0", "", {}, "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ=="], "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], "tinygradient": ["tinygradient@1.1.5", "", { "dependencies": { "@types/tinycolor2": "^1.4.0", "tinycolor2": "^1.0.0" } }, "sha512-8nIfc2vgQ4TeLnk2lFj4tRLvvJwEfQuabdsmvDdQPT0xlk9TaNtpGd6nNRxXoK6vQhN6RSzj+Cnp5tTQmpxmbw=="], - "tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="], + "tinyrainbow": ["tinyrainbow@3.1.1", "", {}, "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw=="], "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], @@ -2618,7 +2626,7 @@ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "tsx": ["tsx@4.23.1", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ=="], + "tsx": ["tsx@4.23.12", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-FDf4L4sYzKtzWYhU/Xm0AQFdTjdIxNo9ElTf2mxXM6k8YMHXzYUe4yODVaXP4V9uMFbVg8c0qyBccK2OOxb45Q=="], "tsyringe": ["tsyringe@4.10.0", "", { "dependencies": { "tslib": "^1.9.3" } }, "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw=="], @@ -2662,7 +2670,7 @@ "unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="], - "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + "update-browserslist-db": ["update-browserslist-db@1.3.1", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ=="], "use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="], @@ -2678,15 +2686,15 @@ "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], - "verkit": ["verkit@0.3.1", "", {}, "sha512-w2Eo8LSIIoW7qxNBzT7/17k+bh8plXo7G3dHjEIDqPlnluhzaxr9JX8F28VSYEtDvc1/a3WBDih6xNUZseebXg=="], + "verkit": ["verkit@0.3.2", "", {}, "sha512-zj/ob3UsvJGN0whEAKFp53REA5X66hvffVqoCtVQAakJKnKlH+/PcOfMoFwIG/o4rElqLv/ycAFlx8ZlXUorCg=="], "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], - "viem": ["viem@2.55.16", "", { "dependencies": { "@noble/curves": "1.9.1", "@noble/hashes": "1.8.0", "@scure/bip32": "1.7.0", "@scure/bip39": "1.6.0", "abitype": "1.2.3", "isows": "1.0.7", "ox": "0.14.33", "ws": "8.21.0" }, "peerDependencies": { "typescript": ">=5.0.4" }, "optionalPeers": ["typescript"] }, "sha512-bKs/0PNIkiaG45ZM0ERddHSVtku7hvyebK2i3bMLuyJQevh15tpDh3MiE3PhLjx5kxGrvizaXQw67gWc9Jl7fQ=="], + "viem": ["viem@2.55.17", "", { "dependencies": { "@noble/curves": "1.9.1", "@noble/hashes": "1.8.0", "@scure/bip32": "1.7.0", "@scure/bip39": "1.6.0", "abitype": "1.2.3", "isows": "1.0.7", "ox": "0.14.33", "ws": "8.21.0" }, "peerDependencies": { "typescript": ">=5.0.4" }, "optionalPeers": ["typescript"] }, "sha512-XKASpfG9jznxsU/J7Bj2k9aS0UZgU3sBcwVoQ6slD5T1RmNJHh3khmFVJuRKjrOe3uqHb0FJKbb6jwE49WtVDQ=="], - "vite": ["vite@8.1.5", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.5", "postcss": "^8.5.17", "rolldown": "~1.1.5", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.3.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw=="], + "vite": ["vite@8.2.1", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.5", "postcss": "^8.5.25", "rolldown": "~1.2.1", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.4.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw=="], "vite-tsconfig-paths": ["vite-tsconfig-paths@6.1.1", "", { "dependencies": { "debug": "^4.1.1", "globrex": "^0.1.2", "tsconfck": "^3.0.3" }, "peerDependencies": { "vite": "*" } }, "sha512-2cihq7zliibCCZ8P9cKJrQBkfgdvcFkOOc3Y02o3GWUDLgqjWsZudaoiuOwO/gzTzy17cS5F7ZPo4bsnS4DGkg=="], @@ -2700,7 +2708,7 @@ "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], - "webpack": ["webpack@5.109.0", "", { "dependencies": { "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.16.0", "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.24.2", "es-module-lexer": "^2.1.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "graceful-fs": "^4.2.11", "mime-db": "^1.54.0", "minimizer-webpack-plugin": "^5.6.1", "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", "watchpack": "^2.5.2", "webpack-sources": "^3.5.1" }, "peerDependencies": { "webpack-cli": "*" }, "optionalPeers": ["webpack-cli"], "bin": { "webpack": "bin/webpack.js" } }, "sha512-vomrngskVVXEZF9sMZfYAd4pXZUnfaWdJGlF+BTNF+gJBCKYCQBnOeVPlrh39Ewl7nlCsirDplMy6o5g9xJHBg=="], + "webpack": ["webpack@5.109.2", "", { "dependencies": { "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.16.0", "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.24.4", "es-module-lexer": "^2.1.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "graceful-fs": "^4.2.11", "mime-db": "^1.54.0", "minimizer-webpack-plugin": "^5.6.1", "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", "watchpack": "^2.5.2", "webpack-sources": "^3.5.1" }, "peerDependencies": { "webpack-cli": "*" }, "optionalPeers": ["webpack-cli"], "bin": { "webpack": "bin/webpack.js" } }, "sha512-U9/cvLzxObKNEZ9+TtdqrHM5/9z3lgl2c+c4BzbqGxFQvQvBAq87yql5A8pQ+rrMbS496MZJeF5enVBndIy2hw=="], "webpack-bundle-analyzer": ["webpack-bundle-analyzer@4.10.2", "", { "dependencies": { "@discoveryjs/json-ext": "0.5.7", "acorn": "^8.0.4", "acorn-walk": "^8.0.0", "commander": "^7.2.0", "debounce": "^1.2.1", "escape-string-regexp": "^4.0.0", "gzip-size": "^6.0.0", "html-escaper": "^2.0.2", "opener": "^1.5.2", "picocolors": "^1.0.0", "sirv": "^2.0.3", "ws": "^7.3.1" }, "bin": { "webpack-bundle-analyzer": "lib/bin/analyzer.js" } }, "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw=="], @@ -2744,25 +2752,25 @@ "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="], - "yuku-ast": ["yuku-ast@0.8.0", "", { "dependencies": { "@yuku-toolchain/types": "^0.8.0" } }, "sha512-trBzFsSa6k32vzNUCH6pFhAoTzWD/NifSYOIQ/6v14vXKh7TRhd2vDNIwRguGdXvcyfBNEEGcsfxFHrnJbS+FQ=="], + "yuku-ast": ["yuku-ast@0.8.7", "", { "dependencies": { "@yuku-toolchain/types": "^0.8.7" } }, "sha512-h6+4bDfyootiMB9vckk5uKo5r5j0GHrkr17FQTDNfEsFT3DWlN9uu1HJwQwc64pgmLCI945fWM3lbTIqxjT3GQ=="], - "yuku-codegen": ["yuku-codegen@0.8.0", "", { "dependencies": { "@yuku-toolchain/types": "^0.8.0" }, "optionalDependencies": { "@yuku-codegen/binding-darwin-arm64": "0.8.0", "@yuku-codegen/binding-darwin-x64": "0.8.0", "@yuku-codegen/binding-freebsd-x64": "0.8.0", "@yuku-codegen/binding-linux-arm-gnu": "0.8.0", "@yuku-codegen/binding-linux-arm-musl": "0.8.0", "@yuku-codegen/binding-linux-arm64-gnu": "0.8.0", "@yuku-codegen/binding-linux-arm64-musl": "0.8.0", "@yuku-codegen/binding-linux-x64-gnu": "0.8.0", "@yuku-codegen/binding-linux-x64-musl": "0.8.0", "@yuku-codegen/binding-win32-arm64": "0.8.0", "@yuku-codegen/binding-win32-x64": "0.8.0" } }, "sha512-f82SDo8moLRymtdYN7/cz2yRbWE6Pmbmph+mLj24QkIR8ASG/c2nETdHzBhV/3rR/r8K+qmy7+JqQV3thHAm8g=="], + "yuku-codegen": ["yuku-codegen@0.8.7", "", { "dependencies": { "@yuku-toolchain/types": "^0.8.7" }, "optionalDependencies": { "@yuku-codegen/binding-android-arm64": "0.8.7", "@yuku-codegen/binding-darwin-arm64": "0.8.7", "@yuku-codegen/binding-darwin-x64": "0.8.7", "@yuku-codegen/binding-freebsd-x64": "0.8.7", "@yuku-codegen/binding-linux-arm-gnu": "0.8.7", "@yuku-codegen/binding-linux-arm-musl": "0.8.7", "@yuku-codegen/binding-linux-arm64-gnu": "0.8.7", "@yuku-codegen/binding-linux-arm64-musl": "0.8.7", "@yuku-codegen/binding-linux-x64-gnu": "0.8.7", "@yuku-codegen/binding-linux-x64-musl": "0.8.7", "@yuku-codegen/binding-win32-arm64": "0.8.7", "@yuku-codegen/binding-win32-x64": "0.8.7" } }, "sha512-adwDZSh8oVDzhE6Du9PwVWxcOxeV0e2EVhUuMKWfhSY4wkrDq9eqixlxFF3l/XGUV1E7UFzhpz9393MUumkyNw=="], - "yuku-parser": ["yuku-parser@0.8.0", "", { "dependencies": { "@yuku-toolchain/types": "^0.8.0", "yuku-ast": "^0.8.0" }, "optionalDependencies": { "@yuku-parser/binding-darwin-arm64": "0.8.0", "@yuku-parser/binding-darwin-x64": "0.8.0", "@yuku-parser/binding-freebsd-x64": "0.8.0", "@yuku-parser/binding-linux-arm-gnu": "0.8.0", "@yuku-parser/binding-linux-arm-musl": "0.8.0", "@yuku-parser/binding-linux-arm64-gnu": "0.8.0", "@yuku-parser/binding-linux-arm64-musl": "0.8.0", "@yuku-parser/binding-linux-x64-gnu": "0.8.0", "@yuku-parser/binding-linux-x64-musl": "0.8.0", "@yuku-parser/binding-win32-arm64": "0.8.0", "@yuku-parser/binding-win32-x64": "0.8.0" } }, "sha512-obrazyE8Cyh79xTQS9wv44khnhvkXG1CDSSy0bg+Pjjla+iXkKXK2b6+LTYXSN3qvVfQxc0MN5qhNKYr9sl3Zw=="], + "yuku-parser": ["yuku-parser@0.8.7", "", { "dependencies": { "@yuku-toolchain/types": "^0.8.7", "yuku-ast": "^0.8.7" }, "optionalDependencies": { "@yuku-parser/binding-android-arm64": "0.8.7", "@yuku-parser/binding-darwin-arm64": "0.8.7", "@yuku-parser/binding-darwin-x64": "0.8.7", "@yuku-parser/binding-freebsd-x64": "0.8.7", "@yuku-parser/binding-linux-arm-gnu": "0.8.7", "@yuku-parser/binding-linux-arm-musl": "0.8.7", "@yuku-parser/binding-linux-arm64-gnu": "0.8.7", "@yuku-parser/binding-linux-arm64-musl": "0.8.7", "@yuku-parser/binding-linux-x64-gnu": "0.8.7", "@yuku-parser/binding-linux-x64-musl": "0.8.7", "@yuku-parser/binding-win32-arm64": "0.8.7", "@yuku-parser/binding-win32-x64": "0.8.7" } }, "sha512-vRD9nwt4L3aYpxNqeSC4WqLv58xrXef0Ong1Mc45CTXTIpvLafx7JO05sczmQZwdLEZvywrLOGdNC5+Rp5N1BQ=="], - "zephyr-agent": ["zephyr-agent@1.1.2", "", { "dependencies": { "@toon-format/toon": "^0.9.0", "axios": "^1.15.0", "axios-retry": "^4.5.0", "debug": "^4.3.4", "eventsource": "^4.0.0", "git-url-parse": "^15.0.0", "https-proxy-agent": "^7.0.6", "is-ci": "^4.1.0", "jose": "^5.10.0", "node-persist": "^4.0.1", "open": "^10.1.0", "proper-lockfile": "^4.1.2", "tslib": "^2.8.1", "zephyr-edge-contract": "1.1.2" } }, "sha512-mJlj9VL2P+wzaLge5sF27LrYBP+wVj+5BiNaQIPTsLSiU9Om3CXHXWTR6ss0w4FIdYazrJyAq2KFw3PgKsBDgA=="], + "zephyr-agent": ["zephyr-agent@1.2.2", "", { "dependencies": { "@toon-format/toon": "^0.9.0", "axios": "^1.18.1", "axios-retry": "^4.5.0", "debug": "^4.4.3", "eventsource": "^4.1.0", "git-url-parse": "^16.1.0", "https-proxy-agent": "^7.0.6", "is-ci": "^4.1.0", "jiti": "^2.7.0", "jose": "^5.10.0", "node-persist": "^4.0.4", "open": "^10.2.0", "proper-lockfile": "^4.1.2", "zephyr-edge-contract": "1.2.2" } }, "sha512-lN/QvWcNj/+5amGqp+FbvpOXGF1yFUptpl8kWSkTMbYiMJGX6qL/5E7QRT9wtD9bgPsqq8HsSG+MkZMPNG3jlw=="], - "zephyr-edge-contract": ["zephyr-edge-contract@1.1.2", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-ZeHYPIrDifKutPzTsjPjHP9JIdPqTpyBESeHHZzG8VSzo6UT58tpwUnDDgJtF/ONwresnD/0ypEYxfBxtcrV6Q=="], + "zephyr-edge-contract": ["zephyr-edge-contract@1.2.2", "", {}, "sha512-2/PdLJ8CBFL7t9L047ejL/adhxv7oVC9kTgvckOuk6EauU9Y1CQ88JsnPB4EkKb/fYjZmurtp43E79MhRU5Rjw=="], - "zephyr-rsbuild-plugin": ["zephyr-rsbuild-plugin@1.1.2", "", { "dependencies": { "zephyr-rspack-plugin": "1.1.2" }, "peerDependencies": { "@rsbuild/core": "^1.0.0 || ^2.0.0-0" } }, "sha512-MhXfQ9oCP76LBDDc8Yl21ridsXFMobvTIjjyYbi+4LpOIof8ap+ZLyVtE14S1w4VyDerZNIzenOEJGbvJ5I/9g=="], + "zephyr-rsbuild-plugin": ["zephyr-rsbuild-plugin@1.2.2", "", { "dependencies": { "zephyr-agent": "1.2.2", "zephyr-rspack-plugin": "1.2.2", "zephyr-xpack-internal": "1.2.2" }, "peerDependencies": { "@rsbuild/core": "^1.0.0 || ^2.0.0-0" } }, "sha512-jeKcM16VHCUutttPgXuj6wPeJ9mT/OVEE/vlHPGOqkmu/vEow8Zf1pVKvurgxP63knoS1lFN8XKzuVZ09Ul9KA=="], - "zephyr-rspack-plugin": ["zephyr-rspack-plugin@1.1.2", "", { "dependencies": { "tslib": "^2.8.1", "zephyr-agent": "1.1.2", "zephyr-xpack-internal": "1.1.2" }, "peerDependencies": { "@rspack/core": "^1.0.0 || ^2.0.0-0" } }, "sha512-OFIghyy2GXaLB/RLJCEWmRoXQf0FNvNB2VQJL3mWofk8oNRu1PxYCxCJ3ThAGN2tRJv0sCjMpzqpuHU+h3Kv/A=="], + "zephyr-rspack-plugin": ["zephyr-rspack-plugin@1.2.2", "", { "dependencies": { "zephyr-agent": "1.2.2", "zephyr-xpack-internal": "1.2.2" }, "peerDependencies": { "@rspack/core": "^1.0.0 || ^2.0.0-0" } }, "sha512-W2Mj9LSLL3HPDdP1WzNnSAp4fnQufSBDzsCPDQy+s9fLZm0kLZMxcS29f06E78t05YyvOg+ZrWqB1y/d7VENYw=="], - "zephyr-xpack-internal": ["zephyr-xpack-internal@1.1.2", "", { "dependencies": { "@module-federation/automatic-vendor-federation": "^1.2.1", "tslib": "^2.8.1", "zephyr-agent": "1.1.2", "zephyr-edge-contract": "1.1.2" } }, "sha512-IYs5aMOxGXH3YGv7Z9uW5pH9/r15UNNEg4CdSuWDDTJ7MskMVmHjSYwM57AYxHvMw/loVXOY1PQDVaUxklOUfQ=="], + "zephyr-xpack-internal": ["zephyr-xpack-internal@1.2.2", "", { "dependencies": { "@module-federation/automatic-vendor-federation": "^1.2.1", "zephyr-agent": "1.2.2", "zephyr-edge-contract": "1.2.2" } }, "sha512-SejAjAhoYX2lSTlFVybeZzbcDeVbk299XIQ458qIx57Vy9u01/CIWeBo14qZam1pEcZT2j/3Xio/6wZfIdk3bg=="], "zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], - "zustand": ["zustand@5.0.14", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g=="], + "zustand": ["zustand@5.0.15", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-MpSEjRiBkA9crSYeOUH32rJC7SVqAbm0Fqcqge/bUi2PPoLcBWKOsG+C8mevmpr8TwXHBVkChbbJiyvkE+i/3A=="], "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], @@ -2772,9 +2780,9 @@ "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "@effect/platform-node/ws": ["ws@8.21.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw=="], + "@effect/platform-node/ws": ["ws@8.21.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw=="], - "@effect/platform-node-shared/ws": ["ws@8.21.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw=="], + "@effect/platform-node-shared/ws": ["ws@8.21.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw=="], "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], @@ -2792,7 +2800,7 @@ "@libsql/hrana-client/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], - "@libsql/isomorphic-ws/ws": ["ws@8.21.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw=="], + "@libsql/isomorphic-ws/ws": ["ws@8.21.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw=="], "@manypkg/find-root/@types/node": ["@types/node@12.20.55", "", {}, "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ=="], @@ -2806,26 +2814,58 @@ "@module-federation/cli/jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="], - "@module-federation/dts-plugin/undici": ["undici@7.28.0", "", {}, "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA=="], - "@module-federation/dts-plugin/ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="], - "@module-federation/enhanced/@module-federation/runtime-tools": ["@module-federation/runtime-tools@2.8.0", "", { "dependencies": { "@module-federation/runtime": "2.8.0", "@module-federation/webpack-bundler-runtime": "2.8.0" } }, "sha512-3yOqjdSHXxX4HA3GhlXg3hghGAXW2RJUsnwXCcik2/lTxOHizKI8f3RM+GGCKPxDVqtw43IShe3tA12jNL5A/A=="], + "@module-federation/enhanced/@module-federation/runtime-tools": ["@module-federation/runtime-tools@2.8.2", "", { "dependencies": { "@module-federation/runtime": "2.8.2", "@module-federation/webpack-bundler-runtime": "2.8.2" } }, "sha512-eW/yPvZB2LbpbyPXPTnOeF1ieWl165D9QcPV0y5Bj1QYGDjL2cb+dnzrBp0fmFtJhCYqmAdsVNoYItVa3yuJ3g=="], - "@module-federation/inject-external-runtime-core-plugin/@module-federation/runtime-tools": ["@module-federation/runtime-tools@2.8.0", "", { "dependencies": { "@module-federation/runtime": "2.8.0", "@module-federation/webpack-bundler-runtime": "2.8.0" } }, "sha512-3yOqjdSHXxX4HA3GhlXg3hghGAXW2RJUsnwXCcik2/lTxOHizKI8f3RM+GGCKPxDVqtw43IShe3tA12jNL5A/A=="], + "@module-federation/inject-external-runtime-core-plugin/@module-federation/runtime-tools": ["@module-federation/runtime-tools@2.8.2", "", { "dependencies": { "@module-federation/runtime": "2.8.2", "@module-federation/webpack-bundler-runtime": "2.8.2" } }, "sha512-eW/yPvZB2LbpbyPXPTnOeF1ieWl165D9QcPV0y5Bj1QYGDjL2cb+dnzrBp0fmFtJhCYqmAdsVNoYItVa3yuJ3g=="], - "@module-federation/rspack/@module-federation/runtime-tools": ["@module-federation/runtime-tools@2.8.0", "", { "dependencies": { "@module-federation/runtime": "2.8.0", "@module-federation/webpack-bundler-runtime": "2.8.0" } }, "sha512-3yOqjdSHXxX4HA3GhlXg3hghGAXW2RJUsnwXCcik2/lTxOHizKI8f3RM+GGCKPxDVqtw43IShe3tA12jNL5A/A=="], + "@module-federation/rspack/@module-federation/runtime-tools": ["@module-federation/runtime-tools@2.8.2", "", { "dependencies": { "@module-federation/runtime": "2.8.2", "@module-federation/webpack-bundler-runtime": "2.8.2" } }, "sha512-eW/yPvZB2LbpbyPXPTnOeF1ieWl165D9QcPV0y5Bj1QYGDjL2cb+dnzrBp0fmFtJhCYqmAdsVNoYItVa3yuJ3g=="], "@module-federation/runtime-tools/@module-federation/runtime": ["@module-federation/runtime@0.22.0", "", { "dependencies": { "@module-federation/error-codes": "0.22.0", "@module-federation/runtime-core": "0.22.0", "@module-federation/sdk": "0.22.0" } }, "sha512-38g5iPju2tPC3KHMPxRKmy4k4onNp6ypFPS1eKGsNLUkXgHsPMBFqAjDw96iEcjri91BrahG4XcdyKi97xZzlA=="], "@module-federation/runtime-tools/@module-federation/webpack-bundler-runtime": ["@module-federation/webpack-bundler-runtime@0.22.0", "", { "dependencies": { "@module-federation/runtime": "0.22.0", "@module-federation/sdk": "0.22.0" } }, "sha512-aM8gCqXu+/4wBmJtVeMeeMN5guw3chf+2i6HajKtQv7SJfxV/f4IyNQJUeUQu9HfiAZHjqtMV5Lvq/Lvh8LdyA=="], + "@orpc/client/@orpc/shared": ["@orpc/shared@1.15.0", "", { "dependencies": { "radash": "^12.1.1", "type-fest": "^5.4.4" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0" }, "optionalPeers": ["@opentelemetry/api"] }, "sha512-A3/JE7pQYSrrRm6/WYJxV3GBhpMdJRPM3h47slQtWBUZe9Sao5En5WBc4tISGQNP2emcez6qIvDziSgD2T+img=="], + + "@orpc/client/@orpc/standard-server": ["@orpc/standard-server@1.15.0", "", { "dependencies": { "@orpc/shared": "1.15.0" } }, "sha512-bje/xn6thDqJY/JQ7xoOjmD1KWE4FsyZOPgnPxwqTZLx/r00stRhLuFvk1hDEGj9UlG7NLZEijDVx0wqvcyDzA=="], + + "@orpc/client/@orpc/standard-server-fetch": ["@orpc/standard-server-fetch@1.15.0", "", { "dependencies": { "@orpc/shared": "1.15.0", "@orpc/standard-server": "1.15.0" } }, "sha512-XYVfgmIt71YrPJSI7RKRiNWjcyktPYHpevkjeohGNBP5aSMAUL95quZArAWr+XOPp4U56XRao54I6wFMZa4How=="], + + "@orpc/client/@orpc/standard-server-peer": ["@orpc/standard-server-peer@1.15.0", "", { "dependencies": { "@orpc/shared": "1.15.0", "@orpc/standard-server": "1.15.0" } }, "sha512-fsTN+FrdPkseVx99yRenGJYSp0xOrEk9fODk8oh3zzricNYzlZ8GGgCYtDNgt9vFIR/J05dc+Nk76KhcPBQpLw=="], + + "@orpc/contract/@orpc/client": ["@orpc/client@1.14.3", "", { "dependencies": { "@orpc/shared": "1.14.3", "@orpc/standard-server": "1.14.3", "@orpc/standard-server-fetch": "1.14.3", "@orpc/standard-server-peer": "1.14.3" } }, "sha512-0HzeD/BgPctvFnd6ltjuQvx4/POXo0K01Tee/3whAm3ohXnlGqCfhzR2VMN8zBaGs1SYe6AFzjmGG928Ej3pAg=="], + "@orpc/experimental-publisher/@orpc/client": ["@orpc/client@1.14.2", "", { "dependencies": { "@orpc/shared": "1.14.2", "@orpc/standard-server": "1.14.2", "@orpc/standard-server-fetch": "1.14.2", "@orpc/standard-server-peer": "1.14.2" } }, "sha512-/tFAua/w/mao2kQtJqjoCYEojHrKMisxOCK8qtkMKOUcXVxWMl+QWhP/MykjzFgkFdO9mzKOu1h7vJvpH73EBA=="], "@orpc/experimental-publisher/@orpc/shared": ["@orpc/shared@1.14.2", "", { "dependencies": { "radash": "^12.1.1", "type-fest": "^5.4.4" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0" }, "optionalPeers": ["@opentelemetry/api"] }, "sha512-5YtbVz4yGbJgcyj7CmEv5FAy4xagCS/hP/MMAlHpJKBSlMHuD7FrDO4LQLFUmSkcBcbLhzX8Ll1ziUw3vtPasw=="], "@orpc/experimental-publisher/@orpc/standard-server": ["@orpc/standard-server@1.14.2", "", { "dependencies": { "@orpc/shared": "1.14.2" } }, "sha512-XHySJICwDsJf211gcxtJBpzB1ldZrSHDW2mqbBQg+I2AewvTqWiqeGZV+SPvmq87q4IfBzncSuwMrRKplUJhsw=="], + "@orpc/json-schema/@orpc/openapi": ["@orpc/openapi@1.14.3", "", { "dependencies": { "@orpc/client": "1.14.3", "@orpc/contract": "1.14.3", "@orpc/interop": "1.14.3", "@orpc/openapi-client": "1.14.3", "@orpc/server": "1.14.3", "@orpc/shared": "1.14.3", "@orpc/standard-server": "1.14.3", "json-schema-typed": "^8.0.2", "rou3": "^0.7.12" } }, "sha512-0tZN91VoT6MEkOfw+ERKyozsnDXzmDSsBeMgEHN3Hl1WVU97T9l4aZzLlZILSNl3fat3HmduEDy/boNGmAWJkQ=="], + + "@orpc/openapi/@orpc/contract": ["@orpc/contract@1.15.0", "", { "dependencies": { "@orpc/client": "1.15.0", "@orpc/shared": "1.15.0", "@standard-schema/spec": "^1.1.0", "openapi-types": "^12.1.3" } }, "sha512-zo8+x+5iqnIMrFdcj+ibVgn6dJneWzLNcD3kZQxrJFGFNAFoxvZbwryR5wOdcHkaSCeS3N7Ib2NydU6pihSYmg=="], + + "@orpc/openapi/@orpc/interop": ["@orpc/interop@1.15.0", "", {}, "sha512-Ah48o/rc00rN1yP5HcaoVsDCjdUhoh3JP1VibBbvwijhGvdOwRwiw5RWiGu442GSKcrPt9etAGhWXH88q8C3ww=="], + + "@orpc/openapi/@orpc/server": ["@orpc/server@1.15.0", "", { "dependencies": { "@orpc/client": "1.15.0", "@orpc/contract": "1.15.0", "@orpc/interop": "1.15.0", "@orpc/shared": "1.15.0", "@orpc/standard-server": "1.15.0", "@orpc/standard-server-aws-lambda": "1.15.0", "@orpc/standard-server-fastify": "1.15.0", "@orpc/standard-server-fetch": "1.15.0", "@orpc/standard-server-node": "1.15.0", "@orpc/standard-server-peer": "1.15.0", "cookie": "^1.1.1" }, "peerDependencies": { "crossws": ">=0.3.4", "ws": ">=8.18.1" }, "optionalPeers": ["crossws", "ws"] }, "sha512-l9FnL1MjQ8g77mB10/8D61okuKHLJBWhuwmd6OF65POHzBF177kMCcbHvRFEqUuxSbzP2eNiZGf3kXPMHmIKiQ=="], + + "@orpc/openapi/@orpc/shared": ["@orpc/shared@1.15.0", "", { "dependencies": { "radash": "^12.1.1", "type-fest": "^5.4.4" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0" }, "optionalPeers": ["@opentelemetry/api"] }, "sha512-A3/JE7pQYSrrRm6/WYJxV3GBhpMdJRPM3h47slQtWBUZe9Sao5En5WBc4tISGQNP2emcez6qIvDziSgD2T+img=="], + + "@orpc/openapi/@orpc/standard-server": ["@orpc/standard-server@1.15.0", "", { "dependencies": { "@orpc/shared": "1.15.0" } }, "sha512-bje/xn6thDqJY/JQ7xoOjmD1KWE4FsyZOPgnPxwqTZLx/r00stRhLuFvk1hDEGj9UlG7NLZEijDVx0wqvcyDzA=="], + + "@orpc/openapi-client/@orpc/contract": ["@orpc/contract@1.15.0", "", { "dependencies": { "@orpc/client": "1.15.0", "@orpc/shared": "1.15.0", "@standard-schema/spec": "^1.1.0", "openapi-types": "^12.1.3" } }, "sha512-zo8+x+5iqnIMrFdcj+ibVgn6dJneWzLNcD3kZQxrJFGFNAFoxvZbwryR5wOdcHkaSCeS3N7Ib2NydU6pihSYmg=="], + + "@orpc/openapi-client/@orpc/shared": ["@orpc/shared@1.15.0", "", { "dependencies": { "radash": "^12.1.1", "type-fest": "^5.4.4" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0" }, "optionalPeers": ["@opentelemetry/api"] }, "sha512-A3/JE7pQYSrrRm6/WYJxV3GBhpMdJRPM3h47slQtWBUZe9Sao5En5WBc4tISGQNP2emcez6qIvDziSgD2T+img=="], + + "@orpc/openapi-client/@orpc/standard-server": ["@orpc/standard-server@1.15.0", "", { "dependencies": { "@orpc/shared": "1.15.0" } }, "sha512-bje/xn6thDqJY/JQ7xoOjmD1KWE4FsyZOPgnPxwqTZLx/r00stRhLuFvk1hDEGj9UlG7NLZEijDVx0wqvcyDzA=="], + + "@orpc/server/@orpc/client": ["@orpc/client@1.14.3", "", { "dependencies": { "@orpc/shared": "1.14.3", "@orpc/standard-server": "1.14.3", "@orpc/standard-server-fetch": "1.14.3", "@orpc/standard-server-peer": "1.14.3" } }, "sha512-0HzeD/BgPctvFnd6ltjuQvx4/POXo0K01Tee/3whAm3ohXnlGqCfhzR2VMN8zBaGs1SYe6AFzjmGG928Ej3pAg=="], + + "@orpc/tanstack-query/@orpc/client": ["@orpc/client@1.14.3", "", { "dependencies": { "@orpc/shared": "1.14.3", "@orpc/standard-server": "1.14.3", "@orpc/standard-server-fetch": "1.14.3", "@orpc/standard-server-peer": "1.14.3" } }, "sha512-0HzeD/BgPctvFnd6ltjuQvx4/POXo0K01Tee/3whAm3ohXnlGqCfhzR2VMN8zBaGs1SYe6AFzjmGG928Ej3pAg=="], + + "@orpc/zod/@orpc/openapi": ["@orpc/openapi@1.14.3", "", { "dependencies": { "@orpc/client": "1.14.3", "@orpc/contract": "1.14.3", "@orpc/interop": "1.14.3", "@orpc/openapi-client": "1.14.3", "@orpc/server": "1.14.3", "@orpc/shared": "1.14.3", "@orpc/standard-server": "1.14.3", "json-schema-typed": "^8.0.2", "rou3": "^0.7.12" } }, "sha512-0tZN91VoT6MEkOfw+ERKyozsnDXzmDSsBeMgEHN3Hl1WVU97T9l4aZzLlZILSNl3fat3HmduEDy/boNGmAWJkQ=="], + "@pingpay/onramp-sdk/dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], "@pingpay/onramp-sdk/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], @@ -2834,19 +2874,17 @@ "@quansync/fs/quansync": ["quansync@1.0.0", "", {}, "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA=="], - "@rolldown/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA=="], + "@rspack/dev-server/ws": ["ws@8.21.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw=="], - "@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.6", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg=="], + "@tailwindcss/node/lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], - "@rspack/dev-server/ws": ["ws@8.21.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw=="], - - "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.3", "tslib": "^2.4.0" }, "bundled": true }, "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g=="], - "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.6", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg=="], + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.2.3", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" }, "bundled": true }, "sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q=="], "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="], @@ -2854,11 +2892,11 @@ "@tanstack/devtools-client/@tanstack/devtools-event-client": ["@tanstack/devtools-event-client@0.5.0", "", { "bin": { "intent": "./bin/intent.js" } }, "sha512-H+OH3zC6Vhu/K0NaVfQKknEKawc/+2PT+D3SB3Ox0V8SiMlTo0abbmH2rH0721R2aNYbjdMXA1oENOd8E2UVoA=="], - "@tanstack/devtools-event-bus/ws": ["ws@8.21.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw=="], + "@tanstack/devtools-event-bus/ws": ["ws@8.21.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw=="], - "@tanstack/form-core/@tanstack/store": ["@tanstack/store@0.11.0", "", {}, "sha512-WlzzCt3xi0G6pCAJu1U+2jiECwabETDpQDi3hfkFZvJii9AuZqEKbOiVarX1/bWhTNjU486yQtJCCasi/0q+Cw=="], + "@tanstack/form-core/@tanstack/store": ["@tanstack/store@0.11.1", "", {}, "sha512-mzTOBhypOuDJAy/D8n2MfUZ1HFkXnmSETviRyhqEC8LUE7/IZQExOTxMANj3KjTofYTkFNpBY67qaVrT41YccA=="], - "@tanstack/react-form/@tanstack/react-store": ["@tanstack/react-store@0.11.0", "", { "dependencies": { "@tanstack/store": "0.11.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-tX4YXh3PDkmpvGQWkWqKpzs/MSqbtuwY9dWdWhtV9Q50PmO+jOkUKIWIX4G85dwt7lxdHLXsiaEKPdKmC8F41w=="], + "@tanstack/react-form/@tanstack/react-store": ["@tanstack/react-store@0.11.1", "", { "dependencies": { "@tanstack/store": "0.11.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-HaIGKI3YLmjBYIvy5DFDY23oNaYZIsTZfngey07Uh5iLVJgM3bIGCnZeOFOqzjFld9JHWcaHJnasD/bKoGKwJQ=="], "@tanstack/router-generator/prettier": ["prettier@3.9.6", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g=="], @@ -2896,6 +2934,10 @@ "esrecurse/estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + "every-plugin/@orpc/client": ["@orpc/client@1.14.3", "", { "dependencies": { "@orpc/shared": "1.14.3", "@orpc/standard-server": "1.14.3", "@orpc/standard-server-fetch": "1.14.3", "@orpc/standard-server-peer": "1.14.3" } }, "sha512-0HzeD/BgPctvFnd6ltjuQvx4/POXo0K01Tee/3whAm3ohXnlGqCfhzR2VMN8zBaGs1SYe6AFzjmGG928Ej3pAg=="], + + "every-plugin/@orpc/openapi": ["@orpc/openapi@1.14.3", "", { "dependencies": { "@orpc/client": "1.14.3", "@orpc/contract": "1.14.3", "@orpc/interop": "1.14.3", "@orpc/openapi-client": "1.14.3", "@orpc/server": "1.14.3", "@orpc/shared": "1.14.3", "@orpc/standard-server": "1.14.3", "json-schema-typed": "^8.0.2", "rou3": "^0.7.12" } }, "sha512-0tZN91VoT6MEkOfw+ERKyozsnDXzmDSsBeMgEHN3Hl1WVU97T9l4aZzLlZILSNl3fat3HmduEDy/boNGmAWJkQ=="], + "everything-dev/@types/node": ["@types/node@22.20.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q=="], "everything-dev/vite-tsconfig-paths": ["vite-tsconfig-paths@5.1.4", "", { "dependencies": { "debug": "^4.1.1", "globrex": "^0.1.2", "tsconfck": "^3.0.3" }, "peerDependencies": { "vite": "*" }, "optionalPeers": ["vite"] }, "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w=="], @@ -2906,6 +2948,8 @@ "finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], + "host/@orpc/openapi": ["@orpc/openapi@1.14.3", "", { "dependencies": { "@orpc/client": "1.14.3", "@orpc/contract": "1.14.3", "@orpc/interop": "1.14.3", "@orpc/openapi-client": "1.14.3", "@orpc/server": "1.14.3", "@orpc/shared": "1.14.3", "@orpc/standard-server": "1.14.3", "json-schema-typed": "^8.0.2", "rou3": "^0.7.12" } }, "sha512-0tZN91VoT6MEkOfw+ERKyozsnDXzmDSsBeMgEHN3Hl1WVU97T9l4aZzLlZILSNl3fat3HmduEDy/boNGmAWJkQ=="], + "hpack.js/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], "http-proxy/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="], @@ -2914,7 +2958,7 @@ "ink/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], - "ink/ws": ["ws@8.21.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw=="], + "ink/ws": ["ws@8.21.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw=="], "libsql/detect-libc": ["detect-libc@2.0.2", "", {}, "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="], @@ -2944,7 +2988,7 @@ "playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], - "postcss/nanoid": ["nanoid@3.3.16", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q=="], + "postcss/nanoid": ["nanoid@3.3.18", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w=="], "pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], @@ -2956,7 +3000,7 @@ "raw-body/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], - "read-yaml-file/js-yaml": ["js-yaml@3.15.0", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog=="], + "read-yaml-file/js-yaml": ["js-yaml@3.15.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag=="], "readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], @@ -2976,6 +3020,10 @@ "sockjs/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], + "solid-js/seroval": ["seroval@1.5.6", "", {}, "sha512-rVQVWjjSvlINzaQPZH5JFqsqEsIWdTxY3iJZCnTL/5gQbXIRooVZKI60tVCkOVfzcRPejboxO2t0P89dg5mQaA=="], + + "solid-js/seroval-plugins": ["seroval-plugins@1.5.6", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-HXuLAX2pu/UByPpaeo/TaMfvMIi+1QqIoPJYCcAtU8QkVNwgR6MPlGuCQTErV1JwraaMbYaWVIBX7mppzGLATQ=="], + "source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], "stack-utils/escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="], @@ -2988,6 +3036,8 @@ "tsyringe/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], + "ui/@orpc/client": ["@orpc/client@1.14.3", "", { "dependencies": { "@orpc/shared": "1.14.3", "@orpc/standard-server": "1.14.3", "@orpc/standard-server-fetch": "1.14.3", "@orpc/standard-server-peer": "1.14.3" } }, "sha512-0HzeD/BgPctvFnd6ltjuQvx4/POXo0K01Tee/3whAm3ohXnlGqCfhzR2VMN8zBaGs1SYe6AFzjmGG928Ej3pAg=="], + "unconfig-core/quansync": ["quansync@1.0.0", "", {}, "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA=="], "viem/@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="], @@ -3000,8 +3050,6 @@ "viem/ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="], - "vite/rolldown": ["rolldown@1.1.5", "", { "dependencies": { "@oxc-project/types": "=0.139.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.1.5", "@rolldown/binding-darwin-arm64": "1.1.5", "@rolldown/binding-darwin-x64": "1.1.5", "@rolldown/binding-freebsd-x64": "1.1.5", "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", "@rolldown/binding-linux-arm64-gnu": "1.1.5", "@rolldown/binding-linux-arm64-musl": "1.1.5", "@rolldown/binding-linux-ppc64-gnu": "1.1.5", "@rolldown/binding-linux-s390x-gnu": "1.1.5", "@rolldown/binding-linux-x64-gnu": "1.1.5", "@rolldown/binding-linux-x64-musl": "1.1.5", "@rolldown/binding-openharmony-arm64": "1.1.5", "@rolldown/binding-wasm32-wasi": "1.1.5", "@rolldown/binding-win32-arm64-msvc": "1.1.5", "@rolldown/binding-win32-x64-msvc": "1.1.5" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA=="], - "webpack/schema-utils": ["schema-utils@4.3.3", "", { "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", "ajv-formats": "^2.1.1", "ajv-keywords": "^5.1.0" } }, "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA=="], "webpack/tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="], @@ -3012,7 +3060,7 @@ "webpack-dev-middleware/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], - "webpack-dev-server/ws": ["ws@8.21.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw=="], + "webpack-dev-server/ws": ["ws@8.21.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw=="], "wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], @@ -3086,9 +3134,47 @@ "@orpc/experimental-publisher/@orpc/client/@orpc/standard-server-peer": ["@orpc/standard-server-peer@1.14.2", "", { "dependencies": { "@orpc/shared": "1.14.2", "@orpc/standard-server": "1.14.2" } }, "sha512-uzbgGaxvlZ0IA2lasaLck+yrbR3bKoqJnsZehEdbSm6eWaKln4COvxQJ2PuPow3gA6tklNnGkTfmRmGIHvS/rg=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="], + "@orpc/json-schema/@orpc/openapi/@orpc/client": ["@orpc/client@1.14.3", "", { "dependencies": { "@orpc/shared": "1.14.3", "@orpc/standard-server": "1.14.3", "@orpc/standard-server-fetch": "1.14.3", "@orpc/standard-server-peer": "1.14.3" } }, "sha512-0HzeD/BgPctvFnd6ltjuQvx4/POXo0K01Tee/3whAm3ohXnlGqCfhzR2VMN8zBaGs1SYe6AFzjmGG928Ej3pAg=="], + + "@orpc/json-schema/@orpc/openapi/@orpc/openapi-client": ["@orpc/openapi-client@1.14.3", "", { "dependencies": { "@orpc/client": "1.14.3", "@orpc/contract": "1.14.3", "@orpc/shared": "1.14.3", "@orpc/standard-server": "1.14.3" } }, "sha512-1vp+hi858XDrCwYdhONl15YKNlHtj5F5gI3dq/McRRZ45tZ9/Ma03hxzABOOcayaT1L9nX7gPxhX7l5EuRf2zw=="], + + "@orpc/openapi/@orpc/server/@orpc/standard-server-aws-lambda": ["@orpc/standard-server-aws-lambda@1.15.0", "", { "dependencies": { "@orpc/shared": "1.15.0", "@orpc/standard-server": "1.15.0", "@orpc/standard-server-fetch": "1.15.0", "@orpc/standard-server-node": "1.15.0" } }, "sha512-laFW7C4t1gEV0NIt4EjCVCBU3kFPnj2FObJLQkTH+DkEOzxVy6zyJ1coIncDSeOPxzbf7rG8cs0B2vQQyuROag=="], + + "@orpc/openapi/@orpc/server/@orpc/standard-server-fastify": ["@orpc/standard-server-fastify@1.15.0", "", { "dependencies": { "@orpc/shared": "1.15.0", "@orpc/standard-server": "1.15.0", "@orpc/standard-server-node": "1.15.0" }, "peerDependencies": { "fastify": ">=5.6.1" }, "optionalPeers": ["fastify"] }, "sha512-fcOl2004o3RnZxW7rhLuDCrCKSi/H2eWnyljeeoY+xykcygRf0ONCs2K2bSdxPm6cmvQGAXa9m8VBglG9oERVQ=="], + + "@orpc/openapi/@orpc/server/@orpc/standard-server-fetch": ["@orpc/standard-server-fetch@1.15.0", "", { "dependencies": { "@orpc/shared": "1.15.0", "@orpc/standard-server": "1.15.0" } }, "sha512-XYVfgmIt71YrPJSI7RKRiNWjcyktPYHpevkjeohGNBP5aSMAUL95quZArAWr+XOPp4U56XRao54I6wFMZa4How=="], + + "@orpc/openapi/@orpc/server/@orpc/standard-server-node": ["@orpc/standard-server-node@1.15.0", "", { "dependencies": { "@orpc/shared": "1.15.0", "@orpc/standard-server": "1.15.0", "@orpc/standard-server-fetch": "1.15.0" } }, "sha512-3ye9SIIhYfJckQwyyGQQRHAEF5vW6QDbax7oi1mOQ9UBHaRDH5kcL28mT0JnETrUvgBoDRx+rfCsQaAOB4n3zw=="], - "@tanstack/react-form/@tanstack/react-store/@tanstack/store": ["@tanstack/store@0.11.0", "", {}, "sha512-WlzzCt3xi0G6pCAJu1U+2jiECwabETDpQDi3hfkFZvJii9AuZqEKbOiVarX1/bWhTNjU486yQtJCCasi/0q+Cw=="], + "@orpc/openapi/@orpc/server/@orpc/standard-server-peer": ["@orpc/standard-server-peer@1.15.0", "", { "dependencies": { "@orpc/shared": "1.15.0", "@orpc/standard-server": "1.15.0" } }, "sha512-fsTN+FrdPkseVx99yRenGJYSp0xOrEk9fODk8oh3zzricNYzlZ8GGgCYtDNgt9vFIR/J05dc+Nk76KhcPBQpLw=="], + + "@orpc/zod/@orpc/openapi/@orpc/client": ["@orpc/client@1.14.3", "", { "dependencies": { "@orpc/shared": "1.14.3", "@orpc/standard-server": "1.14.3", "@orpc/standard-server-fetch": "1.14.3", "@orpc/standard-server-peer": "1.14.3" } }, "sha512-0HzeD/BgPctvFnd6ltjuQvx4/POXo0K01Tee/3whAm3ohXnlGqCfhzR2VMN8zBaGs1SYe6AFzjmGG928Ej3pAg=="], + + "@orpc/zod/@orpc/openapi/@orpc/openapi-client": ["@orpc/openapi-client@1.14.3", "", { "dependencies": { "@orpc/client": "1.14.3", "@orpc/contract": "1.14.3", "@orpc/shared": "1.14.3", "@orpc/standard-server": "1.14.3" } }, "sha512-1vp+hi858XDrCwYdhONl15YKNlHtj5F5gI3dq/McRRZ45tZ9/Ma03hxzABOOcayaT1L9nX7gPxhX7l5EuRf2zw=="], + + "@tailwindcss/node/lightningcss/lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], + + "@tailwindcss/node/lightningcss/lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], + + "@tailwindcss/node/lightningcss/lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], + + "@tailwindcss/node/lightningcss/lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], + + "@tailwindcss/node/lightningcss/lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], + + "@tailwindcss/node/lightningcss/lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], + + "@tailwindcss/node/lightningcss/lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], + + "@tailwindcss/node/lightningcss/lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], + + "@tailwindcss/node/lightningcss/lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], + + "@tailwindcss/node/lightningcss/lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], + + "@tailwindcss/node/lightningcss/lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], + + "@tanstack/react-form/@tanstack/react-store/@tanstack/store": ["@tanstack/store@0.11.1", "", {}, "sha512-mzTOBhypOuDJAy/D8n2MfUZ1HFkXnmSETviRyhqEC8LUE7/IZQExOTxMANj3KjTofYTkFNpBY67qaVrT41YccA=="], "@tanstack/zod-form-adapter/@tanstack/form-core/@tanstack/store": ["@tanstack/store@0.7.7", "", {}, "sha512-xa6pTan1bcaqYDS9BDpSiS63qa6EoDkPN9RsRaxHuDdVDNntzq3xNwR5YKTU/V3SkSyC9T4YVOPh2zRQN0nhIQ=="], @@ -3098,7 +3184,7 @@ "body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], - "c12/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], + "c12/chokidar/readdirp": ["readdirp@5.1.1", "", {}, "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA=="], "compression/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], @@ -3154,12 +3240,18 @@ "drizzle-kit/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + "every-plugin/@orpc/openapi/@orpc/openapi-client": ["@orpc/openapi-client@1.14.3", "", { "dependencies": { "@orpc/client": "1.14.3", "@orpc/contract": "1.14.3", "@orpc/shared": "1.14.3", "@orpc/standard-server": "1.14.3" } }, "sha512-1vp+hi858XDrCwYdhONl15YKNlHtj5F5gI3dq/McRRZ45tZ9/Ma03hxzABOOcayaT1L9nX7gPxhX7l5EuRf2zw=="], + "everything-dev/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], "express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], "finalhandler/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + "host/@orpc/openapi/@orpc/client": ["@orpc/client@1.14.3", "", { "dependencies": { "@orpc/shared": "1.14.3", "@orpc/standard-server": "1.14.3", "@orpc/standard-server-fetch": "1.14.3", "@orpc/standard-server-peer": "1.14.3" } }, "sha512-0HzeD/BgPctvFnd6ltjuQvx4/POXo0K01Tee/3whAm3ohXnlGqCfhzR2VMN8zBaGs1SYe6AFzjmGG928Ej3pAg=="], + + "host/@orpc/openapi/@orpc/openapi-client": ["@orpc/openapi-client@1.14.3", "", { "dependencies": { "@orpc/client": "1.14.3", "@orpc/contract": "1.14.3", "@orpc/shared": "1.14.3", "@orpc/standard-server": "1.14.3" } }, "sha512-1vp+hi858XDrCwYdhONl15YKNlHtj5F5gI3dq/McRRZ45tZ9/Ma03hxzABOOcayaT1L9nX7gPxhX7l5EuRf2zw=="], + "hpack.js/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], "hpack.js/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], @@ -3180,50 +3272,12 @@ "serve-index/http-errors/statuses": ["statuses@1.5.0", "", {}, "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA=="], - "string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + "string-width/strip-ansi/ansi-regex": ["ansi-regex@6.3.0", "", {}, "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ=="], "viem/@scure/bip32/@scure/base": ["@scure/base@1.2.6", "", {}, "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg=="], "viem/@scure/bip39/@scure/base": ["@scure/base@1.2.6", "", {}, "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg=="], - "vite/rolldown/@oxc-project/types": ["@oxc-project/types@0.139.0", "", {}, "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw=="], - - "vite/rolldown/@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.1.5", "", { "os": "android", "cpu": "arm64" }, "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ=="], - - "vite/rolldown/@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.1.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw=="], - - "vite/rolldown/@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.1.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g=="], - - "vite/rolldown/@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.1.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA=="], - - "vite/rolldown/@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.1.5", "", { "os": "linux", "cpu": "arm" }, "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw=="], - - "vite/rolldown/@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.1.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q=="], - - "vite/rolldown/@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.1.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA=="], - - "vite/rolldown/@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.1.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg=="], - - "vite/rolldown/@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.1.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA=="], - - "vite/rolldown/@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.1.5", "", { "os": "linux", "cpu": "x64" }, "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ=="], - - "vite/rolldown/@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.1.5", "", { "os": "linux", "cpu": "x64" }, "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg=="], - - "vite/rolldown/@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.1.5", "", { "os": "none", "cpu": "arm64" }, "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw=="], - - "vite/rolldown/@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.1.5", "", { "dependencies": { "@emnapi/core": "1.11.1", "@emnapi/runtime": "1.11.1", "@napi-rs/wasm-runtime": "^1.1.6" }, "cpu": "none" }, "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA=="], - - "vite/rolldown/@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.1.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw=="], - - "vite/rolldown/@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.1.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA=="], - - "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], - - "vite/rolldown/@rolldown/binding-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ=="], - - "vite/rolldown/@rolldown/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="], - - "vite/rolldown/@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.6", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg=="], + "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.3.0", "", {}, "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ=="], } } diff --git a/package.json b/package.json index 3c1d9266..2e44e3b6 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "better-auth": "1.6.25", "@better-auth/api-key": "1.6.25", "@better-auth/passkey": "1.6.25", - "better-near-auth": "1.8.3", + "better-near-auth": "1.10.0", "every-plugin": "^2.10.1", "everything-dev": "^1.53.0", "typescript": "^5.9.3",