feat: copy share url button and frames to mini app in cli text

This commit is contained in:
veganbeef
2025-05-09 14:08:06 -07:00
parent 08091fc206
commit 5fe54a80da
6 changed files with 59 additions and 42 deletions

View File

@@ -27,15 +27,15 @@ function printWelcomeMessage() {
console.log(` console.log(`
${purple}╔═══════════════════════════════════════════════════╗${reset} ${purple}╔═══════════════════════════════════════════════════╗${reset}
${purple}║ ║${reset} ${purple}║ ║${reset}
${purple}${reset} ${bright}Welcome to Frames v2 Quickstart by Neynar${reset} ${purple}${reset} ${purple}${reset} ${bright}Welcome to Mini Apps Quickstart by Neynar${reset} ${purple}${reset}
${purple}${reset} ${dim}The fastest way to build Farcaster Frames${reset} ${purple}${reset} ${purple}${reset} ${dim}the quickest way to build Farcaster mini apps${reset} ${purple}${reset}
${purple}║ ║${reset} ${purple}║ ║${reset}
${purple}╚═══════════════════════════════════════════════════╝${reset} ${purple}╚═══════════════════════════════════════════════════╝${reset}
${blue}Version:${reset} ${SCRIPT_VERSION} ${blue}Version:${reset} ${SCRIPT_VERSION}
${blue}Repository:${reset} ${dim}${REPO_URL}${reset} ${blue}Repository:${reset} ${dim}${REPO_URL}${reset}
Let's create your Frame! 🚀 Let's create your mini app! 🚀
`); `);
} }
@@ -77,13 +77,13 @@ export async function init() {
type: 'confirm', type: 'confirm',
name: 'useNeynar', name: 'useNeynar',
message: '🪐 Neynar is an API that makes it easy to build on Farcaster.\n\n' + message: '🪐 Neynar is an API that makes it easy to build on Farcaster.\n\n' +
'Benefits of using Neynar in your frame:\n' + 'Benefits of using Neynar in your mini app:\n' +
'- Pre-configured webhook handling (no setup required)\n' + '- Pre-configured webhook handling (no setup required)\n' +
'- Automatic frame analytics in your dev portal\n' + '- Automatic mini app analytics in your dev portal\n' +
'- Send manual notifications from dev.neynar.com\n' + '- Send manual notifications from dev.neynar.com\n' +
'- Built-in rate limiting and error handling\n\n' + '- Built-in rate limiting and error handling\n\n' +
`${purple}${bright}${italic}A demo API key is included if you would like to try out Neynar before signing up!${reset}\n\n` + `${purple}${bright}${italic}A demo API key is included if you would like to try out Neynar before signing up!${reset}\n\n` +
'Would you like to use Neynar in your frame?', 'Would you like to use Neynar in your mini app?',
default: true default: true
} }
]); ]);
@@ -172,7 +172,7 @@ export async function init() {
{ {
type: 'input', type: 'input',
name: 'projectName', name: 'projectName',
message: 'What is the name of your frame?', message: 'What is the name of your mini app?',
default: defaultFrameName, default: defaultFrameName,
validate: (input) => { validate: (input) => {
if (input.trim() === '') { if (input.trim() === '') {
@@ -184,14 +184,14 @@ export async function init() {
{ {
type: 'input', type: 'input',
name: 'description', name: 'description',
message: 'Give a one-line description of your frame (optional):', message: 'Give a one-line description of your mini app (optional):',
default: 'A Farcaster mini-app created with Neynar' default: 'A Farcaster mini-app created with Neynar'
}, },
{ {
type: 'input', type: 'input',
name: 'buttonText', name: 'buttonText',
message: 'Enter the button text for your frame:', message: 'Enter the button text for your mini app:',
default: 'Launch Frame', default: 'Launch Mini App',
validate: (input) => { validate: (input) => {
if (input.trim() === '') { if (input.trim() === '') {
return 'Button text cannot be empty'; return 'Button text cannot be empty';
@@ -218,7 +218,7 @@ export async function init() {
const projectDirName = projectName.replace(/\s+/g, '-').toLowerCase(); const projectDirName = projectName.replace(/\s+/g, '-').toLowerCase();
const projectPath = path.join(process.cwd(), projectDirName); const projectPath = path.join(process.cwd(), projectDirName);
console.log(`\nCreating a new Frames v2 app in ${projectPath}`); console.log(`\nCreating a new mini app in ${projectPath}`);
// Clone the repository // Clone the repository
try { try {

View File

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

View File

@@ -194,7 +194,7 @@ async function main() {
{ {
type: 'input', type: 'input',
name: 'domain', name: 'domain',
message: 'Enter the domain where your frame will be deployed (e.g., example.com):', message: 'Enter the domain where your mini app will be deployed (e.g., example.com):',
validate: async (input) => { validate: async (input) => {
try { try {
await validateDomain(input); await validateDomain(input);
@@ -211,11 +211,11 @@ async function main() {
{ {
type: 'input', type: 'input',
name: 'frameName', name: 'frameName',
message: 'Enter the name for your frame (e.g., My Cool Frame):', message: 'Enter the name for your mini app (e.g., My Cool Mini App):',
default: process.env.NEXT_PUBLIC_FRAME_NAME, default: process.env.NEXT_PUBLIC_FRAME_NAME,
validate: (input) => { validate: (input) => {
if (input.trim() === '') { if (input.trim() === '') {
return 'Frame name cannot be empty'; return 'Mini app name cannot be empty';
} }
return true; return true;
} }
@@ -227,8 +227,8 @@ async function main() {
{ {
type: 'input', type: 'input',
name: 'buttonText', name: 'buttonText',
message: 'Enter the text for your frame button:', message: 'Enter the text for your mini app button:',
default: process.env.NEXT_PUBLIC_FRAME_BUTTON_TEXT || 'Launch Frame', default: process.env.NEXT_PUBLIC_FRAME_BUTTON_TEXT || 'Launch Mini App',
validate: (input) => { validate: (input) => {
if (input.trim() === '') { if (input.trim() === '') {
return 'Button text cannot be empty'; return 'Button text cannot be empty';
@@ -300,7 +300,7 @@ async function main() {
type: 'password', type: 'password',
name: 'seedPhrase', name: 'seedPhrase',
message: 'Your farcaster custody account seed phrase is required to create a signature proving this app was created by you.\n' + message: 'Your farcaster custody account seed phrase is required to create a signature proving this app was created by you.\n' +
`⚠️ ${yellow}${italic}seed phrase is only used to sign the frame manifest, then discarded${reset} ⚠️\n` + `⚠️ ${yellow}${italic}seed phrase is only used to sign the mini app manifest, then discarded${reset} ⚠️\n` +
'Seed phrase:', 'Seed phrase:',
validate: async (input) => { validate: async (input) => {
try { try {
@@ -324,7 +324,7 @@ async function main() {
const fid = await lookupFidByCustodyAddress(accountAddress, neynarApiKey ?? 'FARCASTER_V2_FRAMES_DEMO'); const fid = await lookupFidByCustodyAddress(accountAddress, neynarApiKey ?? 'FARCASTER_V2_FRAMES_DEMO');
// Generate and sign manifest // Generate and sign manifest
console.log('\n🔨 Generating frame manifest...'); console.log('\n🔨 Generating mini app manifest...');
// Determine webhook URL based on environment variables // Determine webhook URL based on environment variables
const webhookUrl = neynarApiKey && neynarClientId const webhookUrl = neynarApiKey && neynarClientId
@@ -332,7 +332,7 @@ async function main() {
: `${domain}/api/webhook`; : `${domain}/api/webhook`;
const metadata = await generateFarcasterMetadata(domain, fid, accountAddress, seedPhrase, webhookUrl); const metadata = await generateFarcasterMetadata(domain, fid, accountAddress, seedPhrase, webhookUrl);
console.log('\n✅ Frame manifest generated' + (seedPhrase ? ' and signed' : '')); console.log('\n✅ Mini app manifest generated' + (seedPhrase ? ' and signed' : ''));
// Read existing .env file or create new one // Read existing .env file or create new one
const envPath = path.join(projectRoot, '.env'); const envPath = path.join(projectRoot, '.env');
@@ -395,7 +395,7 @@ async function main() {
shell: process.platform === 'win32' shell: process.platform === 'win32'
}); });
console.log('\n✨ Build complete! Your frame is ready for deployment. 🪐'); console.log('\n✨ Build complete! Your mini app is ready for deployment. 🪐');
console.log('📝 Make sure to configure the environment variables from .env in your hosting provider'); console.log('📝 Make sure to configure the environment variables from .env in your hosting provider');
} catch (error) { } catch (error) {

View File

@@ -154,14 +154,14 @@ async function checkRequiredEnvVars() {
const requiredVars = [ const requiredVars = [
{ {
name: 'NEXT_PUBLIC_FRAME_NAME', name: 'NEXT_PUBLIC_FRAME_NAME',
message: 'Enter the name for your frame (e.g., My Cool Frame):', message: 'Enter the name for your frame (e.g., My Cool Mini App):',
default: process.env.NEXT_PUBLIC_FRAME_NAME, default: process.env.NEXT_PUBLIC_FRAME_NAME,
validate: input => input.trim() !== '' || 'Frame name cannot be empty' validate: input => input.trim() !== '' || 'Mini app name cannot be empty'
}, },
{ {
name: 'NEXT_PUBLIC_FRAME_BUTTON_TEXT', name: 'NEXT_PUBLIC_FRAME_BUTTON_TEXT',
message: 'Enter the text for your frame button:', message: 'Enter the text for your frame button:',
default: process.env.NEXT_PUBLIC_FRAME_BUTTON_TEXT ?? 'Launch Frame', default: process.env.NEXT_PUBLIC_FRAME_BUTTON_TEXT ?? 'Launch Mini App',
validate: input => input.trim() !== '' || 'Button text cannot be empty' validate: input => input.trim() !== '' || 'Button text cannot be empty'
} }
]; ];
@@ -197,13 +197,13 @@ async function checkRequiredEnvVars() {
// Check for seed phrase // Check for seed phrase
if (!process.env.SEED_PHRASE) { if (!process.env.SEED_PHRASE) {
console.log('\n🔑 Frame Manifest Signing'); console.log('\n🔑 Mini App Manifest Signing');
console.log('A signed manifest helps users trust your frame.'); console.log('A signed manifest helps users trust your mini app.');
const { seedPhrase } = await inquirer.prompt([ const { seedPhrase } = await inquirer.prompt([
{ {
type: 'password', type: 'password',
name: 'seedPhrase', name: 'seedPhrase',
message: 'Enter your Farcaster custody account seed phrase to sign the frame manifest\n(optional -- leave blank to create an unsigned frame)\n\nSeed phrase:', message: 'Enter your Farcaster custody account seed phrase to sign the mini app manifest\n(optional -- leave blank to create an unsigned mini app)\n\nSeed phrase:',
default: null default: null
} }
]); ]);
@@ -385,7 +385,7 @@ 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(' An initial deployment is required to get an assigned domain that can be used in the frame manifest\n'); 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'); console.log('\n⚠ Note: choosing a longer, more unique project name will help avoid conflicts with other existing domains\n');
execSync('vercel', { execSync('vercel', {
cwd: projectRoot, cwd: projectRoot,
@@ -577,7 +577,7 @@ async function deployToVercel(useGitHub = false) {
} }
} }
console.log('\n✨ Deployment complete! Your frame is now live at:'); console.log('\n✨ Deployment complete! Your mini app is now live at:');
console.log(`🌐 https://${domain}`); console.log(`🌐 https://${domain}`);
console.log('\n📝 You can manage your project at https://vercel.com/dashboard'); console.log('\n📝 You can manage your project at https://vercel.com/dashboard');
@@ -590,13 +590,13 @@ async function deployToVercel(useGitHub = false) {
async function main() { async function main() {
try { try {
// Print welcome message // Print welcome message
console.log('🚀 Vercel Frame Deployment'); console.log('🚀 Vercel Mini App Deployment');
console.log('This script will deploy your frame to Vercel.'); console.log('This script will deploy your mini app to Vercel.');
console.log('\nThe script will:'); console.log('\nThe script will:');
console.log('1. Check for required environment variables'); console.log('1. Check for required environment variables');
console.log('2. Set up a Vercel project (new or existing)'); console.log('2. Set up a Vercel project (new or existing)');
console.log('3. Configure environment variables in Vercel'); console.log('3. Configure environment variables in Vercel');
console.log('4. Deploy and build your frame (Vercel will run the build automatically)\n'); console.log('4. Deploy and build your mini app (Vercel will run the build automatically)\n');
// Check for required environment variables // Check for required environment variables
await checkRequiredEnvVars(); await checkRequiredEnvVars();

View File

@@ -100,30 +100,30 @@ async function startDev() {
💻 To test on desktop: 💻 To test on desktop:
1. Open the localtunnel URL in your browser: ${tunnel.url} 1. Open the localtunnel URL in your browser: ${tunnel.url}
2. Enter your IP address in the password field${ip ? `: ${ip}` : ''} (note that this IP may be incorrect if you are using a VPN) 2. Enter your IP address in the password field${ip ? `: ${ip}` : ''} (note that this IP may be incorrect if you are using a VPN)
3. Click "Click to Submit" -- your frame should now load in the browser 3. Click "Click to Submit" -- your mini app should now load in the browser
4. Navigate to the Warpcast Frame Developer Tools: https://warpcast.com/~/developers/frames 4. Navigate to the Warpcast Mini App Developer Tools: https://warpcast.com/~/developers/mini-apps
5. Enter your frame URL: ${tunnel.url} 5. Enter your mini app URL: ${tunnel.url}
6. Click "Preview" to launch your frame within Warpcast (note that it may take ~10 seconds to load) 6. Click "Preview" to launch your mini app within Warpcast (note that it may take ~10 seconds to load)
❗️ You will not be able to load your frame in Warpcast until ❗️ ❗️ You will not be able to load your mini app in Warpcast until ❗️
❗️ you submit your IP address in the localtunnel password field ❗️ ❗️ you submit your IP address in the localtunnel password field ❗️
📱 To test in Warpcast mobile app: 📱 To test in Warpcast mobile app:
1. Open Warpcast on your phone 1. Open Warpcast on your phone
2. Go to Settings > Developer > Frames 2. Go to Settings > Developer > Mini Apps
4. Enter this URL: ${tunnel.url} 4. Enter this URL: ${tunnel.url}
5. Click "Preview" (note that it may take ~10 seconds to load) 5. Click "Preview" (note that it may take ~10 seconds to load)
`); `);
} else { } else {
frameUrl = 'http://localhost:3000'; frameUrl = 'http://localhost:3000';
console.log(` console.log(`
💻 To test your frame: 💻 To test your mini app:
1. Open the Warpcast Frame Developer Tools: https://warpcast.com/~/developers/frames 1. Open the Warpcast Mini App Developer Tools: https://warpcast.com/~/developers/mini-apps
2. Scroll down to the "Preview Frame" tool 2. Scroll down to the "Preview Mini App" tool
3. Enter this URL: ${frameUrl} 3. Enter this URL: ${frameUrl}
4. Click "Preview" to test your frame (note that it may take ~5 seconds to load the first time) 4. Click "Preview" to test your mini app (note that it may take ~5 seconds to load the first time)
`); `);
} }

View File

@@ -34,6 +34,7 @@ export default function Demo(
const [isContextOpen, setIsContextOpen] = useState(false); const [isContextOpen, setIsContextOpen] = useState(false);
const [txHash, setTxHash] = useState<string | null>(null); const [txHash, setTxHash] = useState<string | null>(null);
const [sendNotificationResult, setSendNotificationResult] = useState(""); const [sendNotificationResult, setSendNotificationResult] = useState("");
const [copied, setCopied] = useState(false);
const { address, isConnected } = useAccount(); const { address, isConnected } = useAccount();
const chainId = useChainId(); const chainId = useChainId();
@@ -289,6 +290,22 @@ export default function Demo(
Send notification Send notification
</Button> </Button>
</div> </div>
<div className="mb-4">
<Button
onClick={async () => {
if (context?.user?.fid) {
const shareUrl = `${process.env.NEXT_PUBLIC_URL}/share/${context.user.fid}`;
await navigator.clipboard.writeText(shareUrl);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}
}}
disabled={!context?.user?.fid}
>
{copied ? "Copied!" : "Copy share URL"}
</Button>
</div>
</div> </div>
<div> <div>