feat: add script inputs

This commit is contained in:
veganbeef 2025-06-26 10:07:14 -07:00
parent 9f5d80fddd
commit 2b85800ba7
No known key found for this signature in database
3 changed files with 212 additions and 144 deletions

View File

@ -2,7 +2,30 @@
import { init } from './init.js'; import { init } from './init.js';
init().catch((err) => { // Parse command line arguments
const args = process.argv.slice(2);
let projectName = null;
let autoAcceptDefaults = false;
// Check for -y flag
const yIndex = args.indexOf('-y');
if (yIndex !== -1) {
autoAcceptDefaults = true;
args.splice(yIndex, 1); // Remove -y from args
}
// If there's a remaining argument, it's the project name
if (args.length > 0) {
projectName = args[0];
}
// If -y is used without project name, we still need to ask for project name
if (autoAcceptDefaults && !projectName) {
// We'll handle this case in the init function by asking only for project name
autoAcceptDefaults = false;
}
init(projectName, autoAcceptDefaults).catch((err) => {
console.error('Error:', err); console.error('Error:', err);
process.exit(1); process.exit(1);
}); });

View File

@ -61,7 +61,7 @@ async function queryNeynarApp(apiKey) {
} }
// Export the main CLI function for programmatic use // Export the main CLI function for programmatic use
export async function init() { export async function init(projectName = null, autoAcceptDefaults = false) {
printWelcomeMessage(); printWelcomeMessage();
// Ask about Neynar usage // Ask about Neynar usage
@ -72,21 +72,26 @@ export async function init() {
let neynarAppLogoUrl = null; let neynarAppLogoUrl = null;
while (useNeynar) { while (useNeynar) {
const neynarAnswers = await inquirer.prompt([ let neynarAnswers;
{ if (autoAcceptDefaults) {
type: 'confirm', neynarAnswers = { useNeynar: true };
name: 'useNeynar', } else {
message: `🪐 ${purple}${bright}${italic}Neynar is an API that makes it easy to build on Farcaster.${reset}\n\n` + neynarAnswers = await inquirer.prompt([
'Benefits of using Neynar in your mini app:\n' + {
'- Pre-configured webhook handling (no setup required)\n' + type: 'confirm',
'- Automatic mini app analytics in your dev portal\n' + name: 'useNeynar',
'- Send manual notifications from dev.neynar.com\n' + message: `🪐 ${purple}${bright}${italic}Neynar is an API that makes it easy to build on Farcaster.${reset}\n\n` +
'- Built-in rate limiting and error handling\n\n' + 'Benefits of using Neynar in your mini app:\n' +
`${purple}${bright}${italic}A demo API key is included if you would like to try out Neynar before signing up!${reset}\n\n` + '- Pre-configured webhook handling (no setup required)\n' +
'Would you like to use Neynar in your mini app?', '- Automatic mini app analytics in your dev portal\n' +
default: true '- Send manual notifications from dev.neynar.com\n' +
} '- Built-in rate limiting and error handling\n\n' +
]); `${purple}${bright}${italic}A demo API key is included if you would like to try out Neynar before signing up!${reset}\n\n` +
'Would you like to use Neynar in your mini app?',
default: true
}
]);
}
if (!neynarAnswers.useNeynar) { if (!neynarAnswers.useNeynar) {
useNeynar = false; useNeynar = false;
@ -94,26 +99,37 @@ export async function init() {
} }
console.log('\n🪐 Find your Neynar API key at: https://dev.neynar.com/app\n'); console.log('\n🪐 Find your Neynar API key at: https://dev.neynar.com/app\n');
const neynarKeyAnswer = await inquirer.prompt([
{ let neynarKeyAnswer;
type: 'password', if (autoAcceptDefaults) {
name: 'neynarApiKey', neynarKeyAnswer = { neynarApiKey: null };
message: 'Enter your Neynar API key (or press enter to skip):', } else {
default: null neynarKeyAnswer = await inquirer.prompt([
} {
]); type: 'password',
name: 'neynarApiKey',
message: 'Enter your Neynar API key (or press enter to skip):',
default: null
}
]);
}
if (neynarKeyAnswer.neynarApiKey) { if (neynarKeyAnswer.neynarApiKey) {
neynarApiKey = neynarKeyAnswer.neynarApiKey; neynarApiKey = neynarKeyAnswer.neynarApiKey;
} else { } else {
const useDemoKey = await inquirer.prompt([ let useDemoKey;
{ if (autoAcceptDefaults) {
type: 'confirm', useDemoKey = { useDemo: true };
name: 'useDemo', } else {
message: 'Would you like to try the demo Neynar API key?', useDemoKey = await inquirer.prompt([
default: true {
} type: 'confirm',
]); name: 'useDemo',
message: 'Would you like to try the demo Neynar API key?',
default: true
}
]);
}
if (useDemoKey.useDemo) { if (useDemoKey.useDemo) {
console.warn('\n⚠ Note: the demo key is for development purposes only and is aggressively rate limited.'); console.warn('\n⚠ Note: the demo key is for development purposes only and is aggressively rate limited.');
@ -124,6 +140,10 @@ export async function init() {
} }
if (!neynarApiKey) { if (!neynarApiKey) {
if (autoAcceptDefaults) {
useNeynar = false;
break;
}
console.log('\n⚠ No valid API key provided. Would you like to try again?'); console.log('\n⚠ No valid API key provided. Would you like to try again?');
const { retry } = await inquirer.prompt([ const { retry } = await inquirer.prompt([
{ {
@ -148,6 +168,10 @@ export async function init() {
} }
if (!neynarClientId) { if (!neynarClientId) {
if (autoAcceptDefaults) {
useNeynar = false;
break;
}
const { retry } = await inquirer.prompt([ const { retry } = await inquirer.prompt([
{ {
type: 'confirm', type: 'confirm',
@ -169,121 +193,142 @@ export async function init() {
const defaultMiniAppName = (neynarAppName && !neynarAppName.toLowerCase().includes('demo')) ? neynarAppName : undefined; const defaultMiniAppName = (neynarAppName && !neynarAppName.toLowerCase().includes('demo')) ? neynarAppName : undefined;
const answers = await inquirer.prompt([ let answers;
{ if (autoAcceptDefaults) {
type: 'input', answers = {
name: 'projectName', projectName: projectName || defaultMiniAppName || 'my-farcaster-mini-app',
message: 'What is the name of your mini app?', description: 'A Farcaster mini app created with Neynar',
default: defaultMiniAppName, primaryCategory: null,
validate: (input) => { tags: [],
if (input.trim() === '') { buttonText: 'Launch Mini App',
return 'Project name cannot be empty'; useWallet: true,
useTunnel: true,
enableAnalytics: true
};
} else {
// If autoAcceptDefaults is false but we have a projectName, we still need to ask for other options
const projectNamePrompt = await inquirer.prompt([
{
type: 'input',
name: 'projectName',
message: 'What is the name of your mini app?',
default: projectName || defaultMiniAppName,
validate: (input) => {
if (input.trim() === '') {
return 'Project name cannot be empty';
}
return true;
} }
return true;
} }
}, ]);
{
type: 'input', answers = await inquirer.prompt([
name: 'description', {
message: 'Give a one-line description of your mini app (optional):', type: 'input',
default: 'A Farcaster mini app created with Neynar' name: 'description',
}, message: 'Give a one-line description of your mini app (optional):',
{ default: 'A Farcaster mini app created with Neynar'
type: 'list', },
name: 'primaryCategory', {
message: 'It is strongly recommended to choose a primary category and tags to help users discover your mini app.\n\nSelect a primary category:', type: 'list',
choices: [ name: 'primaryCategory',
new inquirer.Separator(), message: 'It is strongly recommended to choose a primary category and tags to help users discover your mini app.\n\nSelect a primary category:',
{ name: 'Skip (not recommended)', value: null }, choices: [
new inquirer.Separator(), new inquirer.Separator(),
{ name: 'Games', value: 'games' }, { name: 'Skip (not recommended)', value: null },
{ name: 'Social', value: 'social' }, new inquirer.Separator(),
{ name: 'Finance', value: 'finance' }, { name: 'Games', value: 'games' },
{ name: 'Utility', value: 'utility' }, { name: 'Social', value: 'social' },
{ name: 'Productivity', value: 'productivity' }, { name: 'Finance', value: 'finance' },
{ name: 'Health & Fitness', value: 'health-fitness' }, { name: 'Utility', value: 'utility' },
{ name: 'News & Media', value: 'news-media' }, { name: 'Productivity', value: 'productivity' },
{ name: 'Music', value: 'music' }, { name: 'Health & Fitness', value: 'health-fitness' },
{ name: 'Shopping', value: 'shopping' }, { name: 'News & Media', value: 'news-media' },
{ name: 'Education', value: 'education' }, { name: 'Music', value: 'music' },
{ name: 'Developer Tools', value: 'developer-tools' }, { name: 'Shopping', value: 'shopping' },
{ name: 'Entertainment', value: 'entertainment' }, { name: 'Education', value: 'education' },
{ name: 'Art & Creativity', value: 'art-creativity' } { name: 'Developer Tools', value: 'developer-tools' },
], { name: 'Entertainment', value: 'entertainment' },
default: null { name: 'Art & Creativity', value: 'art-creativity' }
}, ],
{ default: null
type: 'input', },
name: 'tags', {
message: 'Enter tags for your mini app (separate with spaces or commas, optional):', type: 'input',
default: '', name: 'tags',
filter: (input) => { message: 'Enter tags for your mini app (separate with spaces or commas, optional):',
if (!input.trim()) return []; default: '',
// Split by both spaces and commas, trim whitespace, and filter out empty strings filter: (input) => {
return input if (!input.trim()) return [];
.split(/[,\s]+/) // Split by both spaces and commas, trim whitespace, and filter out empty strings
.map(tag => tag.trim()) return input
.filter(tag => tag.length > 0); .split(/[,\s]+/)
} .map(tag => tag.trim())
}, .filter(tag => tag.length > 0);
{ }
type: 'input', },
name: 'buttonText', {
message: 'Enter the button text for your mini app:', type: 'input',
default: 'Launch Mini App', name: 'buttonText',
validate: (input) => { message: 'Enter the button text for your mini app:',
if (input.trim() === '') { default: 'Launch Mini App',
return 'Button text cannot be empty'; validate: (input) => {
if (input.trim() === '') {
return 'Button text cannot be empty';
}
return true;
} }
return true;
} }
} ]);
]);
// Ask about wallet and transaction tooling // Merge project name from the first prompt
const walletAnswer = await inquirer.prompt([ answers.projectName = projectNamePrompt.projectName;
{
type: 'confirm',
name: 'useWallet',
message: 'Would you like to include wallet and transaction tooling in your mini app?\n' +
'This includes:\n' +
'- EVM wallet connection\n' +
'- Transaction signing\n' +
'- Message signing\n' +
'- Chain switching\n' +
'- Solana support\n\n' +
'Include wallet and transaction features?',
default: true
}
]);
answers.useWallet = walletAnswer.useWallet;
// Ask about localhost vs tunnel // Ask about wallet and transaction tooling
const hostingAnswer = await inquirer.prompt([ const walletAnswer = await inquirer.prompt([
{ {
type: 'confirm', type: 'confirm',
name: 'useTunnel', name: 'useWallet',
message: 'Would you like to test on mobile and/or test the app with Warpcast developer tools?\n' + message: 'Would you like to include wallet and transaction tooling in your mini app?\n' +
`⚠️ ${yellow}${italic}Both mobile testing and the Warpcast debugger require setting up a tunnel to serve your app from localhost to the broader internet.\n${reset}` + 'This includes:\n' +
'Configure a tunnel for mobile testing and/or Warpcast developer tools?', '- EVM wallet connection\n' +
default: true '- Transaction signing\n' +
} '- Message signing\n' +
]); '- Chain switching\n' +
answers.useTunnel = hostingAnswer.useTunnel; '- Solana support\n\n' +
'Include wallet and transaction features?',
default: true
}
]);
answers.useWallet = walletAnswer.useWallet;
// Ask about analytics opt-out // Ask about localhost vs tunnel
const analyticsAnswer = await inquirer.prompt([ const hostingAnswer = await inquirer.prompt([
{ {
type: 'confirm', type: 'confirm',
name: 'enableAnalytics', name: 'useTunnel',
message: 'Would you like to help improve Neynar products by sharing usage data from your mini app?', message: 'Would you like to test on mobile and/or test the app with Warpcast developer tools?\n' +
default: true `⚠️ ${yellow}${italic}Both mobile testing and the Warpcast debugger require setting up a tunnel to serve your app from localhost to the broader internet.\n${reset}` +
} 'Configure a tunnel for mobile testing and/or Warpcast developer tools?',
]); default: true
answers.enableAnalytics = analyticsAnswer.enableAnalytics; }
]);
answers.useTunnel = hostingAnswer.useTunnel;
const projectName = answers.projectName; // Ask about analytics opt-out
const projectDirName = projectName.replace(/\s+/g, '-').toLowerCase(); const analyticsAnswer = await inquirer.prompt([
{
type: 'confirm',
name: 'enableAnalytics',
message: 'Would you like to help improve Neynar products by sharing usage data from your mini app?',
default: true
}
]);
answers.enableAnalytics = analyticsAnswer.enableAnalytics;
}
const finalProjectName = answers.projectName;
const projectDirName = finalProjectName.replace(/\s+/g, '-').toLowerCase();
const projectPath = path.join(process.cwd(), projectDirName); const projectPath = path.join(process.cwd(), projectDirName);
console.log(`\nCreating a new mini app in ${projectPath}`); console.log(`\nCreating a new mini app in ${projectPath}`);
@ -328,7 +373,7 @@ export async function init() {
const packageJsonPath = path.join(projectPath, 'package.json'); const packageJsonPath = path.join(projectPath, 'package.json');
let packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); let packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
packageJson.name = projectName; packageJson.name = finalProjectName;
packageJson.version = '0.1.0'; packageJson.version = '0.1.0';
delete packageJson.author; delete packageJson.author;
delete packageJson.keywords; delete packageJson.keywords;
@ -464,7 +509,7 @@ export async function init() {
execSync('git commit -m "initial commit from @neynar/create-farcaster-mini-app"', { cwd: projectPath }); execSync('git commit -m "initial commit from @neynar/create-farcaster-mini-app"', { cwd: projectPath });
// Calculate border length based on message length // Calculate border length based on message length
const message = `✨🪐 Successfully created mini app ${projectName} with git and dependencies installed! 🪐✨`; const message = `✨🪐 Successfully created mini app ${finalProjectName} with git and dependencies installed! 🪐✨`;
const borderLength = message.length; const borderLength = message.length;
const borderStars = '✨'.repeat((borderLength / 2) + 1); const borderStars = '✨'.repeat((borderLength / 2) + 1);
@ -472,6 +517,6 @@ export async function init() {
console.log(`${message}`); console.log(`${message}`);
console.log(`${borderStars}`); console.log(`${borderStars}`);
console.log('\nTo run the app:'); console.log('\nTo run the app:');
console.log(` cd ${projectName}`); console.log(` cd ${finalProjectName}`);
console.log(' npm run dev\n'); console.log(' npm run dev\n');
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@neynar/create-farcaster-mini-app", "name": "@neynar/create-farcaster-mini-app",
"version": "1.4.3", "version": "1.4.4",
"type": "module", "type": "module",
"private": false, "private": false,
"access": "public", "access": "public",