diff --git a/scripts/build.js b/scripts/build.js index b680399..6d6bccb 100755 --- a/scripts/build.js +++ b/scripts/build.js @@ -1,111 +1,74 @@ -import { execSync } from 'child_process'; -import fs from 'fs'; -import path from 'path'; -import { mnemonicToAccount } from 'viem/accounts'; -import { fileURLToPath } from 'url'; -import inquirer from 'inquirer'; -import dotenv from 'dotenv'; -import crypto from 'crypto'; - -// ANSI color codes -const yellow = '\x1b[33m'; -const italic = '\x1b[3m'; -const reset = '\x1b[0m'; +import { execSync } from "child_process"; +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; +import inquirer from "inquirer"; +import dotenv from "dotenv"; +import crypto from "crypto"; // Load environment variables in specific order // First load .env for main config -dotenv.config({ path: '.env' }); - -async function lookupFidByCustodyAddress(custodyAddress, apiKey) { - if (!apiKey) { - throw new Error('Neynar API key is required'); - } - const lowerCasedCustodyAddress = custodyAddress.toLowerCase(); - - const response = await fetch( - `https://api.neynar.com/v2/farcaster/user/bulk-by-address?addresses=${lowerCasedCustodyAddress}&address_types=custody_address`, - { - headers: { - 'accept': 'application/json', - 'x-api-key': 'FARCASTER_V2_FRAMES_DEMO' - } - } - ); - - if (!response.ok) { - throw new Error(`Failed to lookup FID: ${response.statusText}`); - } - - const data = await response.json(); - if (!data[lowerCasedCustodyAddress]?.length || !data[lowerCasedCustodyAddress][0].custody_address) { - throw new Error('No FID found for this custody address'); - } - - return data[lowerCasedCustodyAddress][0].fid; -} +dotenv.config({ path: ".env" }); async function loadEnvLocal() { try { - if (fs.existsSync('.env.local')) { + if (fs.existsSync(".env.local")) { const { loadLocal } = await inquirer.prompt([ { - type: 'confirm', - name: 'loadLocal', - message: 'Found .env.local, likely created by the install script - would you like to load its values?', - default: false - } + type: "confirm", + name: "loadLocal", + message: + "Found .env.local, likely created by the install script - would you like to load its values?", + default: false, + }, ]); if (loadLocal) { - console.log('Loading values from .env.local...'); - const localEnv = dotenv.parse(fs.readFileSync('.env.local')); - - // Copy all values except SEED_PHRASE to .env - const envContent = fs.existsSync('.env') ? fs.readFileSync('.env', 'utf8') + '\n' : ''; + console.log("Loading values from .env.local..."); + const localEnv = dotenv.parse(fs.readFileSync(".env.local")); + + // Copy all values 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') { - // Update process.env - process.env[key] = value; - // Add to .env content if not already there - if (!envContent.includes(`${key}=`)) { - newEnvContent += `${key}="${value}"\n`; - } + // Update process.env + process.env[key] = value; + // Add to .env content if not already there + if (!envContent.includes(`${key}=`)) { + newEnvContent += `${key}="${value}"\n`; } } - - // Write updated content to .env - fs.writeFileSync('.env', newEnvContent); - 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; + // Write updated content to .env + fs.writeFileSync(".env", newEnvContent); + console.log("āœ… Values from .env.local have been written to .env"); } } } catch (error) { // Error reading .env.local, which is fine - console.log('Note: No .env.local file found'); + console.log("Note: No .env.local file found"); } } // TODO: make sure rebuilding is supported const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const projectRoot = path.join(__dirname, '..'); +const projectRoot = path.join(__dirname, ".."); async function validateDomain(domain) { // Remove http:// or https:// if present - const cleanDomain = domain.replace(/^https?:\/\//, ''); - + const cleanDomain = domain.replace(/^https?:\/\//, ""); + // Basic domain validation - if (!cleanDomain.match(/^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9](?:\.[a-zA-Z]{2,})+$/)) { - throw new Error('Invalid domain format'); + if ( + !cleanDomain.match( + /^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9](?:\.[a-zA-Z]{2,})+$/ + ) + ) { + throw new Error("Invalid domain format"); } return cleanDomain; @@ -120,54 +83,26 @@ async function queryNeynarApp(apiKey) { `https://api.neynar.com/portal/app_by_api_key`, { headers: { - 'x-api-key': apiKey - } + "x-api-key": apiKey, + }, } ); const data = await response.json(); return data; } catch (error) { - console.error('Error querying Neynar app data:', error); + console.error("Error querying Neynar app data:", error); return null; } } -async function validateSeedPhrase(seedPhrase) { - try { - // Try to create an account from the seed phrase - const account = mnemonicToAccount(seedPhrase); - return account.address; - } catch (error) { - throw new Error('Invalid seed phrase'); - } -} - -async function generateFarcasterMetadata(domain, fid, accountAddress, seedPhrase, webhookUrl) { - const header = { - type: 'custody', - key: accountAddress, - fid, - }; - const encodedHeader = Buffer.from(JSON.stringify(header), 'utf-8').toString('base64'); - - const payload = { - domain - }; - const encodedPayload = Buffer.from(JSON.stringify(payload), 'utf-8').toString('base64url'); - - const account = mnemonicToAccount(seedPhrase); - const signature = await account.signMessage({ - message: `${encodedHeader}.${encodedPayload}` - }); - const encodedSignature = Buffer.from(signature, 'utf-8').toString('base64url'); - - const tags = process.env.NEXT_PUBLIC_MINI_APP_TAGS?.split(','); +async function generateFarcasterMetadata(domain, webhookUrl) { + const tags = process.env.NEXT_PUBLIC_MINI_APP_TAGS?.split(","); return { accountAssociation: { - header: encodedHeader, - payload: encodedPayload, - signature: encodedSignature + header: "", + payload: "", + signature: "", }, frame: { version: "1", @@ -188,18 +123,19 @@ async function generateFarcasterMetadata(domain, fid, accountAddress, seedPhrase async function main() { try { - console.log('\nšŸ“ Checking environment variables...'); - console.log('Loading values from .env...'); - + console.log("\nšŸ“ Checking environment variables..."); + console.log("Loading values from .env..."); + // Load .env.local if user wants to await loadEnvLocal(); // Get domain from user const { domain } = await inquirer.prompt([ { - type: 'input', - name: 'domain', - message: 'Enter the domain where your mini app will be deployed (e.g., example.com):', + type: "input", + name: "domain", + message: + "Enter the domain where your mini app will be deployed (e.g., example.com):", validate: async (input) => { try { await validateDomain(input); @@ -207,40 +143,41 @@ async function main() { } catch (error) { return error.message; } - } - } + }, + }, ]); // Get frame name from user const { frameName } = await inquirer.prompt([ { - type: 'input', - name: 'frameName', - message: 'Enter the name for your mini app (e.g., My Cool Mini App):', + type: "input", + name: "frameName", + message: "Enter the name for your mini app (e.g., My Cool Mini App):", default: process.env.NEXT_PUBLIC_MINI_APP_NAME, validate: (input) => { - if (input.trim() === '') { - return 'Mini app name cannot be empty'; + if (input.trim() === "") { + return "Mini app name cannot be empty"; } return true; - } - } + }, + }, ]); // Get button text from user const { buttonText } = await inquirer.prompt([ { - type: 'input', - name: 'buttonText', - message: 'Enter the text for your mini app button:', - default: process.env.NEXT_PUBLIC_MINI_APP_BUTTON_TEXT || 'Launch Mini App', + type: "input", + name: "buttonText", + message: "Enter the text for your mini app button:", + default: + process.env.NEXT_PUBLIC_MINI_APP_BUTTON_TEXT || "Launch Mini App", validate: (input) => { - if (input.trim() === '') { - return 'Button text cannot be empty'; + if (input.trim() === "") { + return "Button text cannot be empty"; } return true; - } - } + }, + }, ]); // Get Neynar configuration @@ -252,15 +189,16 @@ async function main() { if (!neynarApiKey) { const { neynarApiKey: inputNeynarApiKey } = await inquirer.prompt([ { - type: 'password', - name: 'neynarApiKey', - message: 'Enter your Neynar API key (optional - leave blank to skip):', - default: null - } + type: "password", + name: "neynarApiKey", + message: + "Enter your Neynar API key (optional - leave blank to skip):", + default: null, + }, ]); neynarApiKey = inputNeynarApiKey; } else { - console.log('Using existing Neynar API key from .env'); + console.log("Using existing Neynar API key from .env"); } if (!neynarApiKey) { @@ -273,7 +211,7 @@ async function main() { const appInfo = await queryNeynarApp(neynarApiKey); if (appInfo) { neynarClientId = appInfo.app_uuid; - console.log('āœ… Fetched Neynar app client ID'); + console.log("āœ… Fetched Neynar app client ID"); break; } } @@ -284,14 +222,16 @@ async function main() { } // If we get here, the API key was invalid - console.log('\nāš ļø Could not find Neynar app information. The API key may be incorrect.'); + console.log( + "\nāš ļø Could not find Neynar app information. The API key may be incorrect." + ); const { retry } = await inquirer.prompt([ { - type: 'confirm', - name: 'retry', - message: 'Would you like to try a different API key?', - default: true - } + type: "confirm", + name: "retry", + message: "Would you like to try a different API key?", + default: true, + }, ]); // Reset for retry @@ -304,51 +244,23 @@ async function main() { } } - // Get seed phrase from user - let seedPhrase = process.env.SEED_PHRASE; - if (!seedPhrase) { - const { seedPhrase: inputSeedPhrase } = await inquirer.prompt([ - { - type: 'password', - name: 'seedPhrase', - 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 mini app manifest, then discarded${reset} āš ļø\n` + - 'Seed phrase:', - validate: async (input) => { - try { - await validateSeedPhrase(input); - return true; - } catch (error) { - return error.message; - } - } - } - ]); - seedPhrase = inputSeedPhrase; - } else { - console.log('Using existing seed phrase from .env'); - } + // Generate manifest + console.log("\nšŸ”Ø Generating mini app manifest..."); - // Validate seed phrase and get account address - const accountAddress = await validateSeedPhrase(seedPhrase); - console.log('āœ… Generated account address from seed phrase'); - - const fid = await lookupFidByCustodyAddress(accountAddress, neynarApiKey ?? 'FARCASTER_V2_FRAMES_DEMO'); - - // Generate and sign manifest - console.log('\nšŸ”Ø Generating mini app manifest...'); - // Determine webhook URL based on environment variables - const webhookUrl = neynarApiKey && neynarClientId - ? `https://api.neynar.com/f/app/${neynarClientId}/event` - : `${domain}/api/webhook`; + const webhookUrl = + neynarApiKey && neynarClientId + ? `https://api.neynar.com/f/app/${neynarClientId}/event` + : `https://${domain}/api/webhook`; - const metadata = await generateFarcasterMetadata(domain, fid, accountAddress, seedPhrase, webhookUrl); - console.log('\nāœ… Mini app manifest generated' + (seedPhrase ? ' and signed' : '')); + const metadata = await generateFarcasterMetadata(domain, webhookUrl); + console.log("\nāœ… Mini app manifest generated"); // Read existing .env file or create new one - const envPath = path.join(projectRoot, '.env'); - let envContent = fs.existsSync(envPath) ? fs.readFileSync(envPath, 'utf8') : ''; + const envPath = path.join(projectRoot, ".env"); + let envContent = fs.existsSync(envPath) + ? fs.readFileSync(envPath, "utf8") + : ""; // Add or update environment variables const newEnvVars = [ @@ -357,26 +269,38 @@ async function main() { // Mini app metadata `NEXT_PUBLIC_MINI_APP_NAME="${frameName}"`, - `NEXT_PUBLIC_MINI_APP_DESCRIPTION="${process.env.NEXT_PUBLIC_MINI_APP_DESCRIPTION || ''}"`, - `NEXT_PUBLIC_MINI_APP_PRIMARY_CATEGORY="${process.env.NEXT_PUBLIC_MINI_APP_PRIMARY_CATEGORY || ''}"`, - `NEXT_PUBLIC_MINI_APP_TAGS="${process.env.NEXT_PUBLIC_MINI_APP_TAGS || ''}"`, + `NEXT_PUBLIC_MINI_APP_DESCRIPTION="${ + process.env.NEXT_PUBLIC_MINI_APP_DESCRIPTION || "" + }"`, + `NEXT_PUBLIC_MINI_APP_PRIMARY_CATEGORY="${ + process.env.NEXT_PUBLIC_MINI_APP_PRIMARY_CATEGORY || "" + }"`, + `NEXT_PUBLIC_MINI_APP_TAGS="${ + process.env.NEXT_PUBLIC_MINI_APP_TAGS || "" + }"`, `NEXT_PUBLIC_MINI_APP_BUTTON_TEXT="${buttonText}"`, // Analytics - `NEXT_PUBLIC_ANALYTICS_ENABLED="${process.env.NEXT_PUBLIC_ANALYTICS_ENABLED || 'false'}"`, + `NEXT_PUBLIC_ANALYTICS_ENABLED="${ + process.env.NEXT_PUBLIC_ANALYTICS_ENABLED || "false" + }"`, // Neynar configuration (if it exists in current env) - ...(process.env.NEYNAR_API_KEY ? - [`NEYNAR_API_KEY="${process.env.NEYNAR_API_KEY}"`] : []), - ...(neynarClientId ? - [`NEYNAR_CLIENT_ID="${neynarClientId}"`] : []), + ...(process.env.NEYNAR_API_KEY + ? [`NEYNAR_API_KEY="${process.env.NEYNAR_API_KEY}"`] + : []), + ...(neynarClientId ? [`NEYNAR_CLIENT_ID="${neynarClientId}"`] : []), // FID (if it exists in current env) ...(process.env.FID ? [`FID="${process.env.FID}"`] : []), - `NEXT_PUBLIC_USE_WALLET="${process.env.NEXT_PUBLIC_USE_WALLET || 'false'}"`, + `NEXT_PUBLIC_USE_WALLET="${ + process.env.NEXT_PUBLIC_USE_WALLET || "false" + }"`, // NextAuth configuration - `NEXTAUTH_SECRET="${process.env.NEXTAUTH_SECRET || crypto.randomBytes(32).toString('hex')}"`, + `NEXTAUTH_SECRET="${ + process.env.NEXTAUTH_SECRET || crypto.randomBytes(32).toString("hex") + }"`, `NEXTAUTH_URL="https://${domain}"`, // Mini app manifest with signature @@ -384,14 +308,14 @@ async function main() { ]; // Filter out empty values and join with newlines - const validEnvVars = newEnvVars.filter(line => { - const [, value] = line.split('='); + const validEnvVars = newEnvVars.filter((line) => { + const [, value] = line.split("="); return value && value !== '""'; }); // Update or append each environment variable - validEnvVars.forEach(varLine => { - const [key] = varLine.split('='); + validEnvVars.forEach((varLine) => { + const [key] = varLine.split("="); if (envContent.includes(`${key}=`)) { envContent = envContent.replace(new RegExp(`${key}=.*`), varLine); } else { @@ -402,22 +326,27 @@ async function main() { // Write updated .env file fs.writeFileSync(envPath, envContent); - console.log('\nāœ… Environment variables updated'); + console.log("\nāœ… Environment variables updated"); // Run next build - console.log('\nBuilding Next.js application...'); - const nextBin = path.normalize(path.join(projectRoot, 'node_modules', '.bin', 'next')); - execSync(`"${nextBin}" build`, { - cwd: projectRoot, - stdio: 'inherit', - shell: process.platform === 'win32' + console.log("\nBuilding Next.js application..."); + const nextBin = path.normalize( + path.join(projectRoot, "node_modules", ".bin", "next") + ); + execSync(`"${nextBin}" build`, { + cwd: projectRoot, + stdio: "inherit", + shell: process.platform === "win32", }); - 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( + "\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" + ); } catch (error) { - console.error('\nāŒ Error:', error.message); + console.error("\nāŒ Error:", error.message); process.exit(1); } } diff --git a/scripts/deploy.js b/scripts/deploy.js index 80c8db2..23d0d77 100755 --- a/scripts/deploy.js +++ b/scripts/deploy.js @@ -1,85 +1,24 @@ -import { execSync, spawn } from 'child_process'; -import fs from 'fs'; -import path from 'path'; -import os from 'os'; -import { fileURLToPath } from 'url'; -import inquirer from 'inquirer'; -import dotenv from 'dotenv'; -import crypto from 'crypto'; -import { mnemonicToAccount } from 'viem/accounts'; -import { Vercel } from '@vercel/sdk'; +import { execSync, spawn } from "child_process"; +import fs from "fs"; +import path from "path"; +import os from "os"; +import { fileURLToPath } from "url"; +import inquirer from "inquirer"; +import dotenv from "dotenv"; +import crypto from "crypto"; +import { Vercel } from "@vercel/sdk"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const projectRoot = path.join(__dirname, '..'); +const projectRoot = path.join(__dirname, ".."); // Load environment variables in specific order -dotenv.config({ path: '.env' }); +dotenv.config({ path: ".env" }); -async function validateSeedPhrase(seedPhrase) { - try { - const account = mnemonicToAccount(seedPhrase); - return account.address; - } catch (error) { - throw new Error('Invalid seed phrase'); - } -} - -async function lookupFidByCustodyAddress(custodyAddress, apiKey) { - if (!apiKey) { - throw new Error('Neynar API key is required'); - } - const lowerCasedCustodyAddress = custodyAddress.toLowerCase(); - - const response = await fetch( - `https://api.neynar.com/v2/farcaster/user/bulk-by-address?addresses=${lowerCasedCustodyAddress}&address_types=custody_address`, - { - headers: { - 'accept': 'application/json', - 'x-api-key': apiKey - } - } - ); - - if (!response.ok) { - throw new Error(`Failed to lookup FID: ${response.statusText}`); - } - - const data = await response.json(); - if (!data[lowerCasedCustodyAddress]?.length || !data[lowerCasedCustodyAddress][0].custody_address) { - throw new Error('No FID found for this custody address'); - } - - return data[lowerCasedCustodyAddress][0].fid; -} - -async function generateFarcasterMetadata(domain, fid, accountAddress, seedPhrase, webhookUrl) { +async function generateFarcasterMetadata(domain, webhookUrl) { const trimmedDomain = domain.trim(); - const header = { - type: 'custody', - key: accountAddress, - fid, - }; - const encodedHeader = Buffer.from(JSON.stringify(header), 'utf-8').toString('base64'); - - const payload = { - domain: trimmedDomain - }; - const encodedPayload = Buffer.from(JSON.stringify(payload), 'utf-8').toString('base64url'); - - const account = mnemonicToAccount(seedPhrase); - const signature = await account.signMessage({ - message: `${encodedHeader}.${encodedPayload}` - }); - const encodedSignature = Buffer.from(signature, 'utf-8').toString('base64url'); - - const tags = process.env.NEXT_PUBLIC_MINI_APP_TAGS?.split(','); + const tags = process.env.NEXT_PUBLIC_MINI_APP_TAGS?.split(","); return { - accountAssociation: { - header: encodedHeader, - payload: encodedPayload, - signature: encodedSignature - }, frame: { version: "1", name: process.env.NEXT_PUBLIC_MINI_APP_NAME, @@ -99,130 +38,107 @@ async function generateFarcasterMetadata(domain, fid, accountAddress, seedPhrase async function loadEnvLocal() { try { - if (fs.existsSync('.env.local')) { + if (fs.existsSync(".env.local")) { const { loadLocal } = await inquirer.prompt([ { - type: 'confirm', - name: 'loadLocal', - message: 'Found .env.local - would you like to load its values in addition to .env values? (except for SEED_PHRASE, values will be written to .env)', - default: true - } + type: "confirm", + name: "loadLocal", + message: + "Found .env.local - would you like to load its values in addition to .env values?", + default: true, + }, ]); if (loadLocal) { - console.log('Loading values from .env.local...'); - const localEnv = dotenv.parse(fs.readFileSync('.env.local')); - + console.log("Loading values from .env.local..."); + const localEnv = dotenv.parse(fs.readFileSync(".env.local")); + const allowedVars = [ - 'SEED_PHRASE', - 'NEXT_PUBLIC_MINI_APP_NAME', - 'NEXT_PUBLIC_MINI_APP_DESCRIPTION', - 'NEXT_PUBLIC_MINI_APP_PRIMARY_CATEGORY', - 'NEXT_PUBLIC_MINI_APP_TAGS', - 'NEXT_PUBLIC_MINI_APP_BUTTON_TEXT', - 'NEXT_PUBLIC_ANALYTICS_ENABLED', - 'NEYNAR_API_KEY', - 'NEYNAR_CLIENT_ID' + "NEXT_PUBLIC_MINI_APP_NAME", + "NEXT_PUBLIC_MINI_APP_DESCRIPTION", + "NEXT_PUBLIC_MINI_APP_PRIMARY_CATEGORY", + "NEXT_PUBLIC_MINI_APP_TAGS", + "NEXT_PUBLIC_MINI_APP_BUTTON_TEXT", + "NEXT_PUBLIC_ANALYTICS_ENABLED", + "NEYNAR_API_KEY", + "NEYNAR_CLIENT_ID", ]; - - const envContent = fs.existsSync('.env') ? fs.readFileSync('.env', 'utf8') + '\n' : ''; + + const envContent = fs.existsSync(".env") + ? fs.readFileSync(".env", "utf8") + "\n" + : ""; let newEnvContent = envContent; - + for (const [key, value] of Object.entries(localEnv)) { if (allowedVars.includes(key)) { process.env[key] = value; - if (key !== 'SEED_PHRASE' && !envContent.includes(`${key}=`)) { + if (!envContent.includes(`${key}=`)) { newEnvContent += `${key}="${value}"\n`; } } } - - fs.writeFileSync('.env', newEnvContent); - console.log('āœ… Values from .env.local have been written to .env'); + + fs.writeFileSync(".env", newEnvContent); + console.log("āœ… Values from .env.local have been written to .env"); } } } catch (error) { - console.log('Note: No .env.local file found'); + console.log("Note: No .env.local file found"); } } async function checkRequiredEnvVars() { - console.log('\nšŸ“ Checking environment variables...'); - console.log('Loading values from .env...'); - + console.log("\nšŸ“ Checking environment variables..."); + console.log("Loading values from .env..."); + await loadEnvLocal(); const requiredVars = [ { - name: 'NEXT_PUBLIC_MINI_APP_NAME', - message: 'Enter the name for your frame (e.g., My Cool Mini App):', + name: "NEXT_PUBLIC_MINI_APP_NAME", + message: "Enter the name for your frame (e.g., My Cool Mini App):", default: process.env.NEXT_PUBLIC_MINI_APP_NAME, - validate: input => input.trim() !== '' || 'Mini app name cannot be empty' + validate: (input) => + input.trim() !== "" || "Mini app name cannot be empty", }, { - name: 'NEXT_PUBLIC_MINI_APP_BUTTON_TEXT', - message: 'Enter the text for your frame button:', - default: process.env.NEXT_PUBLIC_MINI_APP_BUTTON_TEXT ?? 'Launch Mini App', - validate: input => input.trim() !== '' || 'Button text cannot be empty' - } + name: "NEXT_PUBLIC_MINI_APP_BUTTON_TEXT", + message: "Enter the text for your frame button:", + default: + process.env.NEXT_PUBLIC_MINI_APP_BUTTON_TEXT ?? "Launch Mini App", + validate: (input) => input.trim() !== "" || "Button text cannot be empty", + }, ]; - const missingVars = requiredVars.filter(varConfig => !process.env[varConfig.name]); - + const missingVars = requiredVars.filter( + (varConfig) => !process.env[varConfig.name] + ); + if (missingVars.length > 0) { - console.log('\nāš ļø Some required information is missing. Let\'s set it up:'); + console.log("\nāš ļø Some required information is missing. Let's set it up:"); for (const varConfig of missingVars) { const { value } = await inquirer.prompt([ { - type: 'input', - name: 'value', + type: "input", + name: "value", message: varConfig.message, default: varConfig.default, - validate: varConfig.validate - } + validate: varConfig.validate, + }, ]); - + process.env[varConfig.name] = value; - - const envContent = fs.existsSync('.env') ? fs.readFileSync('.env', 'utf8') : ''; - + + const envContent = fs.existsSync(".env") + ? fs.readFileSync(".env", "utf8") + : ""; + if (!envContent.includes(`${varConfig.name}=`)) { - const newLine = envContent ? '\n' : ''; - fs.appendFileSync('.env', `${newLine}${varConfig.name}="${value.trim()}"`); - } - } - } - - // Check for seed phrase - if (!process.env.SEED_PHRASE) { - console.log('\nšŸ”‘ Mini App Manifest Signing'); - console.log('A signed manifest helps users trust your mini app.'); - const { seedPhrase } = await inquirer.prompt([ - { - type: 'password', - name: 'seedPhrase', - 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 - } - ]); - - if (seedPhrase) { - process.env.SEED_PHRASE = seedPhrase; - - const { storeSeedPhrase } = await inquirer.prompt([ - { - type: 'confirm', - name: 'storeSeedPhrase', - message: 'Would you like to store this seed phrase in .env.local for future use?', - default: false - } - ]); - - if (storeSeedPhrase) { - fs.appendFileSync('.env.local', `\nSEED_PHRASE="${seedPhrase}"`); - console.log('āœ… Seed phrase stored in .env.local'); - } else { - console.log('ā„¹ļø Seed phrase will only be used for this deployment'); + const newLine = envContent ? "\n" : ""; + fs.appendFileSync( + ".env", + `${newLine}${varConfig.name}="${value.trim()}"` + ); } } } @@ -230,9 +146,9 @@ async function checkRequiredEnvVars() { async function getGitRemote() { try { - const remoteUrl = execSync('git remote get-url origin', { + const remoteUrl = execSync("git remote get-url origin", { cwd: projectRoot, - encoding: 'utf8' + encoding: "utf8", }).trim(); return remoteUrl; } catch (error) { @@ -242,9 +158,9 @@ async function getGitRemote() { async function checkVercelCLI() { try { - execSync('vercel --version', { - stdio: 'ignore', - shell: process.platform === 'win32' + execSync("vercel --version", { + stdio: "ignore", + shell: process.platform === "win32", }); return true; } catch (error) { @@ -253,95 +169,101 @@ async function checkVercelCLI() { } async function installVercelCLI() { - console.log('Installing Vercel CLI...'); - execSync('npm install -g vercel', { - stdio: 'inherit', - shell: process.platform === 'win32' + console.log("Installing Vercel CLI..."); + execSync("npm install -g vercel", { + stdio: "inherit", + shell: process.platform === "win32", }); } async function getVercelToken() { try { // 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)) { - const authConfig = JSON.parse(fs.readFileSync(configPath, 'utf8')); + const authConfig = JSON.parse(fs.readFileSync(configPath, "utf8")); return authConfig.token; } } 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 if (process.env.VERCEL_TOKEN) { return process.env.VERCEL_TOKEN; } - + // Try to extract from vercel whoami try { - const whoamiOutput = execSync('vercel whoami', { - encoding: 'utf8', - stdio: 'pipe' + const whoamiOutput = execSync("vercel whoami", { + encoding: "utf8", + stdio: "pipe", }); - + // 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 - console.log('āœ… Verified Vercel CLI authentication'); + console.log("āœ… Verified Vercel CLI authentication"); return null; // We'll fall back to CLI operations } catch (error) { - throw new Error('Not logged in to Vercel CLI. Please run this script again to login.'); + throw new Error( + "Not logged in to Vercel CLI. Please run this script again to login." + ); } } async function loginToVercel() { - console.log('\nšŸ”‘ Vercel Login'); - console.log('You can either:'); - console.log('1. Log in to an existing Vercel account'); - console.log('2. Create a new Vercel account during login\n'); - console.log('If creating a new account:'); + console.log("\nšŸ”‘ Vercel Login"); + console.log("You can either:"); + console.log("1. Log in to an existing Vercel account"); + console.log("2. Create a new Vercel account during login\n"); + console.log("If creating a new account:"); console.log('1. Click "Continue with GitHub"'); - console.log('2. Authorize GitHub access'); - 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('\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'], { - stdio: 'inherit' + console.log("2. Authorize GitHub access"); + 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( + "\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"], { + stdio: "inherit", }); await new Promise((resolve, reject) => { - child.on('close', (code) => { + child.on("close", (code) => { resolve(); }); }); - console.log('\nšŸ“± Waiting for login to complete...'); - console.log('If you\'re creating a new account, please complete the Vercel account setup in your browser first.'); - + console.log("\nšŸ“± Waiting for login to complete..."); + console.log( + "If you're creating a new account, please complete the Vercel account setup in your browser first." + ); + for (let i = 0; i < 150; i++) { try { - execSync('vercel whoami', { stdio: 'ignore' }); - console.log('āœ… Successfully logged in to Vercel!'); + execSync("vercel whoami", { stdio: "ignore" }); + console.log("āœ… Successfully logged in to Vercel!"); return true; } catch (error) { - if (error.message.includes('Account not found')) { - console.log('ā„¹ļø Waiting for Vercel account setup to complete...'); + if (error.message.includes("Account not found")) { + 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('1. Completed the Vercel account setup in your browser'); - console.error('2. Authorized the GitHub integration'); - console.error('Then try running this script again.'); + console.error("\nāŒ Login timed out. Please ensure you have:"); + console.error("1. Completed the Vercel account setup in your browser"); + console.error("2. Authorized the GitHub integration"); + console.error("Then try running this script again."); return false; } async function setVercelEnvVarSDK(vercelClient, projectId, key, value) { try { let processedValue; - if (typeof value === 'object') { + if (typeof value === "object") { processedValue = JSON.stringify(value); } else { processedValue = value.toString(); @@ -349,11 +271,11 @@ async function setVercelEnvVarSDK(vercelClient, projectId, key, value) { // Get existing environment variables const existingVars = await vercelClient.projects.getEnvironmentVariables({ - idOrName: projectId + idOrName: projectId, }); - const existingVar = existingVars.envs?.find(env => - env.key === key && env.target?.includes('production') + const existingVar = existingVars.envs?.find( + (env) => env.key === key && env.target?.includes("production") ); if (existingVar) { @@ -363,8 +285,8 @@ async function setVercelEnvVarSDK(vercelClient, projectId, key, value) { id: existingVar.id, requestBody: { value: processedValue, - target: ['production'] - } + target: ["production"], + }, }); console.log(`āœ… Updated environment variable: ${key}`); } else { @@ -374,16 +296,19 @@ async function setVercelEnvVarSDK(vercelClient, projectId, key, value) { requestBody: { key: key, value: processedValue, - type: 'encrypted', - target: ['production'] - } + type: "encrypted", + target: ["production"], + }, }); console.log(`āœ… Created environment variable: ${key}`); } - + return true; } catch (error) { - console.warn(`āš ļø Warning: Failed to set environment variable ${key}:`, error.message); + console.warn( + `āš ļø Warning: Failed to set environment variable ${key}:`, + error.message + ); return false; } } @@ -394,15 +319,15 @@ async function setVercelEnvVarCLI(key, value, projectRoot) { try { execSync(`vercel env rm ${key} production -y`, { cwd: projectRoot, - stdio: 'ignore', - env: process.env + stdio: "ignore", + env: process.env, }); } catch (error) { // Ignore errors from removal } let processedValue; - if (typeof value === 'object') { + if (typeof value === "object") { processedValue = JSON.stringify(value); } else { processedValue = value.toString(); @@ -410,11 +335,11 @@ async function setVercelEnvVarCLI(key, value, projectRoot) { // Create temporary file const tempFilePath = path.join(projectRoot, `${key}_temp.txt`); - fs.writeFileSync(tempFilePath, processedValue, 'utf8'); + fs.writeFileSync(tempFilePath, processedValue, "utf8"); // Use appropriate command based on platform let command; - if (process.platform === 'win32') { + if (process.platform === "win32") { command = `type "${tempFilePath}" | vercel env add ${key} production`; } else { command = `cat "${tempFilePath}" | vercel env add ${key} production`; @@ -422,9 +347,9 @@ async function setVercelEnvVarCLI(key, value, projectRoot) { execSync(command, { cwd: projectRoot, - stdio: 'pipe', // Changed from 'inherit' to avoid interactive prompts + stdio: "pipe", // Changed from 'inherit' to avoid interactive prompts shell: true, - env: process.env + env: process.env, }); fs.unlinkSync(tempFilePath); @@ -435,42 +360,52 @@ async function setVercelEnvVarCLI(key, value, projectRoot) { if (fs.existsSync(tempFilePath)) { fs.unlinkSync(tempFilePath); } - console.warn(`āš ļø Warning: Failed to set environment variable ${key}:`, error.message); + console.warn( + `āš ļø Warning: Failed to set environment variable ${key}:`, + error.message + ); return false; } } -async function setEnvironmentVariables(vercelClient, projectId, envVars, projectRoot) { - console.log('\nšŸ“ Setting up environment variables...'); - +async function setEnvironmentVariables( + vercelClient, + projectId, + envVars, + projectRoot +) { + console.log("\nšŸ“ Setting up environment variables..."); + const results = []; - + for (const [key, value] of Object.entries(envVars)) { if (!value) continue; - + let success = false; - + // Try SDK approach first if we have a Vercel client if (vercelClient && projectId) { success = await setVercelEnvVarSDK(vercelClient, projectId, key, value); } - + // Fallback to CLI approach if (!success) { success = await setVercelEnvVarCLI(key, value, projectRoot); } - + results.push({ key, success }); } - + // Report results - const failed = results.filter(r => !r.success); + const failed = results.filter((r) => !r.success); if (failed.length > 0) { console.warn(`\nāš ļø Failed to set ${failed.length} environment variables:`); - failed.forEach(r => console.warn(` - ${r.key}`)); - console.warn('\nYou may need to set these manually in the Vercel dashboard.'); + failed.forEach((r) => console.warn(` - ${r.key}`)); + console.warn( + "\nYou may need to set these manually in the Vercel dashboard." + ); } - + return results; } @@ -515,16 +450,23 @@ async function waitForDeployment(vercelClient, projectId, maxWaitTime = 300000) async function deployToVercel(useGitHub = false) { try { - console.log('\nšŸš€ Deploying to Vercel...'); - + console.log("\nšŸš€ Deploying to Vercel..."); + // Ensure vercel.json exists - const vercelConfigPath = path.join(projectRoot, 'vercel.json'); + const vercelConfigPath = path.join(projectRoot, "vercel.json"); if (!fs.existsSync(vercelConfigPath)) { - console.log('šŸ“ Creating vercel.json configuration...'); - fs.writeFileSync(vercelConfigPath, JSON.stringify({ - buildCommand: "next build", - framework: "nextjs" - }, null, 2)); + console.log("šŸ“ Creating vercel.json configuration..."); + fs.writeFileSync( + vercelConfigPath, + JSON.stringify( + { + buildCommand: "next build", + framework: "nextjs", + }, + null, + 2 + ) + ); } // Set up Vercel project @@ -536,8 +478,8 @@ async function deployToVercel(useGitHub = false) { const { spawn } = await import('child_process'); const vercelSetup = spawn('vercel', [], { cwd: projectRoot, - stdio: 'inherit', - shell: process.platform === 'win32' + stdio: "inherit", + shell: process.platform === "win32", }); await new Promise((resolve, reject) => { @@ -575,29 +517,33 @@ async function deployToVercel(useGitHub = false) { const token = await getVercelToken(); if (token) { vercelClient = new Vercel({ - bearerToken: token + bearerToken: token, }); - console.log('āœ… Initialized Vercel SDK client'); + console.log("āœ… Initialized Vercel SDK client"); } } catch (error) { - console.warn('āš ļø Could not initialize Vercel SDK, falling back to CLI operations'); + console.warn( + "āš ļø Could not initialize Vercel SDK, falling back to CLI operations" + ); } // Get project details - console.log('\nšŸ” Getting project details...'); + console.log("\nšŸ” Getting project details..."); let domain; let projectName; if (vercelClient) { try { const project = await vercelClient.projects.get({ - idOrName: projectId + idOrName: projectId, }); projectName = project.name; domain = `${projectName}.vercel.app`; - console.log('🌐 Using project name for domain:', domain); + console.log("🌐 Using project name for domain:", domain); } catch (error) { - console.warn('āš ļø Could not get project details via SDK, using CLI fallback'); + console.warn( + "āš ļø Could not get project details via SDK, using CLI fallback" + ); } } @@ -613,7 +559,7 @@ async function deployToVercel(useGitHub = false) { if (nameMatch) { projectName = nameMatch[1].trim(); domain = `${projectName}.vercel.app`; - console.log('🌐 Using project name for domain:', domain); + console.log("🌐 Using project name for domain:", domain); } else { const altMatch = inspectOutput.match(/Found Project [^/]+\/([^\n]+)/); if (altMatch) { @@ -635,61 +581,67 @@ async function deployToVercel(useGitHub = false) { } } - // Generate mini app metadata if we have a seed phrase - let miniAppMetadata; - let fid; - if (process.env.SEED_PHRASE) { - console.log('\nšŸ”Ø Generating mini app metadata...'); - const accountAddress = await validateSeedPhrase(process.env.SEED_PHRASE); - fid = await lookupFidByCustodyAddress(accountAddress, process.env.NEYNAR_API_KEY ?? 'FARCASTER_V2_FRAMES_DEMO'); - - const webhookUrl = process.env.NEYNAR_API_KEY && process.env.NEYNAR_CLIENT_ID + // Generate mini app metadata + console.log("\nšŸ”Ø Generating mini app metadata..."); + + const webhookUrl = + process.env.NEYNAR_API_KEY && process.env.NEYNAR_CLIENT_ID ? `https://api.neynar.com/f/app/${process.env.NEYNAR_CLIENT_ID}/event` : `https://${domain}/api/webhook`; - miniAppMetadata = await generateFarcasterMetadata(domain, fid, accountAddress, process.env.SEED_PHRASE, webhookUrl); - console.log('āœ… Mini app metadata generated and signed'); - } + const miniAppMetadata = await generateFarcasterMetadata(domain, webhookUrl); + console.log("āœ… Mini app metadata generated"); // Prepare environment variables - const nextAuthSecret = process.env.NEXTAUTH_SECRET || crypto.randomBytes(32).toString('hex'); + const nextAuthSecret = + process.env.NEXTAUTH_SECRET || crypto.randomBytes(32).toString("hex"); const vercelEnv = { NEXTAUTH_SECRET: nextAuthSecret, AUTH_SECRET: nextAuthSecret, NEXTAUTH_URL: `https://${domain}`, NEXT_PUBLIC_URL: `https://${domain}`, - - ...(process.env.NEYNAR_API_KEY && { NEYNAR_API_KEY: process.env.NEYNAR_API_KEY }), - ...(process.env.NEYNAR_CLIENT_ID && { NEYNAR_CLIENT_ID: process.env.NEYNAR_CLIENT_ID }), + + ...(process.env.NEYNAR_API_KEY && { + NEYNAR_API_KEY: process.env.NEYNAR_API_KEY, + }), + ...(process.env.NEYNAR_CLIENT_ID && { + NEYNAR_CLIENT_ID: process.env.NEYNAR_CLIENT_ID, + }), ...(miniAppMetadata && { MINI_APP_METADATA: miniAppMetadata }), - + ...Object.fromEntries( - Object.entries(process.env) - .filter(([key]) => key.startsWith('NEXT_PUBLIC_')) - ) + Object.entries(process.env).filter(([key]) => + key.startsWith("NEXT_PUBLIC_") + ) + ), }; // Set environment variables - await setEnvironmentVariables(vercelClient, projectId, vercelEnv, projectRoot); + await setEnvironmentVariables( + vercelClient, + projectId, + vercelEnv, + projectRoot + ); // Deploy the project if (useGitHub) { - console.log('\nSetting up GitHub integration...'); - execSync('vercel link', { + console.log("\nSetting up GitHub integration..."); + execSync("vercel link", { cwd: projectRoot, - stdio: 'inherit', - env: process.env + stdio: "inherit", + env: process.env, }); - console.log('\nšŸ“¦ Deploying with GitHub integration...'); + console.log("\nšŸ“¦ Deploying with GitHub integration..."); } else { - console.log('\nšŸ“¦ Deploying local code directly...'); + console.log("\nšŸ“¦ Deploying local code directly..."); } // Use spawn for better control over the deployment process const vercelDeploy = spawn('vercel', ['deploy', '--prod'], { cwd: projectRoot, - stdio: 'inherit', - env: process.env + stdio: "inherit", + env: process.env, }); await new Promise((resolve, reject) => { @@ -721,29 +673,32 @@ async function deployToVercel(useGitHub = false) { } // Verify actual domain after deployment - console.log('\nšŸ” Verifying deployment domain...'); - + console.log("\nšŸ” Verifying deployment domain..."); + let actualDomain = domain; if (vercelClient && deployment) { try { actualDomain = deployment.url || domain; console.log('🌐 Verified actual domain:', actualDomain); } catch (error) { - console.warn('āš ļø Could not verify domain via SDK, using assumed domain'); + console.warn( + "āš ļø Could not verify domain via SDK, using assumed domain" + ); } } // Update environment variables if domain changed if (actualDomain !== domain) { - console.log('šŸ”„ Updating environment variables with correct domain...'); - - const webhookUrl = process.env.NEYNAR_API_KEY && process.env.NEYNAR_CLIENT_ID - ? `https://api.neynar.com/f/app/${process.env.NEYNAR_CLIENT_ID}/event` - : `https://${actualDomain}/api/webhook`; + console.log("šŸ”„ Updating environment variables with correct domain..."); + + const webhookUrl = + process.env.NEYNAR_API_KEY && process.env.NEYNAR_CLIENT_ID + ? `https://api.neynar.com/f/app/${process.env.NEYNAR_CLIENT_ID}/event` + : `https://${actualDomain}/api/webhook`; const updatedEnv = { NEXTAUTH_URL: `https://${actualDomain}`, - NEXT_PUBLIC_URL: `https://${actualDomain}` + NEXT_PUBLIC_URL: `https://${actualDomain}`, }; if (miniAppMetadata) { @@ -756,8 +711,8 @@ async function deployToVercel(useGitHub = false) { console.log('\nšŸ“¦ Redeploying with correct domain...'); const vercelRedeploy = spawn('vercel', ['deploy', '--prod'], { cwd: projectRoot, - stdio: 'inherit', - env: process.env + stdio: "inherit", + env: process.env, }); await new Promise((resolve, reject) => { @@ -779,37 +734,40 @@ async function deployToVercel(useGitHub = false) { domain = actualDomain; } - - console.log('\n✨ Deployment complete! Your mini app is now live at:'); - console.log(`🌐 https://${domain}`); - console.log('\nšŸ“ You can manage your project at https://vercel.com/dashboard'); + console.log("\n✨ Deployment complete! Your mini app is now live at:"); + console.log(`🌐 https://${domain}`); + console.log( + "\nšŸ“ You can manage your project at https://vercel.com/dashboard" + ); } catch (error) { - console.error('\nāŒ Deployment failed:', error.message); + console.error("\nāŒ Deployment failed:", error.message); process.exit(1); } } async function main() { try { - console.log('šŸš€ Vercel Mini App Deployment (SDK Edition)'); - console.log('This script will deploy your mini app to Vercel using the Vercel SDK.'); - console.log('\nThe script will:'); - console.log('1. Check for required environment variables'); - console.log('2. Set up a Vercel project (new or existing)'); - console.log('3. Configure environment variables in Vercel using SDK'); - console.log('4. Deploy and build your mini app\n'); + console.log("šŸš€ Vercel Mini App Deployment (SDK Edition)"); + console.log( + "This script will deploy your mini app to Vercel using the Vercel SDK." + ); + console.log("\nThe script will:"); + console.log("1. Check for required environment variables"); + console.log("2. Set up a Vercel project (new or existing)"); + console.log("3. Configure environment variables in Vercel using SDK"); + console.log("4. Deploy and build your mini app\n"); // Check if @vercel/sdk is installed try { - await import('@vercel/sdk'); + await import("@vercel/sdk"); } catch (error) { - console.log('šŸ“¦ Installing @vercel/sdk...'); - execSync('npm install @vercel/sdk', { + console.log("šŸ“¦ Installing @vercel/sdk..."); + execSync("npm install @vercel/sdk", { cwd: projectRoot, - stdio: 'inherit' + stdio: "inherit", }); - console.log('āœ… @vercel/sdk installed successfully'); + console.log("āœ… @vercel/sdk installed successfully"); } await checkRequiredEnvVars(); @@ -818,58 +776,57 @@ async function main() { let useGitHub = false; if (remoteUrl) { - console.log('\nšŸ“¦ Found GitHub repository:', remoteUrl); + console.log("\nšŸ“¦ Found GitHub repository:", remoteUrl); const { useGitHubDeploy } = await inquirer.prompt([ { - type: 'confirm', - name: 'useGitHubDeploy', - message: 'Would you like to deploy from the GitHub repository?', - default: true - } + type: "confirm", + name: "useGitHubDeploy", + message: "Would you like to deploy from the GitHub repository?", + default: true, + }, ]); useGitHub = useGitHubDeploy; } else { - console.log('\nāš ļø No GitHub repository found.'); + console.log("\nāš ļø No GitHub repository found."); const { action } = await inquirer.prompt([ { - type: 'list', - name: 'action', - message: 'What would you like to do?', + type: "list", + name: "action", + message: "What would you like to do?", choices: [ - { name: 'Deploy local code directly', value: 'deploy' }, - { name: 'Set up GitHub repository first', value: 'setup' } + { name: "Deploy local code directly", value: "deploy" }, + { name: "Set up GitHub repository first", value: "setup" }, ], - default: 'deploy' - } + default: "deploy", + }, ]); - if (action === 'setup') { - console.log('\nšŸ‘‹ Please set up your GitHub repository first:'); - console.log('1. Create a new repository on GitHub'); - console.log('2. Run these commands:'); - console.log(' git remote add origin '); - console.log(' git push -u origin main'); - console.log('\nThen run this script again to deploy.'); + if (action === "setup") { + console.log("\nšŸ‘‹ Please set up your GitHub repository first:"); + console.log("1. Create a new repository on GitHub"); + console.log("2. Run these commands:"); + console.log(" git remote add origin "); + console.log(" git push -u origin main"); + console.log("\nThen run this script again to deploy."); process.exit(0); } } - if (!await checkVercelCLI()) { - console.log('Vercel CLI not found. Installing...'); + if (!(await checkVercelCLI())) { + console.log("Vercel CLI not found. Installing..."); await installVercelCLI(); } - if (!await loginToVercel()) { - console.error('\nāŒ Failed to log in to Vercel. Please try again.'); + if (!(await loginToVercel())) { + console.error("\nāŒ Failed to log in to Vercel. Please try again."); process.exit(1); } await deployToVercel(useGitHub); - } catch (error) { - console.error('\nāŒ Error:', error.message); + console.error("\nāŒ Error:", error.message); process.exit(1); } } -main(); \ No newline at end of file +main(); diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 85168a0..0762d67 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,8 +1,19 @@ -import { type ClassValue, clsx } from 'clsx'; -import { twMerge } from 'tailwind-merge'; -import { mnemonicToAccount } from 'viem/accounts'; -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 { type ClassValue, clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; +import { mnemonicToAccount } from "viem/accounts"; +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"; interface MiniAppMetadata { version: string; @@ -17,7 +28,7 @@ interface MiniAppMetadata { description?: string; primaryCategory?: string; tags?: string[]; -}; +} interface MiniAppManifest { accountAssociation?: { @@ -32,17 +43,6 @@ export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } -export function getSecretEnvVars() { - const seedPhrase = process.env.SEED_PHRASE; - const fid = process.env.FID; - - if (!seedPhrase || !fid) { - return null; - } - - return { seedPhrase, fid }; -} - export function getMiniAppEmbedMetadata(ogImageUrl?: string) { return { version: "next", @@ -69,58 +69,30 @@ export async function getFarcasterMetadata(): Promise { if (process.env.MINI_APP_METADATA) { try { const metadata = JSON.parse(process.env.MINI_APP_METADATA); - console.log('Using pre-signed mini app metadata from environment'); + console.log("Using pre-signed mini app metadata from environment"); return metadata; } catch (error) { - console.warn('Failed to parse MINI_APP_METADATA from environment:', error); + console.warn( + "Failed to parse MINI_APP_METADATA from environment:", + error + ); } } if (!APP_URL) { - throw new Error('NEXT_PUBLIC_URL not configured'); + throw new Error("NEXT_PUBLIC_URL not configured"); } // Get the domain from the URL (without https:// prefix) const domain = new URL(APP_URL).hostname; - console.log('Using domain for manifest:', domain); - - const secretEnvVars = getSecretEnvVars(); - if (!secretEnvVars) { - console.warn('No seed phrase or FID found in environment variables -- generating unsigned metadata'); - } - - let accountAssociation; - if (secretEnvVars) { - // Generate account from seed phrase - const account = mnemonicToAccount(secretEnvVars.seedPhrase); - const custodyAddress = account.address; - - const header = { - fid: parseInt(secretEnvVars.fid), - type: 'custody', - key: custodyAddress, - }; - const encodedHeader = Buffer.from(JSON.stringify(header), 'utf-8').toString('base64'); - - const payload = { - domain - }; - const encodedPayload = Buffer.from(JSON.stringify(payload), 'utf-8').toString('base64url'); - - const signature = await account.signMessage({ - message: `${encodedHeader}.${encodedPayload}` - }); - const encodedSignature = Buffer.from(signature, 'utf-8').toString('base64url'); - - accountAssociation = { - header: encodedHeader, - payload: encodedPayload, - signature: encodedSignature - }; - } + console.log("Using domain for manifest:", domain); return { - accountAssociation, + accountAssociation: { + header: "", + payload: "", + signature: "", + }, frame: { version: "1", name: APP_NAME ?? "Neynar Starter Kit",