Skip to content

Commit

Permalink
feat: introducing create-robo
Browse files Browse the repository at this point in the history
New CLI for easily creating new Robo projects just how you want them!

Just like the Robo.js framework itself, this utility is also considered pre-release. It may not work perfectly and is likely to undergo changes!
  • Loading branch information
Pkmmte committed May 1, 2023
1 parent 14b28d9 commit 4d856c1
Show file tree
Hide file tree
Showing 20 changed files with 1,030 additions and 21 deletions.
5 changes: 5 additions & 0 deletions .changeset/large-walls-carry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'create-robo': minor
---

feat: introducing create-robo
49 changes: 49 additions & 0 deletions packages/create-robo/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"name": "create-robo",
"version": "0.0.0",
"private": false,
"description": "Create Robo.js Discord bots with one command",
"engines": {
"node": ">=18.0.0"
},
"scripts": {
"build": "tsup"
},
"bin": {
"create-robo": "dist/index.js"
},
"keywords": [],
"license": "MIT",
"author": "WavePlay <[email protected]> (waveplay.com)",
"contributors": [
"Pkmmte Xeleon <[email protected]>"
],
"type": "module",
"files": [
"dist/",
"src/",
"templates/",
"LICENSE",
"README.md"
],
"repository": {
"type": "git",
"url": "https://github.com/Wave-Play/robo.git",
"directory": "packages/create-robo"
},
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
},
"dependencies": {
"chalk": "5.2.0",
"commander": "10.0.0",
"inquirer": "^9.2.0"
},
"devDependencies": {
"@types/inquirer": "^9.0.3",
"@types/node": "^18.16.3",
"tsup": "6.7.0",
"typescript": "5.0.2"
}
}
101 changes: 101 additions & 0 deletions packages/create-robo/src/base-logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import chalk from 'chalk'

export type LogLevel = 'trace' | 'debug' | 'info' | 'wait' | 'other' | 'event' | 'ready' | 'warn' | 'error'

export interface BaseLoggerOptions {
enabled?: boolean
level?: LogLevel
}

abstract class BaseLogger {
protected _enabled: boolean
protected _level: LogLevel

constructor(options?: BaseLoggerOptions) {
const { enabled = true, level } = options ?? {}

this._enabled = enabled
this._level = level ?? 'info'
}

public trace(...data: unknown[]) {
if (this._enabled && LogLevelValues[this._level] <= LogLevelValues.trace) {
this._log('trace', ...data)
}
}

public debug(...data: unknown[]) {
if (this._enabled && LogLevelValues[this._level] <= LogLevelValues.debug) {
this._log('debug', ...data)
}
}

public info(...data: unknown[]) {
if (this._enabled && LogLevelValues[this._level] <= LogLevelValues.info) {
this._log('info', ...data)
}
}

public wait(...data: unknown[]) {
if (this._enabled && LogLevelValues[this._level] <= LogLevelValues.wait) {
this._log('wait', ...data)
}
}

public log(...data: unknown[]) {
if (this._enabled && LogLevelValues[this._level] <= LogLevelValues.other) {
this._log('other', ...data)
}
}

public event(...data: unknown[]) {
if (this._enabled && LogLevelValues[this._level] <= LogLevelValues.event) {
this._log('event', ...data)
}
}

public ready(...data: unknown[]) {
if (this._enabled && LogLevelValues[this._level] <= LogLevelValues.ready) {
this._log('ready', ...data)
}
}

public warn(...data: unknown[]) {
if (this._enabled && LogLevelValues[this._level] <= LogLevelValues.warn) {
this._log('warn', ...data)
}
}

public error(...data: unknown[]) {
if (this._enabled && LogLevelValues[this._level] <= LogLevelValues.error) {
this._log('error', ...data)
}
}

protected abstract _log(level: LogLevel, ...data: unknown[]): void
}

const LogLevelValues: Record<LogLevel, number> = {
trace: 0,
debug: 1,
info: 2,
wait: 2,
other: 2,
event: 3,
ready: 5,
warn: 4,
error: 5
}

