Merge branch 'main' into mp/alt-sign

This commit is contained in:
Manan 2025-07-09 17:00:43 -07:00 committed by GitHub
commit 45f8b980b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 178 additions and 81 deletions

View File

@ -388,10 +388,9 @@ export async function init(projectName = null, autoAcceptDefaults = false) {
packageJson.dependencies = { packageJson.dependencies = {
"@farcaster/auth-client": ">=0.3.0 <1.0.0", "@farcaster/auth-client": ">=0.3.0 <1.0.0",
"@farcaster/auth-kit": ">=0.6.0 <1.0.0", "@farcaster/auth-kit": ">=0.6.0 <1.0.0",
"@farcaster/frame-core": ">=0.0.29 <1.0.0", "@farcaster/miniapp-node": ">=0.1.5 <1.0.0",
"@farcaster/frame-node": ">=0.0.18 <1.0.0", "@farcaster/miniapp-sdk": ">=0.1.6 <1.0.0",
"@farcaster/frame-sdk": ">=0.0.31 <1.0.0", "@farcaster/miniapp-wagmi-connector": "^1.0.0",
"@farcaster/frame-wagmi-connector": ">=0.0.19 <1.0.0",
"@farcaster/mini-app-solana": ">=0.0.17 <1.0.0", "@farcaster/mini-app-solana": ">=0.0.17 <1.0.0",
"@neynar/react": "^1.2.5", "@neynar/react": "^1.2.5",
"@radix-ui/react-label": "^2.1.1", "@radix-ui/react-label": "^2.1.1",

View File

@ -1,6 +1,6 @@
{ {
"name": "@neynar/create-farcaster-mini-app", "name": "@neynar/create-farcaster-mini-app",
"version": "1.5.3", "version": "1.5.6",
"type": "module", "type": "module",
"private": false, "private": false,
"access": "public", "access": "public",

View File

@ -1,6 +1,6 @@
#!/usr/bin/env node #!/usr/bin/env node
const { execSync } = require('child_process'); import { execSync } from 'child_process';
// Parse arguments // Parse arguments
const args = process.argv.slice(2); const args = process.argv.slice(2);

View File

@ -409,6 +409,45 @@ async function setEnvironmentVariables(
return results; 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) { async function deployToVercel(useGitHub = false) {
try { try {
console.log("\n🚀 Deploying to Vercel..."); console.log("\n🚀 Deploying to Vercel...");
@ -431,25 +470,46 @@ async function deployToVercel(useGitHub = false) {
} }
// Set up Vercel project // Set up Vercel project
console.log("\n📦 Setting up Vercel project..."); console.log('\n📦 Setting up Vercel project...');
console.log( console.log('An initial deployment is required to get an assigned domain that can be used in the mini app manifest\n');
"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');
);
console.log(
"\n⚠ Note: choosing a longer, more unique project name will help avoid conflicts with other existing domains\n"
);
execSync("vercel", { // Use spawn instead of execSync for better error handling
const { spawn } = await import('child_process');
const vercelSetup = spawn('vercel', [], {
cwd: projectRoot, cwd: projectRoot,
stdio: "inherit", stdio: "inherit",
shell: process.platform === "win32", 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 // Load project info
const projectJson = JSON.parse( let projectId;
fs.readFileSync(".vercel/project.json", "utf8") try {
); const projectJson = JSON.parse(fs.readFileSync('.vercel/project.json', 'utf8'));
const projectId = projectJson.projectId; 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 // Get Vercel token and initialize SDK client
let vercelClient = null; let vercelClient = null;
@ -489,30 +549,35 @@ async function deployToVercel(useGitHub = false) {
// Fallback to CLI method if SDK failed // Fallback to CLI method if SDK failed
if (!domain) { if (!domain) {
const inspectOutput = execSync( try {
`vercel project inspect ${projectId} 2>&1`, const inspectOutput = execSync(`vercel project inspect ${projectId} 2>&1`, {
{
cwd: projectRoot, cwd: projectRoot,
encoding: "utf8", encoding: 'utf8'
} });
);
const nameMatch = inspectOutput.match(/Name\s+([^\n]+)/); const nameMatch = inspectOutput.match(/Name\s+([^\n]+)/);
if (nameMatch) { if (nameMatch) {
projectName = nameMatch[1].trim(); 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();
domain = `${projectName}.vercel.app`; domain = `${projectName}.vercel.app`;
console.log("🌐 Using project name for domain:", domain); console.log("🌐 Using project name for domain:", domain);
} else { } else {
throw new Error( const altMatch = inspectOutput.match(/Found Project [^/]+\/([^\n]+)/);
"Could not determine project name from inspection output" 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..."); 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, cwd: projectRoot,
stdio: "inherit", stdio: "inherit",
env: process.env, 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 // Verify actual domain after deployment
console.log("\n🔍 Verifying deployment domain..."); console.log("\n🔍 Verifying deployment domain...");
let actualDomain = domain; let actualDomain = domain;
if (vercelClient) { if (vercelClient && deployment) {
try { try {
const deployments = await vercelClient.deployments.list({ actualDomain = deployment.url || domain;
projectId: projectId, console.log('🌐 Verified actual domain:', actualDomain);
limit: 1,
});
if (deployments.deployments?.[0]?.url) {
actualDomain = deployments.deployments[0].url;
console.log("🌐 Verified actual domain:", actualDomain);
}
} catch (error) { } catch (error) {
console.warn( console.warn(
"⚠️ Could not verify domain via SDK, using assumed domain" "⚠️ Could not verify domain via SDK, using assumed domain"
@ -614,26 +701,37 @@ async function deployToVercel(useGitHub = false) {
NEXT_PUBLIC_URL: `https://${actualDomain}`, NEXT_PUBLIC_URL: `https://${actualDomain}`,
}; };
const updatedMetadata = await generateFarcasterMetadata( if (miniAppMetadata) {
actualDomain, const updatedMetadata = await generateFarcasterMetadata(actualDomain, fid, await validateSeedPhrase(process.env.SEED_PHRASE), process.env.SEED_PHRASE, webhookUrl);
webhookUrl updatedEnv.MINI_APP_METADATA = updatedMetadata;
); }
updatedEnv.MINI_APP_METADATA = updatedMetadata;
await setEnvironmentVariables( await setEnvironmentVariables(vercelClient, projectId, updatedEnv, projectRoot);
vercelClient,
projectId,
updatedEnv,
projectRoot
);
console.log("\n📦 Redeploying with correct domain..."); console.log('\n📦 Redeploying with correct domain...');
execSync("vercel deploy --prod", { const vercelRedeploy = spawn('vercel', ['deploy', '--prod'], {
cwd: projectRoot, cwd: projectRoot,
stdio: "inherit", stdio: "inherit",
env: process.env, 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; domain = actualDomain;
} }

View File

@ -1,4 +1,4 @@
import { notificationDetailsSchema } from "@farcaster/frame-sdk"; import { notificationDetailsSchema } from "@farcaster/miniapp-sdk";
import { NextRequest } from "next/server"; import { NextRequest } from "next/server";
import { z } from "zod"; import { z } from "zod";
import { setUserNotificationDetails } from "~/lib/kv"; import { setUserNotificationDetails } from "~/lib/kv";

View File

@ -2,7 +2,7 @@ import {
ParseWebhookEvent, ParseWebhookEvent,
parseWebhookEvent, parseWebhookEvent,
verifyAppKeyWithNeynar, verifyAppKeyWithNeynar,
} from "@farcaster/frame-node"; } from "@farcaster/miniapp-node";
import { NextRequest } from "next/server"; import { NextRequest } from "next/server";
import { APP_NAME } from "~/lib/constants"; import { APP_NAME } from "~/lib/constants";
import { import {

View File

@ -1,6 +1,6 @@
import React, { createContext, useEffect, useState } from "react"; import React, { createContext, useEffect, useState } from "react";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import { sdk } from '@farcaster/frame-sdk'; import { sdk } from '@farcaster/miniapp-sdk';
const FarcasterSolanaProvider = dynamic( const FarcasterSolanaProvider = dynamic(
() => import('@farcaster/mini-app-solana').then(mod => mod.FarcasterSolanaProvider), () => import('@farcaster/mini-app-solana').then(mod => mod.FarcasterSolanaProvider),

View File

@ -1,7 +1,7 @@
import { createConfig, http, WagmiProvider } from "wagmi"; import { createConfig, http, WagmiProvider } from "wagmi";
import { base, degen, mainnet, optimism, unichain, celo } from "wagmi/chains"; import { base, degen, mainnet, optimism, unichain, celo } from "wagmi/chains";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 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 { coinbaseWallet, metaMask } from 'wagmi/connectors';
import { APP_NAME, APP_ICON_URL, APP_URL } from "~/lib/constants"; import { APP_NAME, APP_ICON_URL, APP_URL } from "~/lib/constants";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";

View File

@ -2,7 +2,7 @@
import { useState } from "react"; import { useState } from "react";
import { APP_NAME } from "~/lib/constants"; import { APP_NAME } from "~/lib/constants";
import sdk from "@farcaster/frame-sdk"; import sdk from "@farcaster/miniapp-sdk";
import { useMiniApp } from "@neynar/react"; import { useMiniApp } from "@neynar/react";
type HeaderProps = { type HeaderProps = {

View File

@ -3,7 +3,7 @@
import { useCallback, useState, useEffect } from 'react'; import { useCallback, useState, useEffect } from 'react';
import { Button } from './Button'; import { Button } from './Button';
import { useMiniApp } from '@neynar/react'; import { useMiniApp } from '@neynar/react';
import { type ComposeCast } from "@farcaster/frame-sdk"; import { type ComposeCast } from "@farcaster/miniapp-sdk";
interface EmbedConfig { interface EmbedConfig {
path?: string; path?: string;

View File

@ -5,7 +5,7 @@ import { useMiniApp } from "@neynar/react";
import { ShareButton } from "../Share"; import { ShareButton } from "../Share";
import { Button } from "../Button"; import { Button } from "../Button";
import { SignIn } from "../wallet/SignIn"; 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. * ActionsTab component handles mini app actions like sharing, notifications, and haptic feedback.

View File

@ -17,7 +17,7 @@ export function HomeTab() {
<div className="flex items-center justify-center h-[calc(100vh-200px)] px-6"> <div className="flex items-center justify-center h-[calc(100vh-200px)] px-6">
<div className="text-center w-full max-w-md mx-auto"> <div className="text-center w-full max-w-md mx-auto">
<p className="text-lg mb-2">Put your content here!</p> <p className="text-lg mb-2">Put your content here!</p>
<p className="text-sm text-gray-500">Powered by Neynar 🪐</p> <p className="text-sm text-gray-500 dark:text-gray-400">Powered by Neynar 🪐</p>
</div> </div>
</div> </div>
); );

View File

@ -2,7 +2,7 @@
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { signIn, signOut, getCsrfToken } from "next-auth/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 { useSession } from "next-auth/react";
import { Button } from "../Button"; import { Button } from "../Button";
@ -128,9 +128,9 @@ export function SignIn() {
{/* Session Information */} {/* Session Information */}
{session && ( {session && (
<div className="my-2 p-2 text-xs overflow-x-scroll bg-gray-100 rounded-lg font-mono"> <div className="my-2 p-2 text-xs overflow-x-scroll bg-gray-100 dark:bg-gray-900 rounded-lg font-mono">
<div className="font-semibold text-gray-500 mb-1">Session</div> <div className="font-semibold text-gray-500 dark:text-gray-300 mb-1">Session</div>
<div className="whitespace-pre"> <div className="whitespace-pre text-gray-700 dark:text-gray-200">
{JSON.stringify(session, null, 2)} {JSON.stringify(session, null, 2)}
</div> </div>
</div> </div>
@ -138,17 +138,17 @@ export function SignIn() {
{/* Error Display */} {/* Error Display */}
{signInFailure && !authState.signingIn && ( {signInFailure && !authState.signingIn && (
<div className="my-2 p-2 text-xs overflow-x-scroll bg-gray-100 rounded-lg font-mono"> <div className="my-2 p-2 text-xs overflow-x-scroll bg-gray-100 dark:bg-gray-900 rounded-lg font-mono">
<div className="font-semibold text-gray-500 mb-1">SIWF Result</div> <div className="font-semibold text-gray-500 dark:text-gray-300 mb-1">SIWF Result</div>
<div className="whitespace-pre">{signInFailure}</div> <div className="whitespace-pre text-gray-700 dark:text-gray-200">{signInFailure}</div>
</div> </div>
)} )}
{/* Success Result Display */} {/* Success Result Display */}
{signInResult && !authState.signingIn && ( {signInResult && !authState.signingIn && (
<div className="my-2 p-2 text-xs overflow-x-scroll bg-gray-100 rounded-lg font-mono"> <div className="my-2 p-2 text-xs overflow-x-scroll bg-gray-100 dark:bg-gray-900 rounded-lg font-mono">
<div className="font-semibold text-gray-500 mb-1">SIWF Result</div> <div className="font-semibold text-gray-500 dark:text-gray-300 mb-1">SIWF Result</div>
<div className="whitespace-pre"> <div className="whitespace-pre text-gray-700 dark:text-gray-200">
{JSON.stringify(signInResult, null, 2)} {JSON.stringify(signInResult, null, 2)}
</div> </div>
</div> </div>

View File

@ -1,4 +1,4 @@
import { FrameNotificationDetails } from "@farcaster/frame-sdk"; import { FrameNotificationDetails } from "@farcaster/miniapp-sdk";
import { Redis } from "@upstash/redis"; import { Redis } from "@upstash/redis";
import { APP_NAME } from "./constants"; import { APP_NAME } from "./constants";

View File

@ -1,7 +1,7 @@
import { import {
SendNotificationRequest, SendNotificationRequest,
sendNotificationResponseSchema, sendNotificationResponseSchema,
} from "@farcaster/frame-sdk"; } from "@farcaster/miniapp-sdk";
import { getUserNotificationDetails } from "~/lib/kv"; import { getUserNotificationDetails } from "~/lib/kv";
import { APP_URL } from "./constants"; import { APP_URL } from "./constants";