budgie-react
Version:
🐦 Flexible React project initializer — TypeScript, Redux Toolkit, React Router v6, Error Boundaries, Axios and more.
172 lines (147 loc) • 4.54 kB
JavaScript
;
const readline = require('readline');
const Console = require('./console');
// ─── Inline checkbox prompt ──────────────────────────────────────────────────
// budgie-console handles single-line prompts; we build a multi-select on top.
const FEATURES = [
{
key: 'redux',
label: 'Redux Toolkit',
description: 'State management with @reduxjs/toolkit + react-redux',
},
{
key: 'routing',
label: 'React Router v6',
description: 'Client-side routing with react-router-dom',
},
{
key: 'errorBoundary',
label: 'Error Boundaries',
description: 'Global error boundary component + fallback UI',
},
{
key: 'axios',
label: 'Axios HTTP Client',
description: 'Pre-configured Axios instance with interceptors',
},
{
key: 'envConfig',
label: 'Environment Config',
description: '.env.development / .env.production + dotenv-webpack',
},
{
key: 'eslint',
label: 'ESLint + Prettier',
description: 'Airbnb TypeScript rules + auto-format on save',
},
{
key: 'jest',
label: 'Jest + Testing Library',
description: 'Unit test setup with @testing-library/react',
},
];
const PACKAGE_MANAGERS = ['npm', 'yarn', 'pnpm'];
/**
* Renders a numbered feature list and reads a comma-separated selection.
*/
async function selectFeatures() {
Console.log(
Console.FgYellow + '? ' + Console.Reset + 'Select features to include:'
);
Console.log(
Console.Dim + ' (Enter comma-separated numbers, e.g. 1,2,3 — or "all" / Enter to skip)' + Console.Reset
);
Console.log('');
Console.table(
FEATURES.map((f, i) => [String(i + 1), f.label, f.description]),
['#', 'Feature', 'Description']
);
Console.log('');
const raw = await Console.prompt(
Console.FgCyan + '> ' + Console.Reset
);
const trimmed = raw.trim().toLowerCase();
if (trimmed === 'all') {
return FEATURES.reduce((acc, f) => ({ ...acc, [f.key]: true }), {});
}
if (!trimmed) {
return FEATURES.reduce((acc, f) => ({ ...acc, [f.key]: false }), {});
}
const indices = trimmed
.split(',')
.map((s) => parseInt(s.trim(), 10) - 1)
.filter((i) => i >= 0 && i < FEATURES.length);
return FEATURES.reduce((acc, f, i) => {
acc[f.key] = indices.includes(i);
return acc;
}, {});
}
/**
* Ask which package manager to use.
*/
async function selectPackageManager() {
Console.log('');
Console.log(
Console.FgYellow +
'? ' +
Console.Reset +
`Package manager ${Console.Dim}(1 = npm, 2 = yarn, 3 = pnpm — default: 1)${Console.Reset}:`
);
const raw = await Console.prompt(Console.FgCyan + '> ' + Console.Reset);
const choice = parseInt(raw.trim(), 10);
if (choice >= 1 && choice <= 3) return PACKAGE_MANAGERS[choice - 1];
return 'npm';
}
/**
* Ask for project description (optional).
*/
async function askDescription() {
const raw = await Console.prompt(
Console.FgCyan + '? ' + Console.Reset + 'Project description (optional): '
);
return raw.trim() || '';
}
/**
* Ask for author name (optional).
*/
async function askAuthor() {
const raw = await Console.prompt(
Console.FgCyan + '? ' + Console.Reset + 'Author name (optional): '
);
return raw.trim() || '';
}
/**
* Main prompt orchestrator — returns full config object.
*/
async function askProjectConfig(projectName) {
const description = await askDescription();
const author = await askAuthor();
Console.log('');
const features = await selectFeatures();
const packageManager = await selectPackageManager();
Console.log('');
Console.divider('─', 52, Console.FgCyan);
Console.info('Your configuration:');
Console.log('');
const summary = [
['Project', projectName],
['Description', description || '—'],
['Author', author || '—'],
['Package Manager', packageManager],
...FEATURES.map((f) => [f.label, features[f.key] ? '✔ Yes' : '✖ No']),
];
Console.table(summary, ['Setting', 'Value']);
Console.log('');
const confirm = await Console.prompt(
Console.FgYellow +
'? ' +
Console.Reset +
'Looks good? Press Enter to generate, or type "no" to exit: '
);
if (confirm.trim().toLowerCase() === 'no') {
Console.warn('Aborted. No files were created.');
process.exit(0);
}
return { projectName, description, author, packageManager, ...features };
}
module.exports = { askProjectConfig };