build-discord-app
Version:
A CLI tool to scaffold your own Discord bot project quickly
179 lines (160 loc) • 6.86 kB
JavaScript
import fs from 'fs-extra';
import path from 'path';
import { selectPrompt, textPrompt } from './utils/prompts.js';
import { help, version, yesall } from './utils/flags.js';
import { handleExistingDirConflict, isDirectoryNotEmpty } from './utils/directory.js';
import { handleExistingFileConflicts } from './utils/file.js';
import { log, note, outro, spinner } from '@clack/prompts';
import color from 'picocolors';
import { parseCLIArgs } from './utils/args.js';
import cloneTemplate from './utils/cloneTempalte.js';
const knownFlags = ['--version', '-v', '--help', '-h', '--yes', '-y', '--pkgname', '-p', '--lang', '-l'];
const supportedLangs = ['js'];
const args = process.argv.slice(2);
let {
foldername,
firstArg,
unknownFlags,
pkgArg,
langArg,
hasYesFlag
} = parseCLIArgs(args, knownFlags);
let packageName = pkgArg || '';
let language = langArg || '';
const isCurrentDir = foldername === '.';
let targetPath = isCurrentDir ? process.cwd() : path.join(process.cwd(), foldername);
if (firstArg === '--help' || firstArg === '-h') help();
if (firstArg === '--version' || firstArg === '-v') version();
if (hasYesFlag) skipInteraction();
if (isCurrentDir) {
foldername = '';
targetPath = process.cwd();
};
function skipInteraction() {
const presentFolder = foldername || '';
const { folderName, pkgName, lang } = yesall(presentFolder);
foldername = isCurrentDir ? '.' : folderName;
packageName = pkgName;
language = lang;
if (foldername == '.') {
global.allowOverwrite = true;
}
}
if (unknownFlags.length > 0) {
log.error(color.red(`Error: Unknown option(s): ${unknownFlags.join(', ')}`));
log.info(`Use ${color.bold('-h')} or ${color.bold('--help')} to view available options.`);
process.exit(1);
}
(async () => {
if (isCurrentDir) log.info('Creating bot in the current directory.');
if ((!foldername || foldername.trim() === '') && !isCurrentDir) {
const folder = await textPrompt({
message: 'Project name:',
placeholder: `my-discord-bot`,
initialValue: 'my-discord-bot',
validate(value) {
if (value.length === 0) return 'Folder name can\'t be empty';
}
});
foldername = folder;
targetPath = path.join(process.cwd(), foldername);
}
if (!packageName || packageName.trim() === '') {
const defaultPkgName = foldername
? foldername.toLowerCase().trim().replace(/\s+/g, '-')
: 'my-discord-bot';
const pkgname = await textPrompt({
message: 'Package name:',
initialValue: defaultPkgName,
placeholder: defaultPkgName,
validate(name) {
if (!/^(?:@[a-z0-9-*~][a-z0-9-*._~]*)?[a-z0-9-~][a-z0-9-._~]*$/.test(name)) return 'Invalid name. Use: lowercase, numbers, -, ~, . only';
}
});
packageName = pkgname;
}
if (!language || language.trim() === '') {
const lang = await selectPrompt({
message: 'Select a language for your bot:',
options: [
{ value: 'js', label: 'JavaScript', hint: 'recommended' },
{ value: null, label: 'TypeScript', hint: 'coming soon', disabled: true },
{ value: null, label: 'Python', hint: 'coming soon', disabled: true },
],
})
language = lang;
}
if (!supportedLangs.includes(language)) {
log.error(color.red(`Language "${language}" is not supported.`));
note(color.whiteBright(color.bold(supportedLangs.join(', '))), 'Try one of:');
process.exit(1);
}
if (!/^(?:@[a-z0-9-*~][a-z0-9-*._~]*)?[a-z0-9-~][a-z0-9-._~]*$/.test(packageName)) {
log.error(color.red('Invalid package name. Use: lowercase, numbers, -, ~, . only'));
process.exit(1);
}
const [major] = process.versions.node.split('.').map(Number);
if (major < 18) {
log.warn(color.yellow('Node.js v18+ is recommended for best compatibility.'));
}
const spin = spinner();
spin.start('Getting your discord app template ready...');
const templatePath = await cloneTemplate(language);
if (!templatePath || !(await fs.pathExists(templatePath))) {
log.error(color.red('Could not locate or download the template files.'));
process.exit(1);
}
spin.stop('Template work Completed');
if (isCurrentDir && fs.existsSync(targetPath)) {
if (isDirectoryNotEmpty(targetPath)) {
const dirAction = await handleExistingDirConflict(targetPath);
if (dirAction === 'ignored') global.allowOverwrite = !(await handleExistingFileConflicts(templatePath, targetPath));
}
}
spin.start('Creating your discord app..');
try {
if (!isCurrentDir) {
if (fs.existsSync(targetPath)) {
const isEmpty = (await fs.readdir(targetPath)).length === 0;
if (!isEmpty) {
log.error(color.red("Folder already exists and is not empty. Please use a different name or choose a different option."));
process.exit(1);
}
} else {
fs.mkdirSync(targetPath);
}
}
spin.message('Copying starter files...');
if (isCurrentDir) {
for (const file of await fs.readdir(templatePath)) {
const src = path.join(templatePath, file);
const dest = path.join(targetPath, file);
if (!global.allowOverwrite || !(await fs.pathExists(dest))) {
await fs.copy(src, dest);
}
}
} else {
await fs.copy(templatePath, targetPath);
}
spin.message('Adding package details...');
const pkgpath = path.join(targetPath, 'package.json');
const pkgData = await fs.readJson(pkgpath);
pkgData.name = packageName;
await fs.writeJson(pkgpath, pkgData, { spaces: 2 });
await new Promise(res => setTimeout(res, 700));
await fs.remove(templatePath);
spin.stop('Project setup completed!');
note([
!isCurrentDir ? color.dim(color.gray(`cd ${foldername}`)) : null,
color.dim(color.gray('npm install')),
color.dim(color.gray('Rename .env.example to .env')),
color.dim(color.gray('Add your bot token to .env')),
color.dim(color.gray('npm run register')),
color.dim(color.gray('npm start'))
].filter(Boolean).join('\n'), 'Next Steps:');
outro(color.italic('Having issues?' + color.blueBright(' https://github.com/dipanshu447/create-discord-bot/issues ')));
} catch (error) {
log.error(color.red("Unexpected error occurred:"), error);
}
})()