const colorizedLogLevels = {
trace: chalk.gray('trace'.padEnd(5)),
debug: chalk.cyan('debug'.padEnd(5)),
info: chalk.blue('info'.padEnd(5)),
wait: chalk.cyan('wait'.padEnd(5)),
event: chalk.magenta('event'.padEnd(5)),
ready: chalk.green('ready'.padEnd(5)),
warn: chalk.yellow('warn'.padEnd(5)),
error: chalk.red('error'.padEnd(5))
}

export { BaseLogger, colorizedLogLevels }
44 changes: 44 additions & 0 deletions packages/create-robo/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env node
import { Command } from 'commander'
import { createRequire } from 'node:module'
import Robo from './robo.js'
import { logger } from './logger.js'

// Read the version from the package.json file
const require = createRequire(import.meta.url)
const packageJson = require('../package.json')

interface CommandOptions {
verbose?: boolean
}

new Command('create-robo <projectName>')
.description('Create a new Robo project')
.version(packageJson.version)
.option('-v --verbose', 'print more information for debugging')
.action(async (options: CommandOptions, { args }) => {
logger({
level: options.verbose ? 'debug' : 'info'
}).info(`Creating new Robo.js project...\n`)
const projectName = args[0]
const robo = new Robo(projectName)

// Get user input to determine which features to include or use the recommended defaults
const selectedFeaturesOrDefaults = await robo.getUserInput()
if (selectedFeaturesOrDefaults === 'defaults') {
await robo.createPackage(['TypeScript', 'ESLint', 'Prettier'])
} else {
await robo.createPackage(selectedFeaturesOrDefaults)
}

// Determine if TypeScript is selected and copy the corresponding template files
const useTypeScript =
selectedFeaturesOrDefaults === 'defaults' || (selectedFeaturesOrDefaults as string[]).includes('TypeScript')
await robo.copyTemplateFiles('', useTypeScript)

// Ask the user for their Discord credentials (token and client ID) and store them for later use
await robo.askForDiscordCredentials()
logger.log('')
logger.ready(`Successfully created ${projectName}. Happy coding!`)
})
.parse(process.argv)
90 changes: 90 additions & 0 deletions packages/create-robo/src/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { BaseLogger, LogLevel, colorizedLogLevels } from './base-logger.js'
import type { BaseLoggerOptions } from './base-logger.js'

class Logger extends BaseLogger {
constructor(options?: BaseLoggerOptions) {
super(options)
}

protected _log(level: LogLevel, ...data: unknown[]): void {
// Format the message all pretty and stuff
if (level !== 'other') {
const colorizedLevel = colorizedLogLevels[level]
data.unshift((colorizedLevel ?? level.padEnd(5)) + ' -')
}

switch (level) {
case 'trace':
case 'debug':
console.debug(...data)
break
case 'info':
console.info(...data)
break
case 'wait':
console.info(...data)
break
case 'event':
console.log(...data)
break
case 'warn':
console.warn(...data)
break
case 'error':
console.error(...data)
break
default:
console.log(...data)
}
}
}

export { Logger }

let _logger: Logger | null = null

export function logger(options?: BaseLoggerOptions): Logger {
if (options) {
_logger = new Logger(options)
} else if (!_logger) {
_logger = new Logger()
}

return _logger
}

logger.trace = function (...data: unknown[]): void {
return logger().trace(...data)
}

logger.debug = function (...data: unknown[]): void {
return logger().debug(...data)
}

logger.info = function (...data: unknown[]): void {
return logger().info(...data)
}

logger.wait = function (...data: unknown[]): void {
return logger().wait(...data)
}

logger.log = function (...data: unknown[]): void {
return logger().log(...data)
}

logger.event = function (...data: unknown[]): void {
return logger().event(...data)
}

logger.ready = function (...data: unknown[]): void {
return logger().ready(...data)
}

logger.warn = function (...data: unknown[]): void {
return logger().warn(...data)
}

logger.error = function (...data: unknown[]): void {
return logger().error(...data)
}
Loading

0 comments on commit 4d856c1

Please sign in to comment.