UNPKG

@santerzet/botkit

Version:

CLI for Discord Bot

318 lines (257 loc) 7.54 kB
import { fromAsync, isOk, isErr } from '@sapphire/result'; import { blueBright, red } from 'colorette'; import { execa } from 'execa'; import { cp, readFile, rm, writeFile } from 'node:fs/promises'; import prompts from 'prompts'; import { PromptNew, PromptNewObjectKey } from '#prompts/PromptNew'; import { Spinner } from '#functions/Spinner'; import { fileExists } from '#functions/FileExists'; import { repoUrl } from '#constants'; import { CommandExists } from '#functions/CommandExists'; async function editPackageJson( location: string, name: string, description: string, version: string, heroku: boolean ) { const pjLocation = `${location}/package.json`; const output = JSON.parse(await readFile(pjLocation, 'utf8')); if (!output) throw new Error("Can't read file."); output.name = name.toLowerCase(); output.description = description; output.version = version; if (heroku) output.scripts.heroku = `git add . && git commit -m \"Update bot\" && git push heroku main`; const result = await fromAsync(() => writeFile(pjLocation, JSON.stringify(output, null, 2)) ); return isOk(result); } async function cloneRepo(location: string, verbose: boolean) { const result = await fromAsync(async () => execa('git', ['clone', repoUrl, `${location}/ghr`], { stdio: verbose ? 'inherit' : undefined, }) ); if (isErr(result)) { throw result.error; } if (result.value.exitCode !== 0) { throw new Error( 'An unknown error occurred while cloning the repository. Try running BotKit CLI with "--verbose" flag.' ); } } async function initializeGitRepo(location: string) { await execa('git', ['init'], { cwd: `./${location}/` }); return true; } async function herokuLogin() { const result = await fromAsync(() => execa('heroku', ['login'], { stdin: 'inherit', }) ); if (isErr(result)) { throw result.error; } if (result.value.exitCode !== 0) { throw result.error; } return true; } async function initializeHeroku(location: string, verbose: boolean) { const result = await fromAsync(() => execa('heroku', ['create'], { stdio: verbose ? 'inherit' : undefined, cwd: `./${location}/`, }) ); if (isErr(result)) { throw result.error; } if (result.value.exitCode !== 0) { throw new Error( 'An unknown error occurred while creating Heroku app. Try running BotKit CLI with "--verbose" flag.' ); } return true; } async function pushToHeroku(location: string, verbose: boolean) { await execa('git', ['add', '.'], { cwd: `./${location}/` }); await execa('git', ['commit', '-m', '"Initial commit"'], { cwd: `./${location}/`, }); const result = await fromAsync(async () => execa('git', ['push', 'heroku', 'main'], { stdio: verbose ? 'inherit' : undefined, cwd: `./${location}/`, }) ); if (isErr(result)) { throw result.error; } if (result.value.exitCode !== 0) { throw new Error( 'An unknown error occurred while pushing to Heroku. Try running BotKit CLI with "--verbose" flag.' ); } return true; } async function configureHeroku(location: string, verbose: boolean) { const result = await fromAsync(async () => execa('heroku', ['ps:scale', 'web=0', 'worker=1'], { stdio: verbose ? 'inherit' : undefined, cwd: `./${location}/`, }) ); if (isErr(result)) { throw result.error; } if (result.value.exitCode !== 0) { throw new Error( 'An unknown error occurred while configuring Heroku. Try running BotKit CLI with "--verbose" flag.' ); } return true; } async function installDeps(location: string, verbose: boolean) { const result = await fromAsync(() => execa('npm', ['install'], { stdio: verbose ? 'inherit' : undefined, cwd: `./${location}/`, }) ); if (isErr(result)) { throw result.error; } if (result.value.exitCode !== 0) { throw new Error( 'An unknown error occurred while installing dependencies. Try running BotKit CLI with "--verbose" flag.' ); } const oppositeLockfile = `./${location}/package-lock.json`; if (await fileExists(oppositeLockfile)) { await rm(oppositeLockfile); } return true; } async function runJob( job: () => Promise<any>, name: string, showError: boolean = true ) { const spinner = new Spinner(name).start(); const result = await fromAsync(async () => job()); if (isErr(result)) { spinner.error(); if (showError) { console.error(red((result.error as Error).message)); } process.exit(1); } spinner.success(); return true; } export default async (name: string, flags: Record<string, boolean>) => { const response = await prompts<PromptNewObjectKey>( PromptNew(name, await CommandExists('heroku')) ); if (!response.botName || !response.botStructure || !response.commands) { process.exit(1); } let botTemplate = ''; if (response.botStructure === 'basic') { if (response.commands === 'slashCmd') { botTemplate = 'with-basic-slash-commands'; } else if (response.commands === 'prefixCmd') { botTemplate = 'with-basic-prefix-commands'; } else if (response.commands === 'bothCmd') { botTemplate = 'with-basic-both-commands'; } } else if (response.botStructure === 'advanced') { if (response.commands === 'slashCmd') { botTemplate = 'with-advanced-slash-commands'; } else if (response.commands === 'prefixCmd') { botTemplate = 'with-advanced-prefix-commands'; } else if (response.commands === 'bothCmd') { botTemplate = 'with-advanced-both-commands'; } } if (response.heroku) response.git = true; const botName = response.botName === '.' ? '' : response.botName; const stpJob = async () => { await cp( `./${response.botName}/ghr/templates/${response.botStructure}/${botTemplate}/.`, `./${response.botName}/`, { recursive: true } ); if (response.git && !response.heroku) { await cp( `./${response.botName}/ghr/.gitignore`, `./${response.botName}/.gitignore` ); } if (response.heroku) { for (const p of ['.gitignore', 'Procfile', 'HEROKU.md']) { await cp( `./${response.botName}/ghr/resources/with-heroku/${p}`, `./${response.botName}/${p}` ); } } await rm(`./${response.botName}/ghr`, { recursive: true, force: true }); await editPackageJson( response.botName, botName, response.botDescription, response.botVersion, response.heroku ); }; const jobs: [() => any, string, boolean?][] = []; if (response.heroku) { jobs.push([ () => herokuLogin(), 'Logging in to Heroku, press any key to open the browser, or q to quit', false, ]); } jobs.push([ () => cloneRepo(response.botName, flags.verbose), 'Cloning repository', ]); jobs.push([stpJob, 'Setting up project']); if (response.git) { jobs.push([() => initializeGitRepo(response.botName), 'Initializing git']); } if (response.heroku) { jobs.push([ () => initializeHeroku(response.botName, flags.verbose), 'Initializing Heroku', ]); jobs.push([ () => pushToHeroku(response.botName, flags.verbose), 'Pushing to Heroku', ]); jobs.push([ () => configureHeroku(response.botName, flags.verbose), 'Configuring Heroku', ]); } jobs.push([ () => installDeps(response.botName, flags.verbose), 'Installing dependencies', ]); for (const [job, name, showError] of jobs) { await runJob(job, name, showError).catch(() => process.exit(1)); } if (response.heroku) { const { stdout } = await execa('heroku', ['info'], { cwd: `./${response.botName}/`, }); console.log(`Information about your bot in Heroku:\n${stdout}`); } console.log(blueBright('Done!')); };