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
No known key found for this signature in database
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>