/** * Initialize a new game command */ import { Argv, CommandModule } from 'yargs'; import * as path from 'path'; import { OutputHelper } from '@quenty/cli-output-helpers'; import { resolveTemplatePath, TemplateHelper, } from '@quenty/nevermore-template-helpers'; import { NevermoreGlobalArgs } from '../../args/global-args.js'; import { runCommandAsync } from '../../utils/nevermore-cli-utils.js'; export interface InitGameArgs extends NevermoreGlobalArgs { gameName: string; } /** * Creates a new game with Nevermore dependencies */ export class InitGameCommand implements CommandModule { public command = ['game [game-name]', '$0 [game-name]'] as any; public describe = 'Scaffold a new game with Nevermore dependencies'; public builder(args: Argv) { args.positional('game-name', { describe: 'Name of the new package folder.', demandOption: false, type: 'string', }); return args as Argv; } public static async initToolChainAsync( args: NevermoreGlobalArgs, srcRoot: string, packages: string[] ): Promise { // TODO: Something better than this so we don't spam install pnpm every time await runCommandAsync(args, 'npm', ['install', 'pnpm', '-g']); await runCommandAsync(args, 'pnpm', ['install', ...packages], { cwd: srcRoot, }); try { await runCommandAsync(args, 'git', ['init'], { cwd: srcRoot, }); } catch { OutputHelper.error( 'Failed to initialize git repository, is git installed?' ); } try { await runCommandAsync(args, 'aftman', ['install'], { cwd: srcRoot, }); } catch { OutputHelper.error('Failed to install aftman, is aftman installed?'); } try { await runCommandAsync(args, 'pnpm', ['run', 'format'], { cwd: srcRoot, }); } catch { OutputHelper.error( 'Failed to run `pnpm run format`, is stylua installed?' ); } try { await runCommandAsync(args, 'selene', ['generate-roblox-std'], { cwd: srcRoot, }); } catch { OutputHelper.error( 'Failed to run `selene generate-roblox-std`, is selene installed?' ); } } public async handler(args: InitGameArgs) { const rawGameName = await InitGameCommand._ensureGameName(args); const gameName = TemplateHelper.toPackageName(rawGameName); const gameNameProper = TemplateHelper.camelize(rawGameName); const srcRoot = process.cwd(); const templatePath = resolveTemplatePath(import.meta.url, 'game-template'); OutputHelper.info( `Creating a new game at '${srcRoot}' with template '${templatePath}'` ); await TemplateHelper.createDirectoryContentsAsync( templatePath, srcRoot, { gameName: gameName, gameNameProper: gameNameProper, }, args.dryrun ); const packages = [ '@quenty/loader', '@quenty/servicebag', '@quenty/binder', '@quenty/clienttranslator', '@quenty/cmdrservice', ]; await InitGameCommand.initToolChainAsync(args, srcRoot, packages); } private static async _ensureGameName(args: InitGameArgs): Promise { let { gameName } = args; if (!gameName) { gameName = path.basename(process.cwd()); } InitGameCommand._validateGameName(gameName); return gameName; } private static _validateGameName(name: string): void { if (!name) { throw new Error('The project name is required.'); } if (name.length === 0) { throw new Error('The project name cannot be empty string.'); } } }