diff --git a/bin/init.js b/bin/init.js index 3968b60..167969a 100644 --- a/bin/init.js +++ b/bin/init.js @@ -388,10 +388,9 @@ export async function init(projectName = null, autoAcceptDefaults = false) { packageJson.dependencies = { "@farcaster/auth-client": ">=0.3.0 <1.0.0", "@farcaster/auth-kit": ">=0.6.0 <1.0.0", - "@farcaster/frame-core": ">=0.0.29 <1.0.0", - "@farcaster/frame-node": ">=0.0.18 <1.0.0", - "@farcaster/frame-sdk": ">=0.0.31 <1.0.0", - "@farcaster/frame-wagmi-connector": ">=0.0.19 <1.0.0", + "@farcaster/miniapp-node": ">=0.1.5 <1.0.0", + "@farcaster/miniapp-sdk": ">=0.1.6 <1.0.0", + "@farcaster/miniapp-wagmi-connector": "^1.0.0", "@farcaster/mini-app-solana": ">=0.0.17 <1.0.0", "@neynar/react": "^1.2.5", "@radix-ui/react-label": "^2.1.1", diff --git a/package.json b/package.json index 3666086..b5a2f78 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@neynar/create-farcaster-mini-app", - "version": "1.5.3", + "version": "1.5.6", "type": "module", "private": false, "access": "public", diff --git a/scripts/cleanup.js b/scripts/cleanup.js index aed5bca..ed3d3ed 100644 --- a/scripts/cleanup.js +++ b/scripts/cleanup.js @@ -1,6 +1,6 @@ #!/usr/bin/env node -const { execSync } = require('child_process'); +import { execSync } from 'child_process'; // Parse arguments const args = process.argv.slice(2); diff --git a/scripts/deploy.js b/scripts/deploy.js index dbb0dcb..23d0d77 100755 --- a/scripts/deploy.js +++ b/scripts/deploy.js @@ -409,6 +409,45 @@ async function setEnvironmentVariables( return results; } +async function waitForDeployment(vercelClient, projectId, maxWaitTime = 300000) { // 5 minutes + console.log('\nā³ Waiting for deployment to complete...'); + const startTime = Date.now(); + + while (Date.now() - startTime < maxWaitTime) { + try { + const deployments = await vercelClient.deployments.list({ + projectId: projectId, + limit: 1 + }); + + if (deployments.deployments?.[0]) { + const deployment = deployments.deployments[0]; + console.log(`šŸ“Š Deployment status: ${deployment.state}`); + + if (deployment.state === 'READY') { + console.log('āœ… Deployment completed successfully!'); + return deployment; + } else if (deployment.state === 'ERROR') { + throw new Error(`Deployment failed with state: ${deployment.state}`); + } else if (deployment.state === 'CANCELED') { + throw new Error('Deployment was canceled'); + } + + // Still building, wait and check again + await new Promise(resolve => setTimeout(resolve, 5000)); // Wait 5 seconds + } else { + console.log('ā³ No deployment found yet, waiting...'); + await new Promise(resolve => setTimeout(resolve, 5000)); + } + } catch (error) { + console.warn('āš ļø Could not check deployment status:', error.message); + await new Promise(resolve => setTimeout(resolve, 5000)); + } + } + + throw new Error('Deployment timed out after 5 minutes'); +} + async function deployToVercel(useGitHub = false) { try { console.log("\nšŸš€ Deploying to Vercel..."); @@ -431,25 +470,46 @@ async function deployToVercel(useGitHub = false) { } // Set up Vercel project - console.log("\nšŸ“¦ Setting up Vercel project..."); - console.log( - "An initial deployment is required to get an assigned domain that can be used in the mini app manifest\n" - ); - console.log( - "\nāš ļø Note: choosing a longer, more unique project name will help avoid conflicts with other existing domains\n" - ); - - execSync("vercel", { + console.log('\nšŸ“¦ Setting up Vercel project...'); + console.log('An initial deployment is required to get an assigned domain that can be used in the mini app manifest\n'); + console.log('\nāš ļø Note: choosing a longer, more unique project name will help avoid conflicts with other existing domains\n'); + + // Use spawn instead of execSync for better error handling + const { spawn } = await import('child_process'); + const vercelSetup = spawn('vercel', [], { cwd: projectRoot, stdio: "inherit", shell: process.platform === "win32", }); + await new Promise((resolve, reject) => { + vercelSetup.on('close', (code) => { + if (code === 0 || code === null) { + console.log('āœ… Vercel project setup completed'); + resolve(); + } else { + console.log('āš ļø Vercel setup command completed (this is normal)'); + resolve(); // Don't reject, as this is often expected + } + }); + + vercelSetup.on('error', (error) => { + console.log('āš ļø Vercel setup command completed (this is normal)'); + resolve(); // Don't reject, as this is often expected + }); + }); + + // Wait a moment for project files to be written + await new Promise(resolve => setTimeout(resolve, 2000)); + // Load project info - const projectJson = JSON.parse( - fs.readFileSync(".vercel/project.json", "utf8") - ); - const projectId = projectJson.projectId; + let projectId; + try { + const projectJson = JSON.parse(fs.readFileSync('.vercel/project.json', 'utf8')); + projectId = projectJson.projectId; + } catch (error) { + throw new Error('Failed to load project info. Please ensure the Vercel project was created successfully.'); + } // Get Vercel token and initialize SDK client let vercelClient = null; @@ -489,30 +549,35 @@ async function deployToVercel(useGitHub = false) { // Fallback to CLI method if SDK failed if (!domain) { - const inspectOutput = execSync( - `vercel project inspect ${projectId} 2>&1`, - { + try { + const inspectOutput = execSync(`vercel project inspect ${projectId} 2>&1`, { cwd: projectRoot, - encoding: "utf8", - } - ); + encoding: 'utf8' + }); - const nameMatch = inspectOutput.match(/Name\s+([^\n]+)/); - if (nameMatch) { - projectName = nameMatch[1].trim(); - domain = `${projectName}.vercel.app`; - console.log("🌐 Using project name for domain:", domain); - } else { - const altMatch = inspectOutput.match(/Found Project [^/]+\/([^\n]+)/); - if (altMatch) { - projectName = altMatch[1].trim(); + const nameMatch = inspectOutput.match(/Name\s+([^\n]+)/); + if (nameMatch) { + projectName = nameMatch[1].trim(); domain = `${projectName}.vercel.app`; console.log("🌐 Using project name for domain:", domain); } else { - throw new Error( - "Could not determine project name from inspection output" - ); + const altMatch = inspectOutput.match(/Found Project [^/]+\/([^\n]+)/); + if (altMatch) { + projectName = altMatch[1].trim(); + domain = `${projectName}.vercel.app`; + console.log('🌐 Using project name for domain:', domain); + } else { + console.warn('āš ļø Could not determine project name from inspection, using fallback'); + // Use a fallback domain based on project ID + domain = `project-${projectId.slice(-8)}.vercel.app`; + console.log('🌐 Using fallback domain:', domain); + } } + } catch (error) { + console.warn('āš ļø Could not inspect project, using fallback domain'); + // Use a fallback domain based on project ID + domain = `project-${projectId.slice(-8)}.vercel.app`; + console.log('🌐 Using fallback domain:', domain); } } @@ -572,27 +637,49 @@ async function deployToVercel(useGitHub = false) { console.log("\nšŸ“¦ Deploying local code directly..."); } - execSync("vercel deploy --prod", { + // Use spawn for better control over the deployment process + const vercelDeploy = spawn('vercel', ['deploy', '--prod'], { cwd: projectRoot, stdio: "inherit", env: process.env, }); + await new Promise((resolve, reject) => { + vercelDeploy.on('close', (code) => { + if (code === 0) { + console.log('āœ… Vercel deployment command completed'); + resolve(); + } else { + console.error(`āŒ Vercel deployment failed with code: ${code}`); + reject(new Error(`Vercel deployment failed with exit code: ${code}`)); + } + }); + + vercelDeploy.on('error', (error) => { + console.error('āŒ Vercel deployment error:', error.message); + reject(error); + }); + }); + + // Wait for deployment to actually complete + let deployment; + if (vercelClient) { + try { + deployment = await waitForDeployment(vercelClient, projectId); + } catch (error) { + console.warn('āš ļø Could not verify deployment completion:', error.message); + console.log('ā„¹ļø Proceeding with domain verification...'); + } + } + // Verify actual domain after deployment console.log("\nšŸ” Verifying deployment domain..."); let actualDomain = domain; - if (vercelClient) { + if (vercelClient && deployment) { try { - const deployments = await vercelClient.deployments.list({ - projectId: projectId, - limit: 1, - }); - - if (deployments.deployments?.[0]?.url) { - actualDomain = deployments.deployments[0].url; - console.log("🌐 Verified actual domain:", actualDomain); - } + actualDomain = deployment.url || domain; + console.log('🌐 Verified actual domain:', actualDomain); } catch (error) { console.warn( "āš ļø Could not verify domain via SDK, using assumed domain" @@ -614,26 +701,37 @@ async function deployToVercel(useGitHub = false) { NEXT_PUBLIC_URL: `https://${actualDomain}`, }; - const updatedMetadata = await generateFarcasterMetadata( - actualDomain, - webhookUrl - ); - updatedEnv.MINI_APP_METADATA = updatedMetadata; + if (miniAppMetadata) { + const updatedMetadata = await generateFarcasterMetadata(actualDomain, fid, await validateSeedPhrase(process.env.SEED_PHRASE), process.env.SEED_PHRASE, webhookUrl); + updatedEnv.MINI_APP_METADATA = updatedMetadata; + } - await setEnvironmentVariables( - vercelClient, - projectId, - updatedEnv, - projectRoot - ); + await setEnvironmentVariables(vercelClient, projectId, updatedEnv, projectRoot); - console.log("\nšŸ“¦ Redeploying with correct domain..."); - execSync("vercel deploy --prod", { + console.log('\nšŸ“¦ Redeploying with correct domain...'); + const vercelRedeploy = spawn('vercel', ['deploy', '--prod'], { cwd: projectRoot, stdio: "inherit", env: process.env, }); + await new Promise((resolve, reject) => { + vercelRedeploy.on('close', (code) => { + if (code === 0) { + console.log('āœ… Redeployment completed'); + resolve(); + } else { + console.error(`āŒ Redeployment failed with code: ${code}`); + reject(new Error(`Redeployment failed with exit code: ${code}`)); + } + }); + + vercelRedeploy.on('error', (error) => { + console.error('āŒ Redeployment error:', error.message); + reject(error); + }); + }); + domain = actualDomain; } diff --git a/src/app/api/send-notification/route.ts b/src/app/api/send-notification/route.ts index 69d746e..7563723 100644 --- a/src/app/api/send-notification/route.ts +++ b/src/app/api/send-notification/route.ts @@ -1,4 +1,4 @@ -import { notificationDetailsSchema } from "@farcaster/frame-sdk"; +import { notificationDetailsSchema } from "@farcaster/miniapp-sdk"; import { NextRequest } from "next/server"; import { z } from "zod"; import { setUserNotificationDetails } from "~/lib/kv"; diff --git a/src/app/api/webhook/route.ts b/src/app/api/webhook/route.ts index a8c2ece..cc49676 100644 --- a/src/app/api/webhook/route.ts +++ b/src/app/api/webhook/route.ts @@ -2,7 +2,7 @@ import { ParseWebhookEvent, parseWebhookEvent, verifyAppKeyWithNeynar, -} from "@farcaster/frame-node"; +} from "@farcaster/miniapp-node"; import { NextRequest } from "next/server"; import { APP_NAME } from "~/lib/constants"; import { diff --git a/src/components/providers/SafeFarcasterSolanaProvider.tsx b/src/components/providers/SafeFarcasterSolanaProvider.tsx index 2d6c063..9834c95 100644 --- a/src/components/providers/SafeFarcasterSolanaProvider.tsx +++ b/src/components/providers/SafeFarcasterSolanaProvider.tsx @@ -1,6 +1,6 @@ import React, { createContext, useEffect, useState } from "react"; import dynamic from "next/dynamic"; -import { sdk } from '@farcaster/frame-sdk'; +import { sdk } from '@farcaster/miniapp-sdk'; const FarcasterSolanaProvider = dynamic( () => import('@farcaster/mini-app-solana').then(mod => mod.FarcasterSolanaProvider), diff --git a/src/components/providers/WagmiProvider.tsx b/src/components/providers/WagmiProvider.tsx index d3cf282..9fb313e 100644 --- a/src/components/providers/WagmiProvider.tsx +++ b/src/components/providers/WagmiProvider.tsx @@ -1,7 +1,7 @@ import { createConfig, http, WagmiProvider } from "wagmi"; import { base, degen, mainnet, optimism, unichain, celo } from "wagmi/chains"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { farcasterFrame } from "@farcaster/frame-wagmi-connector"; +import { farcasterFrame } from "@farcaster/miniapp-wagmi-connector"; import { coinbaseWallet, metaMask } from 'wagmi/connectors'; import { APP_NAME, APP_ICON_URL, APP_URL } from "~/lib/constants"; import { useEffect, useState } from "react"; diff --git a/src/components/ui/Header.tsx b/src/components/ui/Header.tsx index 9740001..d1339a5 100644 --- a/src/components/ui/Header.tsx +++ b/src/components/ui/Header.tsx @@ -2,7 +2,7 @@ import { useState } from "react"; import { APP_NAME } from "~/lib/constants"; -import sdk from "@farcaster/frame-sdk"; +import sdk from "@farcaster/miniapp-sdk"; import { useMiniApp } from "@neynar/react"; type HeaderProps = { diff --git a/src/components/ui/Share.tsx b/src/components/ui/Share.tsx index d58ec9e..ef29ebd 100644 --- a/src/components/ui/Share.tsx +++ b/src/components/ui/Share.tsx @@ -3,7 +3,7 @@ import { useCallback, useState, useEffect } from 'react'; import { Button } from './Button'; import { useMiniApp } from '@neynar/react'; -import { type ComposeCast } from "@farcaster/frame-sdk"; +import { type ComposeCast } from "@farcaster/miniapp-sdk"; interface EmbedConfig { path?: string; diff --git a/src/components/ui/tabs/ActionsTab.tsx b/src/components/ui/tabs/ActionsTab.tsx index 392929c..2f8af39 100644 --- a/src/components/ui/tabs/ActionsTab.tsx +++ b/src/components/ui/tabs/ActionsTab.tsx @@ -5,7 +5,7 @@ import { useMiniApp } from "@neynar/react"; import { ShareButton } from "../Share"; import { Button } from "../Button"; import { SignIn } from "../wallet/SignIn"; -import { type Haptics } from "@farcaster/frame-sdk"; +import { type Haptics } from "@farcaster/miniapp-sdk"; /** * ActionsTab component handles mini app actions like sharing, notifications, and haptic feedback. diff --git a/src/components/ui/tabs/HomeTab.tsx b/src/components/ui/tabs/HomeTab.tsx index aa7e37d..eb139cc 100644 --- a/src/components/ui/tabs/HomeTab.tsx +++ b/src/components/ui/tabs/HomeTab.tsx @@ -17,7 +17,7 @@ export function HomeTab() {

Put your content here!

-

Powered by Neynar 🪐

+

Powered by Neynar 🪐

); diff --git a/src/components/ui/wallet/SignIn.tsx b/src/components/ui/wallet/SignIn.tsx index 2d049b0..0f2dda5 100644 --- a/src/components/ui/wallet/SignIn.tsx +++ b/src/components/ui/wallet/SignIn.tsx @@ -2,7 +2,7 @@ import { useCallback, useState } from "react"; import { signIn, signOut, getCsrfToken } from "next-auth/react"; -import sdk, { SignIn as SignInCore } from "@farcaster/frame-sdk"; +import sdk, { SignIn as SignInCore } from "@farcaster/miniapp-sdk"; import { useSession } from "next-auth/react"; import { Button } from "../Button"; @@ -128,9 +128,9 @@ export function SignIn() { {/* Session Information */} {session && ( -
-
Session
-
+
+
Session
+
{JSON.stringify(session, null, 2)}
@@ -138,17 +138,17 @@ export function SignIn() { {/* Error Display */} {signInFailure && !authState.signingIn && ( -
-
SIWF Result
-
{signInFailure}
+
+
SIWF Result
+
{signInFailure}
)} {/* Success Result Display */} {signInResult && !authState.signingIn && ( -
-
SIWF Result
-
+
+
SIWF Result
+
{JSON.stringify(signInResult, null, 2)}
diff --git a/src/lib/kv.ts b/src/lib/kv.ts index 55477e9..eefc680 100644 --- a/src/lib/kv.ts +++ b/src/lib/kv.ts @@ -1,4 +1,4 @@ -import { FrameNotificationDetails } from "@farcaster/frame-sdk"; +import { FrameNotificationDetails } from "@farcaster/miniapp-sdk"; import { Redis } from "@upstash/redis"; import { APP_NAME } from "./constants"; diff --git a/src/lib/notifs.ts b/src/lib/notifs.ts index a04b24e..72007b1 100644 --- a/src/lib/notifs.ts +++ b/src/lib/notifs.ts @@ -1,7 +1,7 @@ import { SendNotificationRequest, sendNotificationResponseSchema, -} from "@farcaster/frame-sdk"; +} from "@farcaster/miniapp-sdk"; import { getUserNotificationDetails } from "~/lib/kv"; import { APP_URL } from "./constants";