#!/usr/bin/env node import { consola } from 'consola' import { colorize } from 'consola/utils' import { downloadTemplate } from 'giget' import { spawnSync } from 'node:child_process' import path from 'node:path' const [, , directoryArg] = process.argv /** * @typedef {{ * prompt: string, * info?: string, * startMessage: string, * command: string, * subCommand: string, * error: string * }} PromptAction **/ /** * Create McFly App */ async function create() { const defaultDirectory = 'mcfly-app' consola.box(`Hello! Welcome to ${colorize('bold', 'McFly')}!`) let directory = directoryArg if (!directory) { directory = (await consola.prompt('Give your new project a name:', { placeholder: defaultDirectory, })) ?? defaultDirectory } else { consola.success(`Using ${directory} as name.`) } if (typeof directory !== 'string') { return } directory = getSafeDirectory(directory) const hasErrors = await downloadTemplateToDirectory(directory) if (!hasErrors) { /** * @type Array */ const prompts = [ { prompt: `Would you like to install the dependencies to ${colorize( 'bold', directory )}?`, info: 'This might take some time depending on your connectivity.', startMessage: 'Installing dependencies using npm...', command: `npm`, subCommand: 'install', error: `Install dependencies later with ${colorize( 'yellow', 'npm install' )}`, }, { prompt: 'Would you like to initialize your git repository?', startMessage: 'Initializing git repository...', command: `git`, subCommand: 'init', error: `Initialize git repository later with ${colorize( 'yellow', 'git init' )}`, }, ] const intentions = await askPrompts(prompts, directory) if (!!intentions && intentions.length > 0) showResults(directory, intentions[0]) } } /** * Returns string that is safe for commands * @param {string} directory * @returns string | undefined */ function getSafeDirectory(directory) { const { platform } = process const locale = path[platform === `win32` ? `win32` : `posix`] const localePath = directory.split(path.sep).join(locale.sep) return path.normalize(localePath) } /** * Tries to download the template to the directory and returns a Promise whether the operation resulted to errors * @param {string} directory * @returns Promise hasErrors */ async function downloadTemplateToDirectory(directory) { let hasErrors = false try { consola.start( `Copying template to ${colorize('bold', getSafeDirectory(directory))}...` ) await downloadTemplate('github:ayoayco/mcfly/templates/basic', { dir: directory, }) } catch (ㆆ_ㆆ) { consola.error(ㆆ_ㆆ.message) consola.info('Try a different name.\n') hasErrors = true } return hasErrors } /** * * @param {Array} prompts * @param {string} cwd * @returns Array | undefined */ async function askPrompts(prompts, cwd) { const results = [] for (const p of prompts) { const userIntends = await consola.prompt(p.prompt, { type: 'confirm', }) if (typeof userIntends !== 'boolean') { return } if (userIntends) { p.info && consola.info(p.info) consola.start(p.startMessage) try { spawnSync(p.command, [p.subCommand], { cwd, shell: true, timeout: 100_000, stdio: 'inherit', }) consola.success('Done!') } catch (ㆆ_ㆆ) { consola.error(ㆆ_ㆆ.message) consola.info(p.error + '\n') } } results.push(userIntends) } return results } /** * Displays the success result string based on directory and choices * @param {string} directory * @param {boolean} installDeps */ function showResults(directory, installDeps) { let nextActions = [ `Go to your project by running ${colorize('yellow', `cd ${directory}`)}`, ] if (!installDeps) { nextActions.push( `Install the dependencies with ${colorize('yellow', 'npm install')}` ) } nextActions = nextActions.concat([ `Start the dev server with ${colorize('yellow', 'npm start')}`, `Join us at ${colorize('blue', 'https://ayco.io/gh/McFly')}`, ]) const result = `🎉 Your new ${colorize( 'bold', 'McFly' )} app is ready: ${directory}\n\nNext actions: ${nextActions .map((action, index) => `\n${++index}. ${action}`) .join('')}` consola.box(result) } create()