diff --git a/bun.lock b/bun.lock index 426b6fa07de11..0497c39308f93 100644 --- a/bun.lock +++ b/bun.lock @@ -747,7 +747,7 @@ "ioredis-mock": "^8.13.1", "prettier": "3.6.2", "typescript": "5.8.3", - "uuid": "13.0.0", + "uuid": "14.0.0", }, }, "packages/cloud-shared": { @@ -1707,6 +1707,7 @@ "@types/three": "^0.184.0", "@vitejs/plugin-react": "^6.0.0", "playwright": "^1.59.1", + "rollup-plugin-visualizer": "^7.0.1", "tailwindcss": "^4", "tw-animate-css": "^1.4.0", "typescript": "^6.0.3", @@ -2620,7 +2621,7 @@ "ox": "0.14.20", "undici": "8.2.0", "viem": "^2.48.8", - "ws": "8.18.3", + "ws": "8.20.1", }, "devDependencies": { "@biomejs/biome": "^2.4.14", @@ -11646,16 +11647,12 @@ "@elizaos/operator/typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], - "@elizaos/operator/uuid": ["uuid@13.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w=="], - "@elizaos/plugin-discord/undici": ["undici@6.24.1", "", {}, "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA=="], "@elizaos/plugin-elizacloud/@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="], "@elizaos/plugin-elizacloud/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], - "@elizaos/plugin-elizacloud/ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], - "@elizaos/plugin-social-alpha/@tailwindcss/postcss": ["@tailwindcss/postcss@4.2.4", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.2.4", "@tailwindcss/oxide": "4.2.4", "postcss": "^8.5.6", "tailwindcss": "4.2.4" } }, "sha512-wgAVj6nUWAolAu8YFvzT2cTBIElWHkjZwFYovF+xsqKsW2ADxM/X2opxj5NsF/qVccAOjRNe8X2IdPzMsWyHTg=="], "@elizaos/plugin-social-alpha/tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="], diff --git a/packages/cloud-frontend/src/RootLayout.tsx b/packages/cloud-frontend/src/RootLayout.tsx index ec173a29b8311..ef5a592e3b4a0 100644 --- a/packages/cloud-frontend/src/RootLayout.tsx +++ b/packages/cloud-frontend/src/RootLayout.tsx @@ -38,7 +38,7 @@ export default function RootLayout() { <> - + Eliza Cloud - Launch Eliza Skip to content diff --git a/packages/cloud-frontend/src/components/chat/model-playground.tsx b/packages/cloud-frontend/src/components/chat/model-playground.tsx index aa5d69abaecd3..1946f9d6b3281 100644 --- a/packages/cloud-frontend/src/components/chat/model-playground.tsx +++ b/packages/cloud-frontend/src/components/chat/model-playground.tsx @@ -179,7 +179,7 @@ export function ModelPlayground() {
-
+
@@ -208,7 +208,7 @@ export function ModelPlayground() { {messages.length === 0 ? (
- + Prompt Lab

@@ -238,7 +238,7 @@ export function ModelPlayground() { className={cn( "max-w-[90%] whitespace-pre-wrap border px-4 py-3 text-sm leading-6 md:max-w-[80%]", message.role === "user" - ? "border-[#FF5800]/20 bg-[#FF5800]/10 text-white" + ? "border-[var(--brand-orange)]/20 bg-[var(--brand-orange)]/10 text-white" : "border-white/10 bg-white/[0.03] text-white/90", )} > @@ -307,7 +307,7 @@ export function ModelPlayground() {

@@ -146,7 +146,7 @@ export function FeedbackModal({
@@ -332,7 +332,7 @@ export function ApiKeysPageClient({ keys, summary }: ApiKeysPageClientProps) { setFormData({ ...formData, description: e.target.value }) } rows={3} - className="rounded-sm border-white/10 bg-black/40 text-white placeholder:text-white/40 focus:ring-1 focus:ring-[#FF5800] focus:border-[#FF5800]" + className="rounded-sm border-white/10 bg-black/40 text-white placeholder:text-white/40 focus:ring-1 focus:ring-[var(--brand-orange)] focus:border-[var(--brand-orange)]" />
@@ -388,7 +388,7 @@ export function ApiKeysPageClient({ keys, summary }: ApiKeysPageClientProps) { ) } > - + @@ -426,7 +426,7 @@ export function ApiKeysPageClient({ keys, summary }: ApiKeysPageClientProps) { } min={100} step={100} - className="rounded-sm border-white/10 bg-black/60 text-white placeholder:text-white/40 focus:ring-1 focus:ring-[#FF5800] focus:border-[#FF5800]" + className="rounded-sm border-white/10 bg-black/60 text-white placeholder:text-white/40 focus:ring-1 focus:ring-[var(--brand-orange)] focus:border-[var(--brand-orange)]" />
)} diff --git a/packages/cloud-frontend/src/dashboard/apps/Page.tsx b/packages/cloud-frontend/src/dashboard/apps/Page.tsx index fec84a0bad123..2ddb8626011b9 100644 --- a/packages/cloud-frontend/src/dashboard/apps/Page.tsx +++ b/packages/cloud-frontend/src/dashboard/apps/Page.tsx @@ -63,7 +63,7 @@ export default function AppsPage() { } + icon={} />

- + Domains

@@ -259,7 +259,7 @@ export function AppDomains({ appId }: AppDomainsProps) { @@ -272,7 +272,7 @@ export function AppEarningsDashboard({ appId }: AppEarningsDashboardProps) {

)}
- +
diff --git a/packages/cloud-frontend/src/dashboard/apps/_components/app-overview.tsx b/packages/cloud-frontend/src/dashboard/apps/_components/app-overview.tsx index 76c025dc787a5..71d832535249c 100644 --- a/packages/cloud-frontend/src/dashboard/apps/_components/app-overview.tsx +++ b/packages/cloud-frontend/src/dashboard/apps/_components/app-overview.tsx @@ -189,9 +189,9 @@ export function AppOverview({ app, showApiKey }: AppOverviewProps) {
{/* New API Key Alert */} {showKey && displayApiKey && ( -
+
- +

Your API Key (shown once) @@ -257,7 +257,7 @@ export function AppOverview({ app, showApiKey }: AppOverviewProps) {

- + API Key

@@ -287,7 +287,7 @@ export function AppOverview({ app, showApiKey }: AppOverviewProps) { Cancel Regenerate @@ -337,7 +337,7 @@ export function AppOverview({ app, showApiKey }: AppOverviewProps) { {/* Basic Info Card */}

- + App Information

@@ -485,7 +485,7 @@ function InfoRow({ href={href} target={href.startsWith("mailto:") ? undefined : "_blank"} rel={href.startsWith("mailto:") ? undefined : "noopener noreferrer"} - className="text-sm text-white hover:text-[#FF5800] transition-colors flex items-center gap-1 mt-0.5" + className="text-sm text-white hover:text-[var(--brand-orange)] transition-colors flex items-center gap-1 mt-0.5" > {icon} {value} diff --git a/packages/cloud-frontend/src/dashboard/apps/_components/app-promote.tsx b/packages/cloud-frontend/src/dashboard/apps/_components/app-promote.tsx index 7e6245e403706..7b39421dd42bb 100644 --- a/packages/cloud-frontend/src/dashboard/apps/_components/app-promote.tsx +++ b/packages/cloud-frontend/src/dashboard/apps/_components/app-promote.tsx @@ -98,7 +98,7 @@ export function AppPromote({ app }: AppPromoteProps) {

- + Promote {app.name}

@@ -108,7 +108,7 @@ export function AppPromote({ app }: AppPromoteProps) { @@ -285,7 +285,7 @@ export function CreateAppDialog({ open, onOpenChange }: CreateAppDialogProps) { diff --git a/packages/cloud-frontend/src/dashboard/settings/_components/tabs/billing-tab.tsx b/packages/cloud-frontend/src/dashboard/settings/_components/tabs/billing-tab.tsx index f6183ad4cec22..5203ebca1f657 100644 --- a/packages/cloud-frontend/src/dashboard/settings/_components/tabs/billing-tab.tsx +++ b/packages/cloud-frontend/src/dashboard/settings/_components/tabs/billing-tab.tsx @@ -214,7 +214,7 @@ export function BillingTab({ user }: BillingTabProps) {

{/* Header */}
-
+

Credit Balance

@@ -256,7 +256,7 @@ export function BillingTab({ user }: BillingTabProps) { }} className={`flex items-center gap-2 px-4 py-2 font-mono text-sm border transition-colors ${ paymentMethod === "card" - ? "bg-[#FF5800] border-[#FF5800] text-white" + ? "bg-[var(--brand-orange)] border-[var(--brand-orange)] text-white" : "bg-transparent border-[rgba(255,255,255,0.2)] text-white/60 hover:border-[rgba(255,255,255,0.4)]" }`} > @@ -270,7 +270,7 @@ export function BillingTab({ user }: BillingTabProps) { }} className={`flex items-center gap-2 px-4 py-2 font-mono text-sm border transition-colors ${ paymentMethod === "crypto" - ? "bg-[#FF5800] border-[#FF5800] text-white" + ? "bg-[var(--brand-orange)] border-[var(--brand-orange)] text-white" : "bg-transparent border-[rgba(255,255,255,0.2)] text-white/60 hover:border-[rgba(255,255,255,0.4)]" }`} > @@ -387,7 +387,7 @@ export function BillingTab({ user }: BillingTabProps) { {/* Header */}
-
+

Invoices

@@ -427,7 +427,7 @@ export function BillingTab({ user }: BillingTabProps) { {/* Table Rows */} {loadingInvoices ? (
- +
) : invoices.length === 0 ? (
diff --git a/packages/cloud-frontend/src/dashboard/settings/_components/tabs/general-tab.tsx b/packages/cloud-frontend/src/dashboard/settings/_components/tabs/general-tab.tsx index 5942dd9f746d6..34c514e38bc21 100644 --- a/packages/cloud-frontend/src/dashboard/settings/_components/tabs/general-tab.tsx +++ b/packages/cloud-frontend/src/dashboard/settings/_components/tabs/general-tab.tsx @@ -225,7 +225,7 @@ export function GeneralTab({ user }: GeneralTabProps) { onCheckedChange={(checked) => updateForm({ responseNotifications: checked }) } - className="data-[state=checked]:bg-[#FF5800] flex-shrink-0" + className="data-[state=checked]:bg-[var(--brand-orange)] flex-shrink-0" />
@@ -245,7 +245,7 @@ export function GeneralTab({ user }: GeneralTabProps) { onCheckedChange={(checked) => updateForm({ emailNotifications: checked }) } - className="data-[state=checked]:bg-[#FF5800] flex-shrink-0" + className="data-[state=checked]:bg-[var(--brand-orange)] flex-shrink-0" />
diff --git a/packages/cloud-frontend/src/dashboard/settings/_components/tabs/organization-tab.tsx b/packages/cloud-frontend/src/dashboard/settings/_components/tabs/organization-tab.tsx index a7049fb446f37..b37e58b931bbe 100644 --- a/packages/cloud-frontend/src/dashboard/settings/_components/tabs/organization-tab.tsx +++ b/packages/cloud-frontend/src/dashboard/settings/_components/tabs/organization-tab.tsx @@ -48,7 +48,7 @@ export function OrganizationTab({ user }: OrganizationTabProps) {
-
+

{user.organization.name}

diff --git a/packages/cloud-frontend/src/pages/auth/callback/email/page.tsx b/packages/cloud-frontend/src/pages/auth/callback/email/page.tsx index c710b08fc3965..f32bae78d826a 100644 --- a/packages/cloud-frontend/src/pages/auth/callback/email/page.tsx +++ b/packages/cloud-frontend/src/pages/auth/callback/email/page.tsx @@ -80,7 +80,7 @@ export default function StewardEmailCallbackPage() { if (status === "error") { return ( -
+

Sign-in failed

@@ -109,7 +109,7 @@ export default function StewardEmailCallbackPage() { return ( - +

Verifying sign-in link...

diff --git a/packages/cloud-frontend/src/pages/auth/error/page.tsx b/packages/cloud-frontend/src/pages/auth/error/page.tsx index d79069b012f65..81b95f22b601e 100644 --- a/packages/cloud-frontend/src/pages/auth/error/page.tsx +++ b/packages/cloud-frontend/src/pages/auth/error/page.tsx @@ -49,7 +49,7 @@ function AuthErrorContent() {
{checkoutError ? ( -

+

{checkoutError}

) : null} @@ -220,7 +220,7 @@ export default function CheckoutPage() { @@ -417,7 +417,7 @@ export default function StewardLoginSection() { type="button" onClick={handlePasskey} disabled={isLoading} - className="flex flex-1 items-center justify-center gap-2 bg-[#FF5800] px-4 py-3 font-semibold text-white transition-colors hover:bg-[#FF5800]/85 disabled:opacity-50" + className="flex flex-1 items-center justify-center gap-2 bg-[var(--brand-orange)] px-4 py-3 font-semibold text-white transition-colors hover:bg-[var(--brand-orange)]/85 disabled:opacity-50" > {loading === "passkey" ? : } Passkey diff --git a/packages/cloud-frontend/src/pages/payment/[paymentRequestId]/page.tsx b/packages/cloud-frontend/src/pages/payment/[paymentRequestId]/page.tsx index 0687ba6d9c8f8..2cdf65512303c 100644 --- a/packages/cloud-frontend/src/pages/payment/[paymentRequestId]/page.tsx +++ b/packages/cloud-frontend/src/pages/payment/[paymentRequestId]/page.tsx @@ -187,7 +187,7 @@ export default function PaymentRequestPage() { >
-
+
diff --git a/packages/cloud-frontend/src/pages/payment/app-charge/[appId]/[chargeId]/page.tsx b/packages/cloud-frontend/src/pages/payment/app-charge/[appId]/[chargeId]/page.tsx index 4f9620e1a634b..baa1265a7955d 100644 --- a/packages/cloud-frontend/src/pages/payment/app-charge/[appId]/[chargeId]/page.tsx +++ b/packages/cloud-frontend/src/pages/payment/app-charge/[appId]/[chargeId]/page.tsx @@ -281,7 +281,7 @@ export default function AppChargePaymentPage() { ? "border-green-300/30 bg-green-400/10 text-green-100" : isExpired ? "border-orange-300/30 bg-orange-400/10 text-orange-100" - : "border-[#0B35F1]/30 bg-[#0B35F1]/10 text-white"; + : "border-[var(--brand-blue)]/30 bg-[var(--brand-blue)]/10 text-white"; const shortId = charge.id.slice(0, 8); return ( @@ -354,7 +354,7 @@ export default function AppChargePaymentPage() { )} {returnedFromPayment && !isPaid && !isExpired && ( -
+
Waiting for confirmation.
diff --git a/packages/cloud-frontend/src/pages/privacy-policy/page.tsx b/packages/cloud-frontend/src/pages/privacy-policy/page.tsx index 69a4224bc69e7..a9f131c9802d2 100644 --- a/packages/cloud-frontend/src/pages/privacy-policy/page.tsx +++ b/packages/cloud-frontend/src/pages/privacy-policy/page.tsx @@ -72,7 +72,7 @@ export default function PrivacyPolicyPage() {
Back to login @@ -101,13 +101,13 @@ export default function PrivacyPolicyPage() {
Terms of Service Return to login diff --git a/packages/cloud-frontend/src/pages/terms-of-service/page.tsx b/packages/cloud-frontend/src/pages/terms-of-service/page.tsx index a011fc7708134..a52350e2e47f5 100644 --- a/packages/cloud-frontend/src/pages/terms-of-service/page.tsx +++ b/packages/cloud-frontend/src/pages/terms-of-service/page.tsx @@ -153,7 +153,7 @@ export default function TermsOfServicePage() {
Back to login @@ -182,13 +182,13 @@ export default function TermsOfServicePage() {
Privacy Policy Return to login diff --git a/packages/cloud-frontend/vite.config.ts b/packages/cloud-frontend/vite.config.ts index 5408e40b2e22a..976c21b0d7fdb 100644 --- a/packages/cloud-frontend/vite.config.ts +++ b/packages/cloud-frontend/vite.config.ts @@ -6,6 +6,7 @@ import react from "@vitejs/plugin-react"; import remarkFrontmatter from "remark-frontmatter"; import remarkGfm from "remark-gfm"; import remarkMdxFrontmatter from "remark-mdx-frontmatter"; +import { visualizer } from "rollup-plugin-visualizer"; import { defineConfig, loadEnv } from "vite"; // Resolve aliases. Aliases that previously pointed at `cloud/packages/{lib,db, @@ -148,6 +149,12 @@ export default defineConfig(({ mode }) => { }, react({ include: /\.(jsx|tsx|mdx)$/ }), tailwindcss(), + visualizer({ + filename: "dist/stats.html", + gzipSize: true, + brotliSize: false, + template: "treemap", + }) as never, // Resolve `@/lib/*`, `@/db/*`, `@/types/*`, `@/components/*` with // local-first / cloud-shared-fallback semantics. Some files (utils.ts, // toast-adapter, hooks, providers, chat-store) moved from cloud-shared @@ -244,6 +251,9 @@ export default defineConfig(({ mode }) => { "react-router", "react-router-dom", "@tanstack/react-query", + "@tanstack/query-core", + "tslib", + "buffer", ], alias: [ // The upstream `inherits` package's main entry tries @@ -410,6 +420,22 @@ export default defineConfig(({ mode }) => { // Keep the build warning budget explicit so import and resolver warnings // still stand out in CI output. chunkSizeWarningLimit: 3000, + // Restrict in the generated index.html to the + // entry's react + core runtime only. Wallet, docs, charts, and other + // route-specific vendor chunks are dynamic-import deps of lazy routes; + // preloading them all on the landing page wastes bandwidth (the prior + // behavior preloaded 22 vendor-wallet chunks before the user navigated + // anywhere). The dynamic-import callsite still triggers preload via + // Vite's __vitePreload helper at navigation time. + modulePreload: { + polyfill: false, + resolveDependencies: (_filename, deps) => + deps.filter( + (dep) => + /vendor-react|rolldown-runtime/.test(dep) || + dep.endsWith(".css"), + ), + }, rolldownOptions: { output: { codeSplitting: { diff --git a/packages/homepage/package.json b/packages/homepage/package.json index d8cc3b21185fe..9005796bc24bf 100644 --- a/packages/homepage/package.json +++ b/packages/homepage/package.json @@ -50,6 +50,7 @@ "@types/three": "^0.184.0", "@vitejs/plugin-react": "^6.0.0", "playwright": "^1.59.1", + "rollup-plugin-visualizer": "^7.0.1", "tailwindcss": "^4", "tw-animate-css": "^1.4.0", "typescript": "^6.0.3", diff --git a/packages/homepage/public/brand/ogembeds/eliza_ogembed.png b/packages/homepage/public/brand/ogembeds/eliza_ogembed.png index 37eee076099c3..012ee27300efd 100644 Binary files a/packages/homepage/public/brand/ogembeds/eliza_ogembed.png and b/packages/homepage/public/brand/ogembeds/eliza_ogembed.png differ diff --git a/packages/homepage/public/brand/ogembeds/elizacloud_ogembed.png b/packages/homepage/public/brand/ogembeds/elizacloud_ogembed.png index 05b4541c89460..36e91b31d5143 100644 Binary files a/packages/homepage/public/brand/ogembeds/elizacloud_ogembed.png and b/packages/homepage/public/brand/ogembeds/elizacloud_ogembed.png differ diff --git a/packages/homepage/public/brand/ogembeds/elizaos_ogembed.png b/packages/homepage/public/brand/ogembeds/elizaos_ogembed.png index 39c7f1349fade..3dd6e29a1602e 100644 Binary files a/packages/homepage/public/brand/ogembeds/elizaos_ogembed.png and b/packages/homepage/public/brand/ogembeds/elizaos_ogembed.png differ diff --git a/packages/homepage/src/App.tsx b/packages/homepage/src/App.tsx index b6e3b0114c8d1..6fe29d7ed2cf6 100644 --- a/packages/homepage/src/App.tsx +++ b/packages/homepage/src/App.tsx @@ -1,4 +1,5 @@ import { BRAND_COLORS } from "@elizaos/shared-brand"; +import { Loader2 } from "lucide-react"; import { lazy, Suspense } from "react"; import { BrowserRouter, Route, Routes } from "react-router-dom"; import { QueryProvider } from "@/components/providers/query-provider"; @@ -15,12 +16,12 @@ function RouteFallback() {
-
Loading…
+
); } diff --git a/packages/homepage/vite.config.ts b/packages/homepage/vite.config.ts index ac1ada5ec3964..0fbd410904c69 100644 --- a/packages/homepage/vite.config.ts +++ b/packages/homepage/vite.config.ts @@ -2,6 +2,7 @@ import fs from "node:fs"; import path from "node:path"; import tailwindcss from "@tailwindcss/vite"; import react from "@vitejs/plugin-react"; +import { visualizer } from "rollup-plugin-visualizer"; import { defineConfig, type Plugin } from "vite"; /** @@ -26,9 +27,27 @@ function gh404Fallback(): Plugin { } export default defineConfig({ - plugins: [react(), tailwindcss(), gh404Fallback()], + plugins: [ + react(), + tailwindcss(), + gh404Fallback(), + visualizer({ + filename: "dist/stats.html", + gzipSize: true, + brotliSize: false, + template: "treemap", + }), + ], resolve: { - dedupe: ["react", "react-dom", "react-router", "react-router-dom"], + dedupe: [ + "react", + "react-dom", + "react-router", + "react-router-dom", + "@react-three/fiber", + "three", + "zod", + ], alias: [ { find: "@", replacement: path.resolve(__dirname, "./src") }, { diff --git a/packages/os-homepage/public/brand/ogembeds/eliza_ogembed.png b/packages/os-homepage/public/brand/ogembeds/eliza_ogembed.png index 37eee076099c3..012ee27300efd 100644 Binary files a/packages/os-homepage/public/brand/ogembeds/eliza_ogembed.png and b/packages/os-homepage/public/brand/ogembeds/eliza_ogembed.png differ diff --git a/packages/os-homepage/public/brand/ogembeds/elizacloud_ogembed.png b/packages/os-homepage/public/brand/ogembeds/elizacloud_ogembed.png index 05b4541c89460..36e91b31d5143 100644 Binary files a/packages/os-homepage/public/brand/ogembeds/elizacloud_ogembed.png and b/packages/os-homepage/public/brand/ogembeds/elizacloud_ogembed.png differ diff --git a/packages/os-homepage/public/brand/ogembeds/elizaos_ogembed.png b/packages/os-homepage/public/brand/ogembeds/elizaos_ogembed.png index 39c7f1349fade..3dd6e29a1602e 100644 Binary files a/packages/os-homepage/public/brand/ogembeds/elizaos_ogembed.png and b/packages/os-homepage/public/brand/ogembeds/elizaos_ogembed.png differ diff --git a/packages/os-homepage/src/App.tsx b/packages/os-homepage/src/App.tsx index 8639fbedcbe89..6e2e0ce696dc9 100644 --- a/packages/os-homepage/src/App.tsx +++ b/packages/os-homepage/src/App.tsx @@ -1,39 +1,33 @@ -import { - StripeCheckoutError, - startStripeCheckout, -} from "@elizaos/checkout-shared"; import { HARDWARE_PRODUCTS as hardwareProducts, type Product, } from "@elizaos/hardware-catalog"; import { - BRAND_COLORS, BRAND_PATHS, EXTERNAL_URLS, LOGO_FILES, } from "@elizaos/shared-brand"; -import { - exchangeStewardCode, - hasStewardAuthedCookie, - readStoredStewardToken, - STEWARD_NONCE_EXCHANGE_ENDPOINT, - STEWARD_SESSION_ENDPOINT, - STEWARD_TENANT_ID, - syncStewardSession, - writeStoredStewardToken, -} from "@elizaos/steward-session-client"; import { CloudVideoBackground } from "@elizaos/ui"; -import { StewardAuth } from "@stwd/sdk"; -import { ArrowRight, CreditCard, Download, ShoppingBag } from "lucide-react"; -import { type ReactNode, useEffect, useMemo, useState } from "react"; +import { ArrowRight, Download, ShoppingBag } from "lucide-react"; +import { lazy, type ReactNode, Suspense, useEffect, useState } from "react"; + +const CheckoutPage = lazy(() => + import("./CheckoutPage").then((module) => ({ default: module.CheckoutPage })), +); +const CheckoutResult = lazy(() => + import("./CheckoutPage").then((module) => ({ + default: module.CheckoutResult, + })), +); +const ProductDetail = lazy(() => + import("./ProductDetail").then((module) => ({ + default: module.ProductDetail, + })), +); const appUrl = EXTERNAL_URLS.app; const cloudUrl = `${EXTERNAL_URLS.cloud}/login?intent=launch`; const checkoutBaseUrl = `${EXTERNAL_URLS.os}/checkout`; -const cloudApiUrl = - import.meta.env.VITE_ELIZA_CLOUD_API_URL || "https://api.elizacloud.ai"; -const stewardApiUrl = `${cloudApiUrl.replace(/\/$/, "")}/steward`; -const stewardTenantId = STEWARD_TENANT_ID; const betaManifestUrl = "/downloads/elizaos-beta-manifest.json"; type ReleaseArtifact = { @@ -101,124 +95,6 @@ const releaseFallback: ReleaseManifest = { ], }; -function productCheckoutUrl(sku: string) { - return `${checkoutBaseUrl}?sku=${sku}`; -} - -function getDefaultProduct(): Product { - const fallback = - hardwareProducts.find((product) => product.sku === "elizaos-usb") ?? - hardwareProducts[0]; - if (!fallback) throw new Error("Hardware catalog is empty"); - return fallback; -} - -function getCheckoutProduct(): Product { - const sku = new URLSearchParams(window.location.search).get("sku"); - return ( - hardwareProducts.find((product) => product.sku === sku) ?? - getDefaultProduct() - ); -} - -function buildCheckoutPath(product: Product) { - return `/checkout?sku=${encodeURIComponent(product.sku)}`; -} - -/** - * Build the redirect_uri we hand to Steward. Kept as a single function so the - * value we send at /authorize time exactly matches the value we send at - * /exchange time — Steward rejects the exchange if they differ. - */ -function buildOAuthRedirectUri(product: Product): string { - return `${window.location.origin}${buildCheckoutPath(product)}`; -} - -function buildOAuthUrl(provider: "google" | "discord" | "github") { - const product = getCheckoutProduct(); - const params = new URLSearchParams({ - redirect_uri: buildOAuthRedirectUri(product), - tenant_id: stewardTenantId, - // Opt into the nonce-exchange flow: Steward redirects back with - // `?code=` (no tokens in the URL) and we trade the code for - // tokens server-side via /api/auth/steward-nonce-exchange. - response_type: "code", - }); - return `${stewardApiUrl}/auth/oauth/${provider}/authorize?${params.toString()}`; -} - -function getStoredStewardToken() { - return readStoredStewardToken(); -} - -const stewardSessionEndpoint = `${cloudApiUrl.replace(/\/$/, "")}${STEWARD_SESSION_ENDPOINT}`; -const stewardNonceExchangeEndpoint = `${cloudApiUrl.replace(/\/$/, "")}${STEWARD_NONCE_EXCHANGE_ENDPOINT}`; - -/** - * Read the one-time OAuth code from `?code=` (nonce-exchange flow). Steward - * redirects to the callback with `?code=` and **no tokens** in the - * URL. We pull the code, strip it from history immediately so it doesn't - * appear in browser history / extension snapshots / shared URLs, and POST it - * server-side. Returns null when no code is present so the caller can fall - * through to the hash / query token fallbacks during the rollout window. - */ -function consumeStewardCodeFromQuery(): string | null { - if (typeof window === "undefined") return null; - const params = new URLSearchParams(window.location.search); - const code = params.get("code"); - if (!code) return null; - params.delete("code"); - const query = params.toString(); - window.history.replaceState( - null, - "", - query ? `${window.location.pathname}?${query}` : window.location.pathname, - ); - return code; -} - -/** - * Parse Steward tokens from the URL hash fragment. The hash never leaves the - * browser — it is not sent to the server, not written to access logs, not - * passed via Referer, and not stored in browser history beyond what the SPA - * sees on first paint. Strips the hash from `location` immediately after - * reading so it cannot be re-read or copy-pasted out of the address bar. - * - * Returns null when no `#token=` is present so the caller can fall through to - * the legacy `?token=` query parser during the rollout window. - */ -function consumeStewardTokensFromHash(): { - token: string; - refreshToken: string | null; -} | null { - // The inline pre-init script in index.html snapshots and removes any - // `#token=...` fragment before React mounts and stores it on - // window.__stewardOAuthHash. Prefer that so we never depend on the - // fragment still being in `location.hash` by the time React boots - // (analytics, Sentry, etc. may have already read `location.href`). - const stewardWindow = window as Window & { __stewardOAuthHash?: string }; - const snapshotted = stewardWindow.__stewardOAuthHash; - const hash = snapshotted || window.location.hash; - if (snapshotted) { - delete stewardWindow.__stewardOAuthHash; - } - if (!hash || hash.length < 2) return null; - const params = new URLSearchParams(hash.replace(/^#/, "")); - const token = params.get("token"); - if (!token) return null; - const refreshToken = params.get("refreshToken"); - if (!snapshotted) { - // Fallback path (legacy browsers without the inline script): strip the - // hash now. `replaceState` keeps pathname + search intact. - window.history.replaceState( - null, - "", - `${window.location.pathname}${window.location.search}`, - ); - } - return { token, refreshToken }; -} - function ProductImage({ product, priority = false, @@ -399,346 +275,6 @@ function Footer() { ); } -function ProductDetail({ product }: { product: Product }) { - return ( -
-
-
-
-
-
- - Hardware - -

{product.name}

-

{product.summary}

-

{product.detail}

-
- {product.price ? {product.price} : null} - {product.ships ? {product.ships} : null} -
- -

Checkout stays on elizaos.ai.

-
- -
-
-
-
-
- ); -} - -function CheckoutResult({ - success, - canceled, -}: { - success?: boolean; - canceled?: boolean; -}) { - return ( -
-
-
-
-
-

{success ? "Pre-order received." : "Checkout canceled."}

-

- {success - ? "Your ElizaOS hardware order is connected to your Eliza Cloud account." - : "No payment was completed. You can return to the store when ready."} -

- - {canceled ? "Return to hardware" : "Back to elizaOS"} - -
-
-
-
-
- ); -} - -function CheckoutPage() { - const [product, setProduct] = useState(getCheckoutProduct); - const [selectedColor, setSelectedColor] = useState(product.colors[0]); - const [email, setEmail] = useState(""); - const [status, setStatus] = useState< - "idle" | "syncing" | "email-sent" | "checkout" - >("idle"); - const [isAuthed, setIsAuthed] = useState(false); - const [message, setMessage] = useState(null); - const auth = useMemo( - () => - new StewardAuth({ baseUrl: stewardApiUrl, tenantId: stewardTenantId }), - [], - ); - - useEffect(() => { - // Preferred path: server-side nonce exchange. Steward redirects to - // `?code=` (no tokens in the URL at all). We POST the code to the - // cloud-api server-side exchange route, which calls Steward - // `/auth/oauth/exchange` and sets HttpOnly cookies. Access and refresh - // tokens never enter this process. - const code = consumeStewardCodeFromQuery(); - if (code) { - setStatus("syncing"); - exchangeStewardCode(code, { - endpoint: stewardNonceExchangeEndpoint, - redirectUri: buildOAuthRedirectUri(product), - tenantId: stewardTenantId, - }) - .then(() => { - setIsAuthed(true); - }) - .catch((error: unknown) => { - setMessage( - error instanceof Error - ? error.message - : "Could not complete Eliza Cloud sign-in.", - ); - }) - .finally(() => setStatus("idle")); - return; - } - - // Fallback (one-release rollout window): tokens in the URL hash - // (#token=...). Hash never leaves the browser, but the tokens still - // touch JS — preferred only until all consumers have moved to the - // nonce-exchange flow above. Legacy `?token=` query also accepted. - const fromHash = consumeStewardTokensFromHash(); - const params = new URLSearchParams(window.location.search); - const queryToken = params.get("token"); - const queryRefreshToken = params.get("refreshToken"); - const token = fromHash?.token ?? queryToken; - const refreshToken = - fromHash?.refreshToken ?? queryRefreshToken ?? undefined; - if (!token) { - setIsAuthed(Boolean(getStoredStewardToken()) || hasStewardAuthedCookie()); - return; - } - - setStatus("syncing"); - writeStoredStewardToken(token); - // Refresh token is forwarded to the server only so it can be set as the - // HttpOnly steward-refresh-token cookie — it is NOT persisted in - // localStorage (XSS-reachable). The HttpOnly cookie is the only - // persistence after first login. - - syncStewardSession(token, refreshToken ?? null, { - endpoint: stewardSessionEndpoint, - }) - .then(() => { - setIsAuthed(true); - if (queryToken || queryRefreshToken) { - params.delete("token"); - params.delete("refreshToken"); - const query = params.toString(); - window.history.replaceState( - null, - "", - query - ? `${window.location.pathname}?${query}` - : window.location.pathname, - ); - } - }) - .catch((error: unknown) => { - setMessage( - error instanceof Error - ? error.message - : "Could not sync Eliza Cloud session.", - ); - }) - .finally(() => setStatus("idle")); - }, [product]); - - useEffect(() => { - setSelectedColor(product.colors[0]); - }, [product]); - - async function sendMagicLink() { - if (!email.trim()) { - setMessage("Enter your email first."); - return; - } - setStatus("syncing"); - setMessage(null); - try { - await auth.signInWithEmail(email.trim()); - setStatus("email-sent"); - } catch (error) { - setStatus("idle"); - setMessage( - error instanceof Error ? error.message : "Could not send magic link.", - ); - } - } - - async function beginCheckout() { - setStatus("checkout"); - setMessage(null); - try { - await startStripeCheckout( - { - hardwareColor: selectedColor.name, - hardwareSku: product.sku, - returnUrl: "billing", - }, - { - apiBaseUrl: cloudApiUrl, - bearerToken: getStoredStewardToken(), - }, - ); - } catch (error) { - setStatus("idle"); - if (error instanceof StripeCheckoutError && error.status === 401) { - setIsAuthed(false); - } - setMessage( - error instanceof Error ? error.message : "Could not start checkout.", - ); - } - } - - return ( -
-
-
-
-
-
-

Pre-order

-

{product.name}

-

{product.detail}

-
- {product.price ? {product.price} : null} - {product.ships ? {product.ships} : null} -
-
-
- -
-
-
- -
-
-
-

Checkout on elizaOS.

-

- Login, customer records, credits, and Stripe payments are - provided by Eliza Cloud. -

-
-
-
- {hardwareProducts.map((item) => ( - - ))} -
- -
- {product.colors.map((color) => ( -
- - {isAuthed ? ( - - ) : ( -
-
- setEmail(event.target.value)} - placeholder="you@example.com" - type="email" - /> - -
- -
- )} - {status === "email-sent" ? ( -

Check your inbox.

- ) : null} - {message ?

{message}

: null} -
-
-
-
-
-
- ); -} - function HomePage() { return (
@@ -782,15 +318,55 @@ function HomePage() { ); } +function RouteFallback() { + return ( +
+
+ +
+ ); +} + export function App() { if (window.location.pathname === "/checkout/success") { - return ; + return ( + }> + + + ); } if (window.location.pathname === "/checkout/cancel") { - return ; + return ( + }> + + + ); } if (window.location.pathname === "/checkout") { - return ; + return ( + }> + + + ); } const match = window.location.pathname.match(/^\/hardware\/([^/]+)\/?$/); @@ -799,7 +375,11 @@ export function App() { : undefined; if (match && product) { - return ; + return ( + }> + + + ); } return ; diff --git a/packages/os-homepage/src/CheckoutPage.tsx b/packages/os-homepage/src/CheckoutPage.tsx new file mode 100644 index 0000000000000..b00fb60543a22 --- /dev/null +++ b/packages/os-homepage/src/CheckoutPage.tsx @@ -0,0 +1,457 @@ +import { + StripeCheckoutError, + startStripeCheckout, +} from "@elizaos/checkout-shared"; +import { + HARDWARE_PRODUCTS as hardwareProducts, + type Product, +} from "@elizaos/hardware-catalog"; +import { BRAND_COLORS, BRAND_PATHS, LOGO_FILES } from "@elizaos/shared-brand"; +import { + exchangeStewardCode, + hasStewardAuthedCookie, + readStoredStewardToken, + STEWARD_NONCE_EXCHANGE_ENDPOINT, + STEWARD_SESSION_ENDPOINT, + STEWARD_TENANT_ID, + syncStewardSession, + writeStoredStewardToken, +} from "@elizaos/steward-session-client"; +import { StewardAuth } from "@stwd/sdk"; +import { CreditCard } from "lucide-react"; +import { useEffect, useMemo, useState } from "react"; + +const cloudApiUrl = + import.meta.env.VITE_ELIZA_CLOUD_API_URL || "https://api.elizacloud.ai"; +const stewardApiUrl = `${cloudApiUrl.replace(/\/$/, "")}/steward`; +const stewardTenantId = STEWARD_TENANT_ID; +const stewardSessionEndpoint = `${cloudApiUrl.replace(/\/$/, "")}${STEWARD_SESSION_ENDPOINT}`; +const stewardNonceExchangeEndpoint = `${cloudApiUrl.replace(/\/$/, "")}${STEWARD_NONCE_EXCHANGE_ENDPOINT}`; + +function getDefaultProduct(): Product { + const fallback = + hardwareProducts.find((product) => product.sku === "elizaos-usb") ?? + hardwareProducts[0]; + if (!fallback) throw new Error("Hardware catalog is empty"); + return fallback; +} + +function getCheckoutProduct(): Product { + const sku = new URLSearchParams(window.location.search).get("sku"); + return ( + hardwareProducts.find((product) => product.sku === sku) ?? + getDefaultProduct() + ); +} + +function buildCheckoutPath(product: Product) { + return `/checkout?sku=${encodeURIComponent(product.sku)}`; +} + +function buildOAuthRedirectUri(product: Product): string { + return `${window.location.origin}${buildCheckoutPath(product)}`; +} + +function buildOAuthUrl(provider: "google" | "discord" | "github") { + const product = getCheckoutProduct(); + const params = new URLSearchParams({ + redirect_uri: buildOAuthRedirectUri(product), + tenant_id: stewardTenantId, + response_type: "code", + }); + return `${stewardApiUrl}/auth/oauth/${provider}/authorize?${params.toString()}`; +} + +function getStoredStewardToken() { + return readStoredStewardToken(); +} + +function consumeStewardCodeFromQuery(): string | null { + if (typeof window === "undefined") return null; + const params = new URLSearchParams(window.location.search); + const code = params.get("code"); + if (!code) return null; + params.delete("code"); + const query = params.toString(); + window.history.replaceState( + null, + "", + query ? `${window.location.pathname}?${query}` : window.location.pathname, + ); + return code; +} + +function consumeStewardTokensFromHash(): { + token: string; + refreshToken: string | null; +} | null { + const stewardWindow = window as Window & { __stewardOAuthHash?: string }; + const snapshotted = stewardWindow.__stewardOAuthHash; + const hash = snapshotted || window.location.hash; + if (snapshotted) { + delete stewardWindow.__stewardOAuthHash; + } + if (!hash || hash.length < 2) return null; + const params = new URLSearchParams(hash.replace(/^#/, "")); + const token = params.get("token"); + if (!token) return null; + const refreshToken = params.get("refreshToken"); + if (!snapshotted) { + window.history.replaceState( + null, + "", + `${window.location.pathname}${window.location.search}`, + ); + } + return { token, refreshToken }; +} + +function ProductImage({ + product, + priority = false, +}: { + product: Product; + priority?: boolean; +}) { + return ( + {product.imageAlt} + ); +} + +function Header() { + return ( +
+ + Skip to content + + + elizaOS + + +
+ ); +} + +function Footer() { + return ( + + ); +} + +export function CheckoutResult({ + success, + canceled, +}: { + success?: boolean; + canceled?: boolean; +}) { + return ( +
+
+
+
+
+

{success ? "Pre-order received." : "Checkout canceled."}

+

+ {success + ? "Your ElizaOS hardware order is connected to your Eliza Cloud account." + : "No payment was completed. You can return to the store when ready."} +

+ + {canceled ? "Return to hardware" : "Back to elizaOS"} + +
+
+
+
+
+ ); +} + +export function CheckoutPage() { + const [product, setProduct] = useState(getCheckoutProduct); + const [selectedColor, setSelectedColor] = useState(product.colors[0]); + const [email, setEmail] = useState(""); + const [status, setStatus] = useState< + "idle" | "syncing" | "email-sent" | "checkout" + >("idle"); + const [isAuthed, setIsAuthed] = useState(false); + const [message, setMessage] = useState(null); + const auth = useMemo( + () => + new StewardAuth({ baseUrl: stewardApiUrl, tenantId: stewardTenantId }), + [], + ); + + useEffect(() => { + const code = consumeStewardCodeFromQuery(); + if (code) { + setStatus("syncing"); + exchangeStewardCode(code, { + endpoint: stewardNonceExchangeEndpoint, + redirectUri: buildOAuthRedirectUri(product), + tenantId: stewardTenantId, + }) + .then(() => { + setIsAuthed(true); + }) + .catch((error: unknown) => { + setMessage( + error instanceof Error + ? error.message + : "Could not complete Eliza Cloud sign-in.", + ); + }) + .finally(() => setStatus("idle")); + return; + } + + const fromHash = consumeStewardTokensFromHash(); + const params = new URLSearchParams(window.location.search); + const queryToken = params.get("token"); + const queryRefreshToken = params.get("refreshToken"); + const token = fromHash?.token ?? queryToken; + const refreshToken = + fromHash?.refreshToken ?? queryRefreshToken ?? undefined; + if (!token) { + setIsAuthed(Boolean(getStoredStewardToken()) || hasStewardAuthedCookie()); + return; + } + + setStatus("syncing"); + writeStoredStewardToken(token); + + syncStewardSession(token, refreshToken ?? null, { + endpoint: stewardSessionEndpoint, + }) + .then(() => { + setIsAuthed(true); + if (queryToken || queryRefreshToken) { + params.delete("token"); + params.delete("refreshToken"); + const query = params.toString(); + window.history.replaceState( + null, + "", + query + ? `${window.location.pathname}?${query}` + : window.location.pathname, + ); + } + }) + .catch((error: unknown) => { + setMessage( + error instanceof Error + ? error.message + : "Could not sync Eliza Cloud session.", + ); + }) + .finally(() => setStatus("idle")); + }, [product]); + + useEffect(() => { + setSelectedColor(product.colors[0]); + }, [product]); + + async function sendMagicLink() { + if (!email.trim()) { + setMessage("Enter your email first."); + return; + } + setStatus("syncing"); + setMessage(null); + try { + await auth.signInWithEmail(email.trim()); + setStatus("email-sent"); + } catch (error) { + setStatus("idle"); + setMessage( + error instanceof Error ? error.message : "Could not send magic link.", + ); + } + } + + async function beginCheckout() { + setStatus("checkout"); + setMessage(null); + try { + await startStripeCheckout( + { + hardwareColor: selectedColor.name, + hardwareSku: product.sku, + returnUrl: "billing", + }, + { + apiBaseUrl: cloudApiUrl, + bearerToken: getStoredStewardToken(), + }, + ); + } catch (error) { + setStatus("idle"); + if (error instanceof StripeCheckoutError && error.status === 401) { + setIsAuthed(false); + } + setMessage( + error instanceof Error ? error.message : "Could not start checkout.", + ); + } + } + + return ( +
+
+
+
+
+
+

Pre-order

+

{product.name}

+

{product.detail}

+
+ {product.price ? {product.price} : null} + {product.ships ? {product.ships} : null} +
+
+
+ +
+
+
+ +
+
+
+

Checkout on elizaOS.

+

+ Login, customer records, credits, and Stripe payments are + provided by Eliza Cloud. +

+
+
+
+ {hardwareProducts.map((item) => ( + + ))} +
+ +
+ {product.colors.map((color) => ( +
+ + {isAuthed ? ( + + ) : ( +
+
+ setEmail(event.target.value)} + placeholder="you@example.com" + type="email" + /> + +
+ +
+ )} + {status === "email-sent" ? ( +

Check your inbox.

+ ) : null} + {message ?

{message}

: null} +
+
+
+
+
+
+ ); +} + +export default CheckoutPage; diff --git a/packages/os-homepage/src/ProductDetail.tsx b/packages/os-homepage/src/ProductDetail.tsx new file mode 100644 index 0000000000000..335e439a1663b --- /dev/null +++ b/packages/os-homepage/src/ProductDetail.tsx @@ -0,0 +1,117 @@ +import type { Product } from "@elizaos/hardware-catalog"; +import { + BRAND_PATHS, + EXTERNAL_URLS, + LOGO_FILES, +} from "@elizaos/shared-brand"; +import { ArrowRight, Download } from "lucide-react"; + +const appUrl = EXTERNAL_URLS.app; +const cloudUrl = `${EXTERNAL_URLS.cloud}/login?intent=launch`; +const checkoutBaseUrl = `${EXTERNAL_URLS.os}/checkout`; +const betaManifestUrl = "/downloads/elizaos-beta-manifest.json"; + +function productCheckoutUrl(sku: string) { + return `${checkoutBaseUrl}?sku=${sku}`; +} + +function ProductImage({ + product, + priority = false, +}: { + product: Product; + priority?: boolean; +}) { + return ( + {product.imageAlt} + ); +} + +function Header() { + return ( +
+ + Skip to content + + + elizaOS + + +
+ ); +} + +function Footer() { + return ( + + ); +} + +export function ProductDetail({ product }: { product: Product }) { + return ( +
+
+
+
+
+
+ + Hardware + +

{product.name}

+

{product.summary}

+

{product.detail}

+
+ {product.price ? {product.price} : null} + {product.ships ? {product.ships} : null} +
+ +

Checkout stays on elizaos.ai.

+
+ +
+
+
+
+
+ ); +} + +export default ProductDetail; diff --git a/packages/os-homepage/vite.config.ts b/packages/os-homepage/vite.config.ts index 5287b155be648..5a49cdbd51486 100644 --- a/packages/os-homepage/vite.config.ts +++ b/packages/os-homepage/vite.config.ts @@ -3,6 +3,7 @@ import path from "node:path"; import { fileURLToPath } from "node:url"; import tailwindcss from "@tailwindcss/vite"; import react from "@vitejs/plugin-react"; +import { visualizer } from "rollup-plugin-visualizer"; import { defineConfig, type Plugin } from "vite"; const packageDir = path.dirname(fileURLToPath(import.meta.url)); @@ -58,9 +59,19 @@ function pruneStaticAssets(): Plugin { } export default defineConfig({ - plugins: [react(), tailwindcss(), spa404Fallback(), pruneStaticAssets()], + plugins: [ + react(), + tailwindcss(), + spa404Fallback(), + pruneStaticAssets(), + visualizer({ + filename: "dist/stats.html", + gzipSize: true, + brotliSize: true, + }) as Plugin, + ], resolve: { - dedupe: ["react", "react-dom", "react-router", "react-router-dom"], + dedupe: ["react", "react-dom", "react-router", "react-router-dom", "zod"], alias: { "@": path.resolve(packageDir, "./src"), "@elizaos/ui": path.resolve( diff --git a/plugins/plugin-wallet/build.ts b/plugins/plugin-wallet/build.ts index b967cd3848e81..0ecd76ce3f676 100644 --- a/plugins/plugin-wallet/build.ts +++ b/plugins/plugin-wallet/build.ts @@ -2,16 +2,24 @@ import { renameSync, rmSync } from "node:fs"; import { $ } from "bun"; -// Externals: -// - All @elizaos/* workspace packages stay external so we don't double-bundle them. -// - `undici` must be external. Bundling undici 8.x produces a `CacheStorage` -// constructor that calls Node's internal `webidl.util.markAsUncloneable`, -// which is not present in Bun. Bun's native fetch/WebSocket cover the call -// sites, so importing undici from the runtime works; bundling it does not. -// Some workspace deps (notably @elizaos/plugin-elizacloud) end up inlined -// here via Bun.build's workspace resolution despite the @elizaos/* regex, -// which is how undici reaches this bundle in the first place. -const external = [/^@elizaos\//, "undici"]; +type PackageJson = { + dependencies?: Record; + peerDependencies?: Record; +}; + +async function externalsFromPackageJson( + pkgJsonPath: string, +): Promise { + const pkg = (await Bun.file(pkgJsonPath).json()) as PackageJson; + const deps = Object.keys(pkg.dependencies ?? {}); + const peers = Object.keys(pkg.peerDependencies ?? {}); + return [...new Set([...deps, ...peers])]; +} + +// Externalize anything in dependencies + peerDependencies so transitive Node-internal API users (undici, ws, etc.) aren't inlined. +// Workspace `@elizaos/*` packages are already listed in package.json, so the +// previous `/^@elizaos\//` regex is redundant once we read from package.json. +const external = await externalsFromPackageJson("./package.json"); console.log("🔨 Building @elizaos/plugin-wallet..."); const start = Date.now();