feat: add primary category and tags

This commit is contained in:
veganbeef 2025-05-09 16:41:53 -07:00
parent 5fe54a80da
commit f350aaa897
No known key found for this signature in database
8 changed files with 89 additions and 20 deletions

View File

@ -185,7 +185,44 @@ export async function init() {
type: 'input', type: 'input',
name: 'description', name: 'description',
message: 'Give a one-line description of your mini app (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: 'list',
name: 'primaryCategory',
message: 'It is strongly recommended to choose a primary category and tags to help users discover your mini app.\n\nSelect a primary category:',
choices: [
{ name: 'Games', value: 'games' },
{ name: 'Social', value: 'social' },
{ name: 'Finance', value: 'finance' },
{ name: 'Utility', value: 'utility' },
{ name: 'Productivity', value: 'productivity' },
{ name: 'Health & Fitness', value: 'health-fitness' },
{ name: 'News & Media', value: 'news-media' },
{ name: 'Music', value: 'music' },
{ name: 'Shopping', value: 'shopping' },
{ name: 'Education', value: 'education' },
{ name: 'Developer Tools', value: 'developer-tools' },
{ name: 'Entertainment', value: 'entertainment' },
{ name: 'Art & Creativity', value: 'art-creativity' },
new inquirer.Separator(),
{ name: 'Skip (not recommended)', value: null }
],
default: 'social'
},
{
type: 'input',
name: 'tags',
message: 'Enter tags for your mini app (separate with spaces or commas, optional):',
default: '',
filter: (input) => {
if (!input.trim()) return [];
// Split by both spaces and commas, trim whitespace, and filter out empty strings
return input
.split(/[,\s]+/)
.map(tag => tag.trim())
.filter(tag => tag.length > 0);
}
}, },
{ {
type: 'input', type: 'input',
@ -333,6 +370,8 @@ export async function init() {
// Append all remaining environment variables // Append all remaining environment variables
fs.appendFileSync(envPath, `\nNEXT_PUBLIC_FRAME_NAME="${answers.projectName}"`); fs.appendFileSync(envPath, `\nNEXT_PUBLIC_FRAME_NAME="${answers.projectName}"`);
fs.appendFileSync(envPath, `\nNEXT_PUBLIC_FRAME_DESCRIPTION="${answers.description}"`); fs.appendFileSync(envPath, `\nNEXT_PUBLIC_FRAME_DESCRIPTION="${answers.description}"`);
fs.appendFileSync(envPath, `\nNEXT_PUBLIC_FRAME_PRIMARY_CATEGORY="${answers.primaryCategory}"`);
fs.appendFileSync(envPath, `\nNEXT_PUBLIC_FRAME_TAGS="${answers.tags.join(',')}"`);
fs.appendFileSync(envPath, `\nNEXT_PUBLIC_FRAME_BUTTON_TEXT="${answers.buttonText}"`); fs.appendFileSync(envPath, `\nNEXT_PUBLIC_FRAME_BUTTON_TEXT="${answers.buttonText}"`);
fs.appendFileSync(envPath, `\nNEXTAUTH_SECRET="${crypto.randomBytes(32).toString('hex')}"`); fs.appendFileSync(envPath, `\nNEXTAUTH_SECRET="${crypto.randomBytes(32).toString('hex')}"`);
if (useNeynar && neynarApiKey && neynarClientId) { if (useNeynar && neynarApiKey && neynarClientId) {

2
index.d.ts vendored
View File

@ -1,5 +1,5 @@
/** /**
* Initialize a new Farcaster mini-app project * Initialize a new Farcaster mini app project
* @returns Promise<void> * @returns Promise<void>
*/ */
export function init(): Promise<void>; export function init(): Promise<void>;

View File

@ -1,6 +1,6 @@
{ {
"name": "@neynar/create-farcaster-mini-app", "name": "@neynar/create-farcaster-mini-app",
"version": "1.2.24", "version": "1.2.25",
"type": "module", "type": "module",
"private": false, "private": false,
"access": "public", "access": "public",
@ -22,6 +22,9 @@
"frame", "frame",
"frames-v2", "frames-v2",
"farcaster-frames", "farcaster-frames",
"miniapps",
"miniapp",
"mini-apps",
"mini-app", "mini-app",
"neynar", "neynar",
"web3" "web3"

View File

@ -161,6 +161,8 @@ async function generateFarcasterMetadata(domain, fid, accountAddress, seedPhrase
}); });
const encodedSignature = Buffer.from(signature, 'utf-8').toString('base64url'); const encodedSignature = Buffer.from(signature, 'utf-8').toString('base64url');
const tags = process.env.NEXT_PUBLIC_FRAME_TAGS?.split(',');
return { return {
accountAssociation: { accountAssociation: {
header: encodedHeader, header: encodedHeader,
@ -177,6 +179,9 @@ async function generateFarcasterMetadata(domain, fid, accountAddress, seedPhrase
splashImageUrl: `https://${domain}/splash.png`, splashImageUrl: `https://${domain}/splash.png`,
splashBackgroundColor: "#f7f7f7", splashBackgroundColor: "#f7f7f7",
webhookUrl, webhookUrl,
description: process.env.NEXT_PUBLIC_FRAME_DESCRIPTION,
primaryCategory: process.env.NEXT_PUBLIC_FRAME_PRIMARY_CATEGORY,
tags,
}, },
}; };
} }
@ -346,6 +351,8 @@ async function main() {
// Frame metadata // Frame metadata
`NEXT_PUBLIC_FRAME_NAME="${frameName}"`, `NEXT_PUBLIC_FRAME_NAME="${frameName}"`,
`NEXT_PUBLIC_FRAME_DESCRIPTION="${process.env.NEXT_PUBLIC_FRAME_DESCRIPTION || ''}"`, `NEXT_PUBLIC_FRAME_DESCRIPTION="${process.env.NEXT_PUBLIC_FRAME_DESCRIPTION || ''}"`,
`NEXT_PUBLIC_FRAME_PRIMARY_CATEGORY="${process.env.NEXT_PUBLIC_FRAME_PRIMARY_CATEGORY || ''}"`,
`NEXT_PUBLIC_FRAME_TAGS="${process.env.NEXT_PUBLIC_FRAME_TAGS || ''}"`,
`NEXT_PUBLIC_FRAME_BUTTON_TEXT="${buttonText}"`, `NEXT_PUBLIC_FRAME_BUTTON_TEXT="${buttonText}"`,
// Neynar configuration (if it exists in current env) // Neynar configuration (if it exists in current env)

View File

@ -72,6 +72,8 @@ async function generateFarcasterMetadata(domain, fid, accountAddress, seedPhrase
}); });
const encodedSignature = Buffer.from(signature, 'utf-8').toString('base64url'); const encodedSignature = Buffer.from(signature, 'utf-8').toString('base64url');
const tags = process.env.NEXT_PUBLIC_FRAME_TAGS?.split(',');
return { return {
accountAssociation: { accountAssociation: {
header: encodedHeader, header: encodedHeader,
@ -80,14 +82,17 @@ async function generateFarcasterMetadata(domain, fid, accountAddress, seedPhrase
}, },
frame: { frame: {
version: "1", version: "1",
name: process.env.NEXT_PUBLIC_FRAME_NAME?.trim(), name: process.env.NEXT_PUBLIC_FRAME_NAME,
iconUrl: `https://${trimmedDomain}/icon.png`, iconUrl: `https://${trimmedDomain}/icon.png`,
homeUrl: `https://${trimmedDomain}`, homeUrl: `https://${trimmedDomain}`,
imageUrl: `https://${trimmedDomain}/api/opengraph-image`, imageUrl: `https://${trimmedDomain}/api/opengraph-image`,
buttonTitle: process.env.NEXT_PUBLIC_FRAME_BUTTON_TEXT?.trim(), buttonTitle: process.env.NEXT_PUBLIC_FRAME_BUTTON_TEXT,
splashImageUrl: `https://${trimmedDomain}/splash.png`, splashImageUrl: `https://${trimmedDomain}/splash.png`,
splashBackgroundColor: "#f7f7f7", splashBackgroundColor: "#f7f7f7",
webhookUrl: webhookUrl?.trim(), webhookUrl: webhookUrl?.trim(),
description: process.env.NEXT_PUBLIC_FRAME_DESCRIPTION,
primaryCategory: process.env.NEXT_PUBLIC_FRAME_PRIMARY_CATEGORY,
tags,
}, },
}; };
} }
@ -113,6 +118,8 @@ async function loadEnvLocal() {
'SEED_PHRASE', 'SEED_PHRASE',
'NEXT_PUBLIC_FRAME_NAME', 'NEXT_PUBLIC_FRAME_NAME',
'NEXT_PUBLIC_FRAME_DESCRIPTION', 'NEXT_PUBLIC_FRAME_DESCRIPTION',
'NEXT_PUBLIC_FRAME_PRIMARY_CATEGORY',
'NEXT_PUBLIC_FRAME_TAGS',
'NEXT_PUBLIC_FRAME_BUTTON_TEXT', 'NEXT_PUBLIC_FRAME_BUTTON_TEXT',
'NEYNAR_API_KEY', 'NEYNAR_API_KEY',
'NEYNAR_CLIENT_ID' 'NEYNAR_CLIENT_ID'

View File

@ -101,7 +101,7 @@ async function startDev() {
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 mini app 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 Mini App Developer Tools: https://warpcast.com/~/developers/mini-apps 4. Navigate to the Warpcast Mini App Developer Tools: https://warpcast.com/~/developers
5. Enter your mini app URL: ${tunnel.url} 5. Enter your mini app URL: ${tunnel.url}
6. Click "Preview" to launch your mini app 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)
@ -120,7 +120,7 @@ async function startDev() {
frameUrl = 'http://localhost:3000'; frameUrl = 'http://localhost:3000';
console.log(` console.log(`
💻 To test your mini app: 💻 To test your mini app:
1. Open the Warpcast Mini App Developer Tools: https://warpcast.com/~/developers/mini-apps 1. Open the Warpcast Mini App Developer Tools: https://warpcast.com/~/developers
2. Scroll down to the "Preview Mini App" 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 mini app (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

@ -1,6 +1,8 @@
export const APP_URL = process.env.NEXT_PUBLIC_URL!; export const APP_URL = process.env.NEXT_PUBLIC_URL!;
export const APP_NAME = process.env.NEXT_PUBLIC_FRAME_NAME; export const APP_NAME = process.env.NEXT_PUBLIC_FRAME_NAME;
export const APP_DESCRIPTION = process.env.NEXT_PUBLIC_FRAME_DESCRIPTION; export const APP_DESCRIPTION = process.env.NEXT_PUBLIC_FRAME_DESCRIPTION;
export const APP_PRIMARY_CATEGORY = process.env.NEXT_PUBLIC_FRAME_PRIMARY_CATEGORY;
export const APP_TAGS = process.env.NEXT_PUBLIC_FRAME_TAGS?.split(',');
export const APP_ICON_URL = `${APP_URL}/icon.png`; export const APP_ICON_URL = `${APP_URL}/icon.png`;
export const APP_OG_IMAGE_URL = `${APP_URL}/api/opengraph-image`; export const APP_OG_IMAGE_URL = `${APP_URL}/api/opengraph-image`;
export const APP_SPLASH_URL = `${APP_URL}/splash.png`; export const APP_SPLASH_URL = `${APP_URL}/splash.png`;

View File

@ -1,26 +1,31 @@
import { type ClassValue, clsx } from 'clsx'; import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
import { mnemonicToAccount } from 'viem/accounts'; import { mnemonicToAccount } from 'viem/accounts';
import { APP_BUTTON_TEXT, APP_ICON_URL, APP_NAME, APP_OG_IMAGE_URL, APP_SPLASH_BACKGROUND_COLOR, APP_URL, APP_WEBHOOK_URL } from './constants'; import { APP_BUTTON_TEXT, APP_DESCRIPTION, APP_ICON_URL, APP_NAME, APP_OG_IMAGE_URL, APP_PRIMARY_CATEGORY, APP_SPLASH_BACKGROUND_COLOR, APP_TAGS, APP_URL, APP_WEBHOOK_URL } from './constants';
import { APP_SPLASH_URL } from './constants'; import { APP_SPLASH_URL } from './constants';
interface FrameMetadata { interface FrameMetadata {
version: string;
name: string;
iconUrl: string;
homeUrl: string;
imageUrl?: string;
buttonTitle?: string;
splashImageUrl?: string;
splashBackgroundColor?: string;
webhookUrl?: string;
description?: string;
primaryCategory?: string;
tags?: string[];
};
interface FrameManifest {
accountAssociation?: { accountAssociation?: {
header: string; header: string;
payload: string; payload: string;
signature: string; signature: string;
}; };
frame: { frame: FrameMetadata;
version: string;
name: string;
iconUrl: string;
homeUrl: string;
imageUrl: string;
buttonTitle: string;
splashImageUrl: string;
splashBackgroundColor: string;
webhookUrl: string;
};
} }
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
@ -51,12 +56,15 @@ export function getFrameEmbedMetadata(ogImageUrl?: string) {
splashImageUrl: APP_SPLASH_URL, splashImageUrl: APP_SPLASH_URL,
iconUrl: APP_ICON_URL, iconUrl: APP_ICON_URL,
splashBackgroundColor: APP_SPLASH_BACKGROUND_COLOR, splashBackgroundColor: APP_SPLASH_BACKGROUND_COLOR,
description: APP_DESCRIPTION,
primaryCategory: APP_PRIMARY_CATEGORY,
tags: APP_TAGS,
}, },
}, },
}; };
} }
export async function getFarcasterMetadata(): Promise<FrameMetadata> { export async function getFarcasterMetadata(): Promise<FrameManifest> {
// First check for FRAME_METADATA in .env and use that if it exists // First check for FRAME_METADATA in .env and use that if it exists
if (process.env.FRAME_METADATA) { if (process.env.FRAME_METADATA) {
try { try {
@ -123,6 +131,9 @@ export async function getFarcasterMetadata(): Promise<FrameMetadata> {
splashImageUrl: APP_SPLASH_URL, splashImageUrl: APP_SPLASH_URL,
splashBackgroundColor: APP_SPLASH_BACKGROUND_COLOR, splashBackgroundColor: APP_SPLASH_BACKGROUND_COLOR,
webhookUrl: APP_WEBHOOK_URL, webhookUrl: APP_WEBHOOK_URL,
description: APP_DESCRIPTION,
primaryCategory: APP_PRIMARY_CATEGORY,
tags: APP_TAGS,
}, },
}; };
} }