mirror of
https://github.com/neynarxyz/create-farcaster-mini-app.git
synced 2025-11-16 08:08:56 -05:00
feat: copy share url button and frames to mini app in cli text
This commit is contained in:
parent
08091fc206
commit
5fe54a80da
22
bin/init.js
22
bin/init.js
@ -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 {
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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)
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user