import { dasherize } from '@angular-devkit/core/src/utils/strings' ; import * as chalk from 'chalk' ; import { execSync } from 'child_process' ; import * as fs from 'fs' ; import * as inquirer from 'inquirer' ; import { Answers , Question } from 'inquirer' ; import { join } from 'path' ; import { promisify } from 'util' ; import { Input } from '../commands' ; import { defaultGitIgnore } from '../lib/configuration/defaults' ; import { AbstractPackageManager , PackageManager , PackageManagerFactory , } from '../lib/package-managers' ; import { generateInput , generateSelect } from '../lib/questions/questions' ; import { GitRunner } from '../lib/runners/git.runner' ; import { AbstractCollection , Collection , CollectionFactory , SchematicOption , } from '../lib/schematics' ; import { EMOJIS , MESSAGES } from '../lib/ui' ; import { AbstractAction } from './abstract.action' ; export class NewAction extends AbstractAction { public async handle ( inputs , options ) { const directoryOption = options . find ( ( option ) => option . name === 'directory' ) ; const dryRunOption = options . find ( ( option ) => option . name === 'dry-run' ) ; const isDryRunEnabled = dryRunOption && dryRunOption . value ; await askForMissingInformation ( inputs ) ; await generateApplicationFiles ( inputs , options ) . catch ( exit ) ; const shouldSkipInstall = options . some ( ( option ) => option . name === 'skip-install' && option . value === true ) ; const shouldSkipGit = options . some ( ( option ) => option . name === 'skip-git' && option . value === true ) ; const projectDirectory = getProjectDirectory ( getApplicationNameInput ( inputs ) ! , directoryOption ) ; if ( ! shouldSkipInstall ) { await installPackages ( options , isDryRunEnabled as boolean , projectDirectory ) ; } if ( ! isDryRunEnabled ) { if ( ! shouldSkipGit ) { await initializeGitRepository ( projectDirectory ) ; await createGitIgnoreFile ( projectDirectory ) ; } printCollective ( ) ; } process . exit ( 0 ) ; } } const getApplicationNameInput = ( inputs ) => inputs . find ( ( input ) => input . name === 'name' ) ; const getProjectDirectory = ( applicationName , directoryOption ? ) : string => { return ( ( directoryOption && ( directoryOption . value as string ) ) || dasherize ( applicationName . value as string ) ) ; } ; const askForMissingInformation = async ( inputs ) => { console . info ( MESSAGES . PROJECT_INFORMATION_START ) ; console . info ( ) ; const prompt = inquirer . createPromptModule ( ) ; const nameInput = getApplicationNameInput ( inputs ) ; if ( ! nameInput ! . value ) { const message = 'What name would you like to use for the new project?' ; const questions = [ generateInput ( 'name' , message ) ( 'nest-app' ) ] ; const answers = await prompt ( questions as ReadonlyArray < Question > ) ; replaceInputMissingInformation ( inputs , answers ) ; } } ; const replaceInputMissingInformation = ( inputs , answers ) : Input [ ] => { return inputs . map ( ( input ) => ( input . value = input . value !== undefined ? input . value : answers [ input . name ] ) ) ; } ; const generateApplicationFiles = async ( args , options ) => { const collectionName = options . find ( ( option ) => option . name === 'collection' && option . value != null ) ! . value ; const collection = CollectionFactory . create ( ( collectionName as Collection ) || Collection . NESTJS ) ; const schematicOptions = mapSchematicOptions ( args . concat ( options ) ) ; await collection . execute ( 'application' , schematicOptions ) ; console . info ( ) ; } ; const mapSchematicOptions = ( options ) : SchematicOption [ ] => { return options . reduce ( ( schematicOptions , option ) => { if ( option . name !== 'skip-install' && option . value !== 'package-manager' ) { schematicOptions . push ( new SchematicOption ( option . name , option . value ) ) ; } return schematicOptions ; } , [ ] ) ; } ; const installPackages = async ( options , dryRunMode , installDirectory ) => { const inputPackageManager = options . find ( ( option ) => option . name === 'package-manager' ) ! . value as string ; let packageManager ; if ( dryRunMode ) { console . info ( ) ; console . info ( chalk . green ( MESSAGES . DRY_RUN_MODE ) ) ; console . info ( ) ; return ; } if ( inputPackageManager !== undefined ) { try { packageManager = PackageManagerFactory . create ( inputPackageManager ) ; await packageManager . install ( installDirectory , inputPackageManager ) ; } catch ( error ) { if ( error && error . message ) { console . error ( chalk . red ( error . message ) ) ; } } } else { packageManager = await selectPackageManager ( ) ; await packageManager . install ( installDirectory , packageManager . name . toLowerCase ( ) ) ; } } ; const selectPackageManager = async ( ) : Promise < AbstractPackageManager > => { const answers = await askForPackageManager ( ) ; return PackageManagerFactory . create ( answers [ 'package-manager' ] ) ; } ; const askForPackageManager = async ( ) : Promise < Answers > => { const questions = [ generateSelect ( 'package-manager' ) ( MESSAGES . PACKAGE_MANAGER_QUESTION ) ( [ PackageManager . NPM , PackageManager . YARN , PackageManager . PNPM ] ) , ] ; const prompt = inquirer . createPromptModule ( ) ; return await prompt ( questions ) ; } ; const initializeGitRepository = async ( dir ) => { const runner = new GitRunner ( ) ; await runner . run ( 'init' , true , join ( process . cwd ( ) , dir ) ) . catch ( ( ) => { console . error ( chalk . red ( MESSAGES . GIT_INITIALIZATION_ERROR ) ) ; } ) ; } ; param dir param content return const createGitIgnoreFile = ( dir , content ? ) => { const fileContent = content || defaultGitIgnore ; const filePath = join ( process . cwd ( ) , dir , '.gitignore' ) ; return promisify ( fs . writeFile ) ( filePath , fileContent ) ; } ; const printCollective = ( ) => { const dim = print ( 'dim' ) ; const yellow = print ( 'yellow' ) ; const emptyLine = print ( ) ; emptyLine ( ) ; yellow ( `Thanks for installing Nest ${ EMOJIS . PRAY }` ) ; dim ( 'Please consider donating to our open collective' ) ; dim ( 'to help us maintain this package.' ) ; emptyLine ( ) ; emptyLine ( ) ; print ( ) ( `${ chalk . bold ( `${ EMOJIS . WINE }  Donate:` ) } ${ chalk . underline ( 'https://opencollective.com/nest' ) }` ) ; emptyLine ( ) ; } ; const print = ( color = null ) => ( str = '' ) => { const terminalCols = retrieveCols ( ) ; const strLength = str . replace ( /\u001b\[[0-9]{2}m/g , '' ) . length ; const leftPaddingLength = Math . floor ( ( terminalCols - strLength ) / 2 ) ; const leftPadding = ' ' . repeat ( Math . max ( leftPaddingLength , 0 ) ) ; if ( color ) { str = ( chalk as any ) [ color ] ( str ) ; } console . log ( leftPadding , str ) ; } ; export const retrieveCols = ( ) => { const defaultCols = 80 ; try { const terminalCols = execSync ( 'tput cols' , { stdio : [ 'pipe' , 'pipe' , 'ignore' ] , } ) ; return parseInt ( terminalCols . toString ( ) , 10 ) || defaultCols ; } catch { return defaultCols ; } } ; export const exit = ( ) => process . exit ( 1 ) ;