fix: verify vercel domain after deployment

This commit is contained in:
lucas-neynar 2025-03-21 12:23:51 -07:00
parent 6128212f0d
commit 1d55fbe940
No known key found for this signature in database
3 changed files with 118 additions and 133 deletions

View File

@ -176,12 +176,14 @@ async function init() {
break;
}
const defaultFrameName = neynarAppName.toLowerCase().includes('demo') ? undefined : neynarAppName;
const answers = await inquirer.prompt([
{
type: 'input',
name: 'projectName',
message: 'What is the name of your frame?',
default: neynarAppName || undefined,
message: '⚠️ Note: choosing a longer, more unique project name will help avoid conflicts with other existing domains\nWhat is the name of your frame?',
default: defaultFrameName,
validate: (input) => {
if (input.trim() === '') {
return 'Project name cannot be empty';
@ -211,18 +213,6 @@ async function init() {
}
return true;
}
},
{
type: 'input',
name: 'splashImageUrl',
message: 'Enter the URL for your splash image\n(optional -- leave blank to use the default public/splash.png image or replace public/splash.png with your own)\n\nExternal splash image URL:',
default: neynarAppLogoUrl || undefined
},
{
type: 'input',
name: 'iconImageUrl',
message: 'Enter the URL for your app icon\n(optional -- leave blank to use the default public/icon.png image or replace public/icon.png with your own)\n\nExternal app icon URL:',
default: neynarAppLogoUrl || undefined
}
]);
@ -242,7 +232,7 @@ async function init() {
'- Cannot test frame embeds or mobile devices\n\n' +
'Note: You can always switch between localhost and tunnel by editing the USE_TUNNEL environment variable in .env.local\n\n' +
'Use tunnel?',
default: true
default: false
}
]);
answers.useTunnel = hostingAnswer.useTunnel;
@ -423,14 +413,6 @@ async function init() {
fs.appendFileSync(envPath, `\nFID="${fid}"`);
}
if (answers.splashImageUrl) {
fs.appendFileSync(envPath, `\nNEXT_PUBLIC_FRAME_SPLASH_IMAGE_URL="${answers.splashImageUrl}"`);
}
if (answers.iconImageUrl) {
fs.appendFileSync(envPath, `\nNEXT_PUBLIC_FRAME_ICON_IMAGE_URL="${answers.iconImageUrl}"`);
}
// Append all remaining environment variables
fs.appendFileSync(envPath, `\nNEXT_PUBLIC_FRAME_NAME="${answers.projectName}"`);
fs.appendFileSync(envPath, `\nNEXT_PUBLIC_FRAME_DESCRIPTION="${answers.description}"`);

View File

@ -1,6 +1,6 @@
{
"name": "create-neynar-farcaster-frame",
"version": "1.1.3",
"version": "1.1.4",
"type": "module",
"files": [
"bin/index.js"

View File

@ -25,6 +25,7 @@ async function validateSeedPhrase(seedPhrase) {
}
async function generateFarcasterMetadata(domain, accountAddress, seedPhrase, webhookUrl) {
const trimmedDomain = domain.trim();
const header = {
type: 'custody',
key: accountAddress,
@ -32,7 +33,7 @@ async function generateFarcasterMetadata(domain, accountAddress, seedPhrase, web
const encodedHeader = Buffer.from(JSON.stringify(header), 'utf-8').toString('base64');
const payload = {
domain
domain: trimmedDomain
};
const encodedPayload = Buffer.from(JSON.stringify(payload), 'utf-8').toString('base64url');
@ -51,11 +52,11 @@ async function generateFarcasterMetadata(domain, accountAddress, seedPhrase, web
frame: {
version: "1",
name: process.env.NEXT_PUBLIC_FRAME_NAME,
iconUrl: process.env.NEXT_PUBLIC_FRAME_ICON_IMAGE_URL || `https://${domain}/icon.png`,
homeUrl: domain,
imageUrl: `https://${domain}/opengraph-image`,
iconUrl: `https://${trimmedDomain}/icon.png`,
homeUrl: trimmedDomain,
imageUrl: `https://${trimmedDomain}/opengraph-image`,
buttonTitle: process.env.NEXT_PUBLIC_FRAME_BUTTON_TEXT,
splashImageUrl: process.env.NEXT_PUBLIC_FRAME_SPLASH_IMAGE_URL || `https://${domain}/splash.png`,
splashImageUrl: `https://${trimmedDomain}/splash.png`,
splashBackgroundColor: "#f7f7f7",
webhookUrl,
},
@ -78,16 +79,26 @@ async function loadEnvLocal() {
console.log('Loading values from .env.local...');
const localEnv = dotenv.parse(fs.readFileSync('.env.local'));
// Copy all values except SEED_PHRASE to .env
// Define allowed variables to load from .env.local
const allowedVars = [
'SEED_PHRASE',
'NEXT_PUBLIC_FRAME_NAME',
'NEXT_PUBLIC_FRAME_DESCRIPTION',
'NEXT_PUBLIC_FRAME_BUTTON_TEXT',
'NEYNAR_API_KEY',
'NEYNAR_CLIENT_ID'
];
// Copy allowed values except SEED_PHRASE to .env
const envContent = fs.existsSync('.env') ? fs.readFileSync('.env', 'utf8') + '\n' : '';
let newEnvContent = envContent;
for (const [key, value] of Object.entries(localEnv)) {
if (key !== 'SEED_PHRASE') {
if (allowedVars.includes(key)) {
// Update process.env
process.env[key] = value;
// Add to .env content if not already there
if (!envContent.includes(`${key}=`)) {
// Add to .env content if not already there (except for SEED_PHRASE)
if (key !== 'SEED_PHRASE' && !envContent.includes(`${key}=`)) {
newEnvContent += `${key}="${value}"\n`;
}
}
@ -98,14 +109,6 @@ async function loadEnvLocal() {
console.log('✅ Values from .env.local have been written to .env');
}
}
// Always try to load SEED_PHRASE from .env.local
if (fs.existsSync('.env.local')) {
const localEnv = dotenv.parse(fs.readFileSync('.env.local'));
if (localEnv.SEED_PHRASE) {
process.env.SEED_PHRASE = localEnv.SEED_PHRASE;
}
}
} catch (error) {
// Error reading .env.local, which is fine
console.log('Note: No .env.local file found');
@ -156,8 +159,9 @@ async function checkRequiredEnvVars() {
// Check if the variable already exists in .env
if (!envContent.includes(`${varConfig.name}=`)) {
// Append the new variable to .env
fs.appendFileSync('.env', `\n${varConfig.name}="${value}"`);
// Append the new variable to .env without extra newlines
const newLine = envContent ? '\n' : '';
fs.appendFileSync('.env', `${newLine}${varConfig.name}="${value.trim()}"`);
}
}
}
@ -294,51 +298,49 @@ async function deployToVercel(useGitHub = false) {
}, null, 2));
}
// First try to link to an existing project
console.log('\n🔗 Checking for existing Vercel projects...');
let isNewProject = false;
let projectName = '';
let domain = '';
// TODO: check if project already exists here
try {
execSync('vercel link', {
cwd: projectRoot,
stdio: 'inherit'
});
// Get project info after linking
// question: do these lines do anything?
const projectOutput = execSync('vercel project ls', {
cwd: projectRoot,
encoding: 'utf8'
});
// Extract domain from project output
const projectLines = projectOutput.split('\n');
const currentProject = projectLines.find(line => line.includes('(current)'));
if (currentProject) {
const parts = currentProject.split(/\s+/);
projectName = parts[0];
domain = parts[1]?.replace('https://', '') || '';
console.log('🌐 Found existing project domain:', domain);
} else {
throw new Error('No existing project found');
}
} catch (error) {
// If linking fails (user declines to link), create a new project
console.log('\n📦 Creating new Vercel project...');
// Set 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('\n⚠ Note: choosing a longer, more unique project name will help avoid conflicts with other existing domains\n');
execSync('vercel', {
cwd: projectRoot,
stdio: 'inherit'
});
// Use NEXT_PUBLIC_FRAME_NAME for domain, replacing spaces with dashes
projectName = process.env.NEXT_PUBLIC_FRAME_NAME.toLowerCase().replace(/\s+/g, '-');
domain = `${projectName}.vercel.app`;
isNewProject = true;
}
// Load project info from .vercel/project.json
const projectJson = JSON.parse(fs.readFileSync('.vercel/project.json', 'utf8'));
const projectId = projectJson.projectId;
console.log('🌐 Using frame name for domain:', domain);
// Get project details using project inspect
console.log('\n🔍 Getting project details...');
const inspectOutput = execSync(`vercel project inspect ${projectId} 2>&1`, {
cwd: projectRoot,
encoding: 'utf8'
});
console.log('inspectOutput');
console.log(inspectOutput);
// Extract project name from inspect output
let projectName;
let domain;
const nameMatch = inspectOutput.match(/Name\s+([^\n]+)/);
if (nameMatch) {
projectName = nameMatch[1].trim();
domain = `${projectName}.vercel.app`;
console.log('🌐 Using project name for domain:', domain);
} else {
// Try alternative format
const altMatch = inspectOutput.match(/Found Project [^/]+\/([^\n]+)/);
if (altMatch) {
projectName = altMatch[1].trim();
domain = `${projectName}.vercel.app`;
console.log('🌐 Using project name for domain:', domain);
} else {
throw new Error('Could not determine project name from inspection output');
}
}
// Generate frame metadata if we have a seed phrase
let frameMetadata;
@ -362,6 +364,7 @@ async function deployToVercel(useGitHub = false) {
NEXTAUTH_SECRET: nextAuthSecret,
AUTH_SECRET: nextAuthSecret, // Fallback for some NextAuth versions
NEXTAUTH_URL: `https://${domain}`, // Add the deployment URL
NEXT_PUBLIC_URL: `https://${domain}`,
// Optional vars that should be set if they exist
...(process.env.NEYNAR_API_KEY && { NEYNAR_API_KEY: process.env.NEYNAR_API_KEY }),
@ -428,8 +431,8 @@ async function deployToVercel(useGitHub = false) {
});
}
// For new projects, verify the actual domain after deployment
if (isNewProject) {
// Verify the actual domain after deployment
console.log('\n🔍 Verifying deployment domain...');
const projectOutput = execSync('vercel project ls', {
cwd: projectRoot,
@ -495,7 +498,7 @@ async function deployToVercel(useGitHub = false) {
domain = actualDomain;
}
}
}
console.log('\n✨ Deployment complete! Your frame is now live at:');
console.log(`🌐 https://${domain}`);