Add more info

This commit is contained in:
Shreyaschorge 2025-07-11 03:44:09 +05:30
parent e0ca42169b
commit 8ff7080e84
No known key found for this signature in database

View File

@ -1,33 +1,33 @@
import { execSync, spawn } from "child_process"; import { execSync, spawn } from 'child_process';
import fs from "fs"; import fs from 'fs';
import path from "path"; import path from 'path';
import os from "os"; import os from 'os';
import { fileURLToPath } from "url"; import { fileURLToPath } from 'url';
import inquirer from "inquirer"; import inquirer from 'inquirer';
import dotenv from "dotenv"; import dotenv from 'dotenv';
import crypto from "crypto"; import crypto from 'crypto';
import { Vercel } from "@vercel/sdk"; import { Vercel } from '@vercel/sdk';
const __dirname = path.dirname(fileURLToPath(import.meta.url)); const __dirname = path.dirname(fileURLToPath(import.meta.url));
const projectRoot = path.join(__dirname, ".."); const projectRoot = path.join(__dirname, '..');
// Load environment variables in specific order // Load environment variables in specific order
dotenv.config({ path: ".env" }); dotenv.config({ path: '.env' });
async function generateFarcasterMetadata(domain, webhookUrl) { async function generateFarcasterMetadata(domain, webhookUrl) {
const trimmedDomain = domain.trim(); const trimmedDomain = domain.trim();
const tags = process.env.NEXT_PUBLIC_MINI_APP_TAGS?.split(","); const tags = process.env.NEXT_PUBLIC_MINI_APP_TAGS?.split(',');
return { return {
frame: { frame: {
version: "1", version: '1',
name: process.env.NEXT_PUBLIC_MINI_APP_NAME, name: process.env.NEXT_PUBLIC_MINI_APP_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_MINI_APP_BUTTON_TEXT, buttonTitle: process.env.NEXT_PUBLIC_MINI_APP_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_MINI_APP_DESCRIPTION, description: process.env.NEXT_PUBLIC_MINI_APP_DESCRIPTION,
primaryCategory: process.env.NEXT_PUBLIC_MINI_APP_PRIMARY_CATEGORY, primaryCategory: process.env.NEXT_PUBLIC_MINI_APP_PRIMARY_CATEGORY,
@ -38,36 +38,36 @@ async function generateFarcasterMetadata(domain, webhookUrl) {
async function loadEnvLocal() { async function loadEnvLocal() {
try { try {
if (fs.existsSync(".env.local")) { if (fs.existsSync('.env.local')) {
const { loadLocal } = await inquirer.prompt([ const { loadLocal } = await inquirer.prompt([
{ {
type: "confirm", type: 'confirm',
name: "loadLocal", name: 'loadLocal',
message: message:
"Found .env.local - would you like to load its values in addition to .env values?", 'Found .env.local - would you like to load its values in addition to .env values?',
default: true, default: true,
}, },
]); ]);
if (loadLocal) { if (loadLocal) {
console.log("Loading values from .env.local..."); console.log('Loading values from .env.local...');
const localEnv = dotenv.parse(fs.readFileSync(".env.local")); const localEnv = dotenv.parse(fs.readFileSync('.env.local'));
const allowedVars = [ const allowedVars = [
"NEXT_PUBLIC_MINI_APP_NAME", 'NEXT_PUBLIC_MINI_APP_NAME',
"NEXT_PUBLIC_MINI_APP_DESCRIPTION", 'NEXT_PUBLIC_MINI_APP_DESCRIPTION',
"NEXT_PUBLIC_MINI_APP_PRIMARY_CATEGORY", 'NEXT_PUBLIC_MINI_APP_PRIMARY_CATEGORY',
"NEXT_PUBLIC_MINI_APP_TAGS", 'NEXT_PUBLIC_MINI_APP_TAGS',
"NEXT_PUBLIC_MINI_APP_BUTTON_TEXT", 'NEXT_PUBLIC_MINI_APP_BUTTON_TEXT',
"NEXT_PUBLIC_ANALYTICS_ENABLED", 'NEXT_PUBLIC_ANALYTICS_ENABLED',
"NEYNAR_API_KEY", 'NEYNAR_API_KEY',
"NEYNAR_CLIENT_ID", 'NEYNAR_CLIENT_ID',
"SPONSOR_SIGNER", 'SPONSOR_SIGNER',
]; ];
const envContent = fs.existsSync(".env") const envContent = fs.existsSync('.env')
? fs.readFileSync(".env", "utf8") + "\n" ? fs.readFileSync('.env', 'utf8') + '\n'
: ""; : '';
let newEnvContent = envContent; let newEnvContent = envContent;
for (const [key, value] of Object.entries(localEnv)) { for (const [key, value] of Object.entries(localEnv)) {
@ -79,35 +79,35 @@ async function loadEnvLocal() {
} }
} }
fs.writeFileSync(".env", newEnvContent); fs.writeFileSync('.env', newEnvContent);
console.log("✅ Values from .env.local have been written to .env"); console.log('✅ Values from .env.local have been written to .env');
} }
} }
} catch (error) { } catch (error) {
console.log("Note: No .env.local file found"); console.log('Note: No .env.local file found');
} }
} }
async function checkRequiredEnvVars() { async function checkRequiredEnvVars() {
console.log("\n📝 Checking environment variables..."); console.log('\n📝 Checking environment variables...');
console.log("Loading values from .env..."); console.log('Loading values from .env...');
await loadEnvLocal(); await loadEnvLocal();
const requiredVars = [ const requiredVars = [
{ {
name: "NEXT_PUBLIC_MINI_APP_NAME", name: 'NEXT_PUBLIC_MINI_APP_NAME',
message: "Enter the name for your frame (e.g., My Cool Mini App):", message: 'Enter the name for your frame (e.g., My Cool Mini App):',
default: process.env.NEXT_PUBLIC_MINI_APP_NAME, default: process.env.NEXT_PUBLIC_MINI_APP_NAME,
validate: (input) => validate: (input) =>
input.trim() !== "" || "Mini app name cannot be empty", input.trim() !== '' || 'Mini app name cannot be empty',
}, },
{ {
name: "NEXT_PUBLIC_MINI_APP_BUTTON_TEXT", name: 'NEXT_PUBLIC_MINI_APP_BUTTON_TEXT',
message: "Enter the text for your frame button:", message: 'Enter the text for your frame button:',
default: default:
process.env.NEXT_PUBLIC_MINI_APP_BUTTON_TEXT ?? "Launch Mini App", process.env.NEXT_PUBLIC_MINI_APP_BUTTON_TEXT ?? 'Launch Mini App',
validate: (input) => input.trim() !== "" || "Button text cannot be empty", validate: (input) => input.trim() !== '' || 'Button text cannot be empty',
}, },
]; ];
@ -120,8 +120,8 @@ async function checkRequiredEnvVars() {
for (const varConfig of missingVars) { for (const varConfig of missingVars) {
const { value } = await inquirer.prompt([ const { value } = await inquirer.prompt([
{ {
type: "input", type: 'input',
name: "value", name: 'value',
message: varConfig.message, message: varConfig.message,
default: varConfig.default, default: varConfig.default,
validate: varConfig.validate, validate: varConfig.validate,
@ -130,14 +130,14 @@ async function checkRequiredEnvVars() {
process.env[varConfig.name] = value; process.env[varConfig.name] = value;
const envContent = fs.existsSync(".env") const envContent = fs.existsSync('.env')
? fs.readFileSync(".env", "utf8") ? fs.readFileSync('.env', 'utf8')
: ""; : '';
if (!envContent.includes(`${varConfig.name}=`)) { if (!envContent.includes(`${varConfig.name}=`)) {
const newLine = envContent ? "\n" : ""; const newLine = envContent ? '\n' : '';
fs.appendFileSync( fs.appendFileSync(
".env", '.env',
`${newLine}${varConfig.name}="${value.trim()}"` `${newLine}${varConfig.name}="${value.trim()}"`
); );
} }
@ -150,7 +150,8 @@ async function checkRequiredEnvVars() {
name: 'sponsorSigner', name: 'sponsorSigner',
message: message:
'Do you want to sponsor the signer? (This will be used in Sign In With Neynar)\n' + 'Do you want to sponsor the signer? (This will be used in Sign In With Neynar)\n' +
'Note: If you choose to sponsor the signer, Neynar will sponsor it for you and you will be charged in CUs.', 'Note: If you choose to sponsor the signer, Neynar will sponsor it for you and you will be charged in CUs.\n' +
'For more information, see https://docs.neynar.com/docs/two-ways-to-sponsor-a-farcaster-signer-via-neynar#sponsor-signers',
default: false, default: false,
}, },
]); ]);
@ -158,7 +159,10 @@ async function checkRequiredEnvVars() {
process.env.SPONSOR_SIGNER = sponsorSigner.toString(); process.env.SPONSOR_SIGNER = sponsorSigner.toString();
if (storeSeedPhrase) { if (storeSeedPhrase) {
fs.appendFileSync('.env.local', `\nSPONSOR_SIGNER="${sponsorSigner}"`); fs.appendFileSync(
'.env.local',
`\nSPONSOR_SIGNER="${sponsorSigner}"`
);
console.log('✅ Sponsor signer preference stored in .env.local'); console.log('✅ Sponsor signer preference stored in .env.local');
} }
} }
@ -166,7 +170,11 @@ async function checkRequiredEnvVars() {
} }
// Load SPONSOR_SIGNER from .env.local if SEED_PHRASE exists but SPONSOR_SIGNER doesn't // Load SPONSOR_SIGNER from .env.local if SEED_PHRASE exists but SPONSOR_SIGNER doesn't
if (process.env.SEED_PHRASE && !process.env.SPONSOR_SIGNER && fs.existsSync('.env.local')) { if (
process.env.SEED_PHRASE &&
!process.env.SPONSOR_SIGNER &&
fs.existsSync('.env.local')
) {
const localEnv = dotenv.parse(fs.readFileSync('.env.local')); const localEnv = dotenv.parse(fs.readFileSync('.env.local'));
if (localEnv.SPONSOR_SIGNER) { if (localEnv.SPONSOR_SIGNER) {
process.env.SPONSOR_SIGNER = localEnv.SPONSOR_SIGNER; process.env.SPONSOR_SIGNER = localEnv.SPONSOR_SIGNER;
@ -176,9 +184,9 @@ async function checkRequiredEnvVars() {
async function getGitRemote() { async function getGitRemote() {
try { try {
const remoteUrl = execSync("git remote get-url origin", { const remoteUrl = execSync('git remote get-url origin', {
cwd: projectRoot, cwd: projectRoot,
encoding: "utf8", encoding: 'utf8',
}).trim(); }).trim();
return remoteUrl; return remoteUrl;
} catch (error) { } catch (error) {
@ -188,9 +196,9 @@ async function getGitRemote() {
async function checkVercelCLI() { async function checkVercelCLI() {
try { try {
execSync("vercel --version", { execSync('vercel --version', {
stdio: "ignore", stdio: 'ignore',
shell: process.platform === "win32", shell: process.platform === 'win32',
}); });
return true; return true;
} catch (error) { } catch (error) {
@ -199,23 +207,23 @@ async function checkVercelCLI() {
} }
async function installVercelCLI() { async function installVercelCLI() {
console.log("Installing Vercel CLI..."); console.log('Installing Vercel CLI...');
execSync("npm install -g vercel", { execSync('npm install -g vercel', {
stdio: "inherit", stdio: 'inherit',
shell: process.platform === "win32", shell: process.platform === 'win32',
}); });
} }
async function getVercelToken() { async function getVercelToken() {
try { try {
// Try to get token from Vercel CLI config // Try to get token from Vercel CLI config
const configPath = path.join(os.homedir(), ".vercel", "auth.json"); const configPath = path.join(os.homedir(), '.vercel', 'auth.json');
if (fs.existsSync(configPath)) { if (fs.existsSync(configPath)) {
const authConfig = JSON.parse(fs.readFileSync(configPath, "utf8")); const authConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
return authConfig.token; return authConfig.token;
} }
} catch (error) { } catch (error) {
console.warn("Could not read Vercel token from config file"); console.warn('Could not read Vercel token from config file');
} }
// Try environment variable // Try environment variable
@ -225,75 +233,75 @@ async function getVercelToken() {
// Try to extract from vercel whoami // Try to extract from vercel whoami
try { try {
const whoamiOutput = execSync("vercel whoami", { const whoamiOutput = execSync('vercel whoami', {
encoding: "utf8", encoding: 'utf8',
stdio: "pipe", stdio: 'pipe',
}); });
// If we can get whoami, we're logged in, but we need the actual token // If we can get whoami, we're logged in, but we need the actual token
// The token isn't directly exposed, so we'll need to use CLI for some operations // The token isn't directly exposed, so we'll need to use CLI for some operations
console.log("✅ Verified Vercel CLI authentication"); console.log('✅ Verified Vercel CLI authentication');
return null; // We'll fall back to CLI operations return null; // We'll fall back to CLI operations
} catch (error) { } catch (error) {
throw new Error( throw new Error(
"Not logged in to Vercel CLI. Please run this script again to login." 'Not logged in to Vercel CLI. Please run this script again to login.'
); );
} }
} }
async function loginToVercel() { async function loginToVercel() {
console.log("\n🔑 Vercel Login"); console.log('\n🔑 Vercel Login');
console.log("You can either:"); console.log('You can either:');
console.log("1. Log in to an existing Vercel account"); console.log('1. Log in to an existing Vercel account');
console.log("2. Create a new Vercel account during login\n"); console.log('2. Create a new Vercel account during login\n');
console.log("If creating a new account:"); console.log('If creating a new account:');
console.log('1. Click "Continue with GitHub"'); console.log('1. Click "Continue with GitHub"');
console.log("2. Authorize GitHub access"); console.log('2. Authorize GitHub access');
console.log("3. Complete the Vercel account setup in your browser"); console.log('3. Complete the Vercel account setup in your browser');
console.log("4. Return here once your Vercel account is created\n"); console.log('4. Return here once your Vercel account is created\n');
console.log( console.log(
"\nNote: you may need to cancel this script with ctrl+c and run it again if creating a new vercel account" '\nNote: you may need to cancel this script with ctrl+c and run it again if creating a new vercel account'
); );
const child = spawn("vercel", ["login"], { const child = spawn('vercel', ['login'], {
stdio: "inherit", stdio: 'inherit',
}); });
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
child.on("close", (code) => { child.on('close', (code) => {
resolve(); resolve();
}); });
}); });
console.log("\n📱 Waiting for login to complete..."); console.log('\n📱 Waiting for login to complete...');
console.log( console.log(
"If you're creating a new account, please complete the Vercel account setup in your browser first." "If you're creating a new account, please complete the Vercel account setup in your browser first."
); );
for (let i = 0; i < 150; i++) { for (let i = 0; i < 150; i++) {
try { try {
execSync("vercel whoami", { stdio: "ignore" }); execSync('vercel whoami', { stdio: 'ignore' });
console.log("✅ Successfully logged in to Vercel!"); console.log('✅ Successfully logged in to Vercel!');
return true; return true;
} catch (error) { } catch (error) {
if (error.message.includes("Account not found")) { if (error.message.includes('Account not found')) {
console.log(" Waiting for Vercel account setup to complete..."); console.log(' Waiting for Vercel account setup to complete...');
} }
await new Promise((resolve) => setTimeout(resolve, 2000)); await new Promise((resolve) => setTimeout(resolve, 2000));
} }
} }
console.error("\n❌ Login timed out. Please ensure you have:"); console.error('\n❌ Login timed out. Please ensure you have:');
console.error("1. Completed the Vercel account setup in your browser"); console.error('1. Completed the Vercel account setup in your browser');
console.error("2. Authorized the GitHub integration"); console.error('2. Authorized the GitHub integration');
console.error("Then try running this script again."); console.error('Then try running this script again.');
return false; return false;
} }
async function setVercelEnvVarSDK(vercelClient, projectId, key, value) { async function setVercelEnvVarSDK(vercelClient, projectId, key, value) {
try { try {
let processedValue; let processedValue;
if (typeof value === "object") { if (typeof value === 'object') {
processedValue = JSON.stringify(value); processedValue = JSON.stringify(value);
} else { } else {
processedValue = value.toString(); processedValue = value.toString();
@ -305,7 +313,7 @@ async function setVercelEnvVarSDK(vercelClient, projectId, key, value) {
}); });
const existingVar = existingVars.envs?.find( const existingVar = existingVars.envs?.find(
(env) => env.key === key && env.target?.includes("production") (env) => env.key === key && env.target?.includes('production')
); );
if (existingVar) { if (existingVar) {
@ -315,7 +323,7 @@ async function setVercelEnvVarSDK(vercelClient, projectId, key, value) {
id: existingVar.id, id: existingVar.id,
requestBody: { requestBody: {
value: processedValue, value: processedValue,
target: ["production"], target: ['production'],
}, },
}); });
console.log(`✅ Updated environment variable: ${key}`); console.log(`✅ Updated environment variable: ${key}`);
@ -326,8 +334,8 @@ async function setVercelEnvVarSDK(vercelClient, projectId, key, value) {
requestBody: { requestBody: {
key: key, key: key,
value: processedValue, value: processedValue,
type: "encrypted", type: 'encrypted',
target: ["production"], target: ['production'],
}, },
}); });
console.log(`✅ Created environment variable: ${key}`); console.log(`✅ Created environment variable: ${key}`);
@ -349,7 +357,7 @@ async function setVercelEnvVarCLI(key, value, projectRoot) {
try { try {
execSync(`vercel env rm ${key} production -y`, { execSync(`vercel env rm ${key} production -y`, {
cwd: projectRoot, cwd: projectRoot,
stdio: "ignore", stdio: 'ignore',
env: process.env, env: process.env,
}); });
} catch (error) { } catch (error) {
@ -357,7 +365,7 @@ async function setVercelEnvVarCLI(key, value, projectRoot) {
} }
let processedValue; let processedValue;
if (typeof value === "object") { if (typeof value === 'object') {
processedValue = JSON.stringify(value); processedValue = JSON.stringify(value);
} else { } else {
processedValue = value.toString(); processedValue = value.toString();
@ -365,11 +373,11 @@ async function setVercelEnvVarCLI(key, value, projectRoot) {
// Create temporary file // Create temporary file
const tempFilePath = path.join(projectRoot, `${key}_temp.txt`); const tempFilePath = path.join(projectRoot, `${key}_temp.txt`);
fs.writeFileSync(tempFilePath, processedValue, "utf8"); fs.writeFileSync(tempFilePath, processedValue, 'utf8');
// Use appropriate command based on platform // Use appropriate command based on platform
let command; let command;
if (process.platform === "win32") { if (process.platform === 'win32') {
command = `type "${tempFilePath}" | vercel env add ${key} production`; command = `type "${tempFilePath}" | vercel env add ${key} production`;
} else { } else {
command = `cat "${tempFilePath}" | vercel env add ${key} production`; command = `cat "${tempFilePath}" | vercel env add ${key} production`;
@ -377,7 +385,7 @@ async function setVercelEnvVarCLI(key, value, projectRoot) {
execSync(command, { execSync(command, {
cwd: projectRoot, cwd: projectRoot,
stdio: "pipe", // Changed from 'inherit' to avoid interactive prompts stdio: 'pipe', // Changed from 'inherit' to avoid interactive prompts
shell: true, shell: true,
env: process.env, env: process.env,
}); });
@ -404,7 +412,7 @@ async function setEnvironmentVariables(
envVars, envVars,
projectRoot projectRoot
) { ) {
console.log("\n📝 Setting up environment variables..."); console.log('\n📝 Setting up environment variables...');
const results = []; const results = [];
@ -432,14 +440,19 @@ async function setEnvironmentVariables(
console.warn(`\n⚠️ Failed to set ${failed.length} environment variables:`); console.warn(`\n⚠️ Failed to set ${failed.length} environment variables:`);
failed.forEach((r) => console.warn(` - ${r.key}`)); failed.forEach((r) => console.warn(` - ${r.key}`));
console.warn( console.warn(
"\nYou may need to set these manually in the Vercel dashboard." '\nYou may need to set these manually in the Vercel dashboard.'
); );
} }
return results; return results;
} }
async function waitForDeployment(vercelClient, projectId, maxWaitTime = 300000) { // 5 minutes async function waitForDeployment(
vercelClient,
projectId,
maxWaitTime = 300000
) {
// 5 minutes
console.log('\n⏳ Waiting for deployment to complete...'); console.log('\n⏳ Waiting for deployment to complete...');
const startTime = Date.now(); const startTime = Date.now();
@ -447,7 +460,7 @@ async function waitForDeployment(vercelClient, projectId, maxWaitTime = 300000)
try { try {
const deployments = await vercelClient.deployments.list({ const deployments = await vercelClient.deployments.list({
projectId: projectId, projectId: projectId,
limit: 1 limit: 1,
}); });
if (deployments.deployments?.[0]) { if (deployments.deployments?.[0]) {
@ -464,14 +477,14 @@ async function waitForDeployment(vercelClient, projectId, maxWaitTime = 300000)
} }
// Still building, wait and check again // Still building, wait and check again
await new Promise(resolve => setTimeout(resolve, 5000)); // Wait 5 seconds await new Promise((resolve) => setTimeout(resolve, 5000)); // Wait 5 seconds
} else { } else {
console.log('⏳ No deployment found yet, waiting...'); console.log('⏳ No deployment found yet, waiting...');
await new Promise(resolve => setTimeout(resolve, 5000)); await new Promise((resolve) => setTimeout(resolve, 5000));
} }
} catch (error) { } catch (error) {
console.warn('⚠️ Could not check deployment status:', error.message); console.warn('⚠️ Could not check deployment status:', error.message);
await new Promise(resolve => setTimeout(resolve, 5000)); await new Promise((resolve) => setTimeout(resolve, 5000));
} }
} }
@ -480,18 +493,18 @@ async function waitForDeployment(vercelClient, projectId, maxWaitTime = 300000)
async function deployToVercel(useGitHub = false) { async function deployToVercel(useGitHub = false) {
try { try {
console.log("\n🚀 Deploying to Vercel..."); console.log('\n🚀 Deploying to Vercel...');
// Ensure vercel.json exists // Ensure vercel.json exists
const vercelConfigPath = path.join(projectRoot, "vercel.json"); const vercelConfigPath = path.join(projectRoot, 'vercel.json');
if (!fs.existsSync(vercelConfigPath)) { if (!fs.existsSync(vercelConfigPath)) {
console.log("📝 Creating vercel.json configuration..."); console.log('📝 Creating vercel.json configuration...');
fs.writeFileSync( fs.writeFileSync(
vercelConfigPath, vercelConfigPath,
JSON.stringify( JSON.stringify(
{ {
buildCommand: "next build", buildCommand: 'next build',
framework: "nextjs", framework: 'nextjs',
}, },
null, null,
2 2
@ -501,15 +514,19 @@ 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 mini app manifest\n'); console.log(
console.log('\n⚠ Note: choosing a longer, more unique project name will help avoid conflicts with other existing domains\n'); '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'
);
// Use spawn instead of execSync for better error handling // Use spawn instead of execSync for better error handling
const { spawn } = await import('child_process'); const { spawn } = await import('child_process');
const vercelSetup = spawn('vercel', [], { const vercelSetup = spawn('vercel', [], {
cwd: projectRoot, cwd: projectRoot,
stdio: "inherit", stdio: 'inherit',
shell: process.platform === "win32", shell: process.platform === 'win32',
}); });
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
@ -530,15 +547,19 @@ async function deployToVercel(useGitHub = false) {
}); });
// Wait a moment for project files to be written // Wait a moment for project files to be written
await new Promise(resolve => setTimeout(resolve, 2000)); await new Promise((resolve) => setTimeout(resolve, 2000));
// Load project info // Load project info
let projectId; let projectId;
try { try {
const projectJson = JSON.parse(fs.readFileSync('.vercel/project.json', 'utf8')); const projectJson = JSON.parse(
fs.readFileSync('.vercel/project.json', 'utf8')
);
projectId = projectJson.projectId; projectId = projectJson.projectId;
} catch (error) { } catch (error) {
throw new Error('Failed to load project info. Please ensure the Vercel project was created successfully.'); throw new Error(
'Failed to load project info. Please ensure the Vercel project was created successfully.'
);
} }
// Get Vercel token and initialize SDK client // Get Vercel token and initialize SDK client
@ -549,16 +570,16 @@ async function deployToVercel(useGitHub = false) {
vercelClient = new Vercel({ vercelClient = new Vercel({
bearerToken: token, bearerToken: token,
}); });
console.log("✅ Initialized Vercel SDK client"); console.log('✅ Initialized Vercel SDK client');
} }
} catch (error) { } catch (error) {
console.warn( console.warn(
"⚠️ Could not initialize Vercel SDK, falling back to CLI operations" '⚠️ Could not initialize Vercel SDK, falling back to CLI operations'
); );
} }
// Get project details // Get project details
console.log("\n🔍 Getting project details..."); console.log('\n🔍 Getting project details...');
let domain; let domain;
let projectName; let projectName;
@ -569,10 +590,10 @@ async function deployToVercel(useGitHub = false) {
}); });
projectName = project.name; projectName = project.name;
domain = `${projectName}.vercel.app`; domain = `${projectName}.vercel.app`;
console.log("🌐 Using project name for domain:", domain); console.log('🌐 Using project name for domain:', domain);
} catch (error) { } catch (error) {
console.warn( console.warn(
"⚠️ Could not get project details via SDK, using CLI fallback" '⚠️ Could not get project details via SDK, using CLI fallback'
); );
} }
} }
@ -580,16 +601,19 @@ async function deployToVercel(useGitHub = false) {
// Fallback to CLI method if SDK failed // Fallback to CLI method if SDK failed
if (!domain) { if (!domain) {
try { try {
const inspectOutput = execSync(`vercel project inspect ${projectId} 2>&1`, { const inspectOutput = execSync(
`vercel project inspect ${projectId} 2>&1`,
{
cwd: projectRoot, cwd: projectRoot,
encoding: 'utf8' encoding: 'utf8',
}); }
);
const nameMatch = inspectOutput.match(/Name\s+([^\n]+)/); const nameMatch = inspectOutput.match(/Name\s+([^\n]+)/);
if (nameMatch) { if (nameMatch) {
projectName = nameMatch[1].trim(); projectName = nameMatch[1].trim();
domain = `${projectName}.vercel.app`; domain = `${projectName}.vercel.app`;
console.log("🌐 Using project name for domain:", domain); console.log('🌐 Using project name for domain:', domain);
} else { } else {
const altMatch = inspectOutput.match(/Found Project [^/]+\/([^\n]+)/); const altMatch = inspectOutput.match(/Found Project [^/]+\/([^\n]+)/);
if (altMatch) { if (altMatch) {
@ -597,7 +621,9 @@ async function deployToVercel(useGitHub = false) {
domain = `${projectName}.vercel.app`; domain = `${projectName}.vercel.app`;
console.log('🌐 Using project name for domain:', domain); console.log('🌐 Using project name for domain:', domain);
} else { } else {
console.warn('⚠️ Could not determine project name from inspection, using fallback'); console.warn(
'⚠️ Could not determine project name from inspection, using fallback'
);
// Use a fallback domain based on project ID // Use a fallback domain based on project ID
domain = `project-${projectId.slice(-8)}.vercel.app`; domain = `project-${projectId.slice(-8)}.vercel.app`;
console.log('🌐 Using fallback domain:', domain); console.log('🌐 Using fallback domain:', domain);
@ -612,7 +638,7 @@ async function deployToVercel(useGitHub = false) {
} }
// Generate mini app metadata // Generate mini app metadata
console.log("\n🔨 Generating mini app metadata..."); console.log('\n🔨 Generating mini app metadata...');
const webhookUrl = const webhookUrl =
process.env.NEYNAR_API_KEY && process.env.NEYNAR_CLIENT_ID process.env.NEYNAR_API_KEY && process.env.NEYNAR_CLIENT_ID
@ -620,11 +646,11 @@ async function deployToVercel(useGitHub = false) {
: `https://${domain}/api/webhook`; : `https://${domain}/api/webhook`;
const miniAppMetadata = await generateFarcasterMetadata(domain, webhookUrl); const miniAppMetadata = await generateFarcasterMetadata(domain, webhookUrl);
console.log("✅ Mini app metadata generated"); console.log('✅ Mini app metadata generated');
// Prepare environment variables // Prepare environment variables
const nextAuthSecret = const nextAuthSecret =
process.env.NEXTAUTH_SECRET || crypto.randomBytes(32).toString("hex"); process.env.NEXTAUTH_SECRET || crypto.randomBytes(32).toString('hex');
const vercelEnv = { const vercelEnv = {
NEXTAUTH_SECRET: nextAuthSecret, NEXTAUTH_SECRET: nextAuthSecret,
AUTH_SECRET: nextAuthSecret, AUTH_SECRET: nextAuthSecret,
@ -637,12 +663,14 @@ async function deployToVercel(useGitHub = false) {
...(process.env.NEYNAR_CLIENT_ID && { ...(process.env.NEYNAR_CLIENT_ID && {
NEYNAR_CLIENT_ID: process.env.NEYNAR_CLIENT_ID, NEYNAR_CLIENT_ID: process.env.NEYNAR_CLIENT_ID,
}), }),
...(process.env.SPONSOR_SIGNER && { SPONSOR_SIGNER: process.env.SPONSOR_SIGNER }), ...(process.env.SPONSOR_SIGNER && {
SPONSOR_SIGNER: process.env.SPONSOR_SIGNER,
}),
...(miniAppMetadata && { MINI_APP_METADATA: miniAppMetadata }), ...(miniAppMetadata && { MINI_APP_METADATA: miniAppMetadata }),
...Object.fromEntries( ...Object.fromEntries(
Object.entries(process.env).filter(([key]) => Object.entries(process.env).filter(([key]) =>
key.startsWith("NEXT_PUBLIC_") key.startsWith('NEXT_PUBLIC_')
) )
), ),
}; };
@ -657,21 +685,21 @@ async function deployToVercel(useGitHub = false) {
// Deploy the project // Deploy the project
if (useGitHub) { if (useGitHub) {
console.log("\nSetting up GitHub integration..."); console.log('\nSetting up GitHub integration...');
execSync("vercel link", { execSync('vercel link', {
cwd: projectRoot, cwd: projectRoot,
stdio: "inherit", stdio: 'inherit',
env: process.env, env: process.env,
}); });
console.log("\n📦 Deploying with GitHub integration..."); console.log('\n📦 Deploying with GitHub integration...');
} else { } else {
console.log("\n📦 Deploying local code directly..."); console.log('\n📦 Deploying local code directly...');
} }
// Use spawn for better control over the deployment process // Use spawn for better control over the deployment process
const vercelDeploy = spawn('vercel', ['deploy', '--prod'], { const vercelDeploy = spawn('vercel', ['deploy', '--prod'], {
cwd: projectRoot, cwd: projectRoot,
stdio: "inherit", stdio: 'inherit',
env: process.env, env: process.env,
}); });
@ -698,13 +726,16 @@ async function deployToVercel(useGitHub = false) {
try { try {
deployment = await waitForDeployment(vercelClient, projectId); deployment = await waitForDeployment(vercelClient, projectId);
} catch (error) { } catch (error) {
console.warn('⚠️ Could not verify deployment completion:', error.message); console.warn(
'⚠️ Could not verify deployment completion:',
error.message
);
console.log(' Proceeding with domain verification...'); console.log(' Proceeding with domain verification...');
} }
} }
// Verify actual domain after deployment // Verify actual domain after deployment
console.log("\n🔍 Verifying deployment domain..."); console.log('\n🔍 Verifying deployment domain...');
let actualDomain = domain; let actualDomain = domain;
if (vercelClient && deployment) { if (vercelClient && deployment) {
@ -713,14 +744,14 @@ async function deployToVercel(useGitHub = false) {
console.log('🌐 Verified actual domain:', actualDomain); console.log('🌐 Verified actual domain:', actualDomain);
} catch (error) { } catch (error) {
console.warn( console.warn(
"⚠️ Could not verify domain via SDK, using assumed domain" '⚠️ Could not verify domain via SDK, using assumed domain'
); );
} }
} }
// Update environment variables if domain changed // Update environment variables if domain changed
if (actualDomain !== domain) { if (actualDomain !== domain) {
console.log("🔄 Updating environment variables with correct domain..."); console.log('🔄 Updating environment variables with correct domain...');
const webhookUrl = const webhookUrl =
process.env.NEYNAR_API_KEY && process.env.NEYNAR_CLIENT_ID process.env.NEYNAR_API_KEY && process.env.NEYNAR_CLIENT_ID
@ -733,16 +764,27 @@ async function deployToVercel(useGitHub = false) {
}; };
if (miniAppMetadata) { if (miniAppMetadata) {
const updatedMetadata = await generateFarcasterMetadata(actualDomain, fid, await validateSeedPhrase(process.env.SEED_PHRASE), process.env.SEED_PHRASE, webhookUrl); const updatedMetadata = await generateFarcasterMetadata(
actualDomain,
fid,
await validateSeedPhrase(process.env.SEED_PHRASE),
process.env.SEED_PHRASE,
webhookUrl
);
updatedEnv.MINI_APP_METADATA = updatedMetadata; updatedEnv.MINI_APP_METADATA = updatedMetadata;
} }
await setEnvironmentVariables(vercelClient, projectId, updatedEnv, projectRoot); await setEnvironmentVariables(
vercelClient,
projectId,
updatedEnv,
projectRoot
);
console.log('\n📦 Redeploying with correct domain...'); console.log('\n📦 Redeploying with correct domain...');
const vercelRedeploy = spawn('vercel', ['deploy', '--prod'], { const vercelRedeploy = spawn('vercel', ['deploy', '--prod'], {
cwd: projectRoot, cwd: projectRoot,
stdio: "inherit", stdio: 'inherit',
env: process.env, env: process.env,
}); });
@ -766,39 +808,39 @@ async function deployToVercel(useGitHub = false) {
domain = actualDomain; domain = actualDomain;
} }
console.log("\n✨ Deployment complete! Your mini app 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( console.log(
"\n📝 You can manage your project at https://vercel.com/dashboard" '\n📝 You can manage your project at https://vercel.com/dashboard'
); );
} catch (error) { } catch (error) {
console.error("\n❌ Deployment failed:", error.message); console.error('\n❌ Deployment failed:', error.message);
process.exit(1); process.exit(1);
} }
} }
async function main() { async function main() {
try { try {
console.log("🚀 Vercel Mini App Deployment (SDK Edition)"); console.log('🚀 Vercel Mini App Deployment (SDK Edition)');
console.log( console.log(
"This script will deploy your mini app to Vercel using the Vercel SDK." 'This script will deploy your mini app to Vercel using the Vercel SDK.'
); );
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 using SDK"); console.log('3. Configure environment variables in Vercel using SDK');
console.log("4. Deploy and build your mini app\n"); console.log('4. Deploy and build your mini app\n');
// Check if @vercel/sdk is installed // Check if @vercel/sdk is installed
try { try {
await import("@vercel/sdk"); await import('@vercel/sdk');
} catch (error) { } catch (error) {
console.log("📦 Installing @vercel/sdk..."); console.log('📦 Installing @vercel/sdk...');
execSync("npm install @vercel/sdk", { execSync('npm install @vercel/sdk', {
cwd: projectRoot, cwd: projectRoot,
stdio: "inherit", stdio: 'inherit',
}); });
console.log("✅ @vercel/sdk installed successfully"); console.log('✅ @vercel/sdk installed successfully');
} }
await checkRequiredEnvVars(); await checkRequiredEnvVars();
@ -807,55 +849,55 @@ async function main() {
let useGitHub = false; let useGitHub = false;
if (remoteUrl) { if (remoteUrl) {
console.log("\n📦 Found GitHub repository:", remoteUrl); console.log('\n📦 Found GitHub repository:', remoteUrl);
const { useGitHubDeploy } = await inquirer.prompt([ const { useGitHubDeploy } = await inquirer.prompt([
{ {
type: "confirm", type: 'confirm',
name: "useGitHubDeploy", name: 'useGitHubDeploy',
message: "Would you like to deploy from the GitHub repository?", message: 'Would you like to deploy from the GitHub repository?',
default: true, default: true,
}, },
]); ]);
useGitHub = useGitHubDeploy; useGitHub = useGitHubDeploy;
} else { } else {
console.log("\n⚠ No GitHub repository found."); console.log('\n⚠ No GitHub repository found.');
const { action } = await inquirer.prompt([ const { action } = await inquirer.prompt([
{ {
type: "list", type: 'list',
name: "action", name: 'action',
message: "What would you like to do?", message: 'What would you like to do?',
choices: [ choices: [
{ name: "Deploy local code directly", value: "deploy" }, { name: 'Deploy local code directly', value: 'deploy' },
{ name: "Set up GitHub repository first", value: "setup" }, { name: 'Set up GitHub repository first', value: 'setup' },
], ],
default: "deploy", default: 'deploy',
}, },
]); ]);
if (action === "setup") { if (action === 'setup') {
console.log("\n👋 Please set up your GitHub repository first:"); console.log('\n👋 Please set up your GitHub repository first:');
console.log("1. Create a new repository on GitHub"); console.log('1. Create a new repository on GitHub');
console.log("2. Run these commands:"); console.log('2. Run these commands:');
console.log(" git remote add origin <your-repo-url>"); console.log(' git remote add origin <your-repo-url>');
console.log(" git push -u origin main"); console.log(' git push -u origin main');
console.log("\nThen run this script again to deploy."); console.log('\nThen run this script again to deploy.');
process.exit(0); process.exit(0);
} }
} }
if (!(await checkVercelCLI())) { if (!(await checkVercelCLI())) {
console.log("Vercel CLI not found. Installing..."); console.log('Vercel CLI not found. Installing...');
await installVercelCLI(); await installVercelCLI();
} }
if (!(await loginToVercel())) { if (!(await loginToVercel())) {
console.error("\n❌ Failed to log in to Vercel. Please try again."); console.error('\n❌ Failed to log in to Vercel. Please try again.');
process.exit(1); process.exit(1);
} }
await deployToVercel(useGitHub); await deployToVercel(useGitHub);
} catch (error) { } catch (error) {
console.error("\n❌ Error:", error.message); console.error('\n❌ Error:', error.message);
process.exit(1); process.exit(1);
} }
} }