1dfe12d45SEvan Baconimport path from 'path'; 2dfe12d45SEvan Baconimport resolveFrom from 'resolve-from'; 3dfe12d45SEvan Bacon 48a424bebSJames Ideimport { DestinationResolutionProps, selectTemplatesAsync, TEMPLATES } from './templates'; 5dfe12d45SEvan Baconimport { installAsync } from '../install/installAsync'; 6dfe12d45SEvan Baconimport { Log } from '../log'; 7dfe12d45SEvan Baconimport { copyAsync } from '../utils/dir'; 8dfe12d45SEvan Baconimport { CommandError } from '../utils/errors'; 9dfe12d45SEvan Bacon 10dfe12d45SEvan Baconexport async function queryAndGenerateAsync( 11dfe12d45SEvan Bacon projectRoot: string, 12dfe12d45SEvan Bacon { 13dfe12d45SEvan Bacon files, 14dfe12d45SEvan Bacon props, 15dfe12d45SEvan Bacon extras, 16dfe12d45SEvan Bacon }: { 17dfe12d45SEvan Bacon files: string[]; 18dfe12d45SEvan Bacon props: DestinationResolutionProps; 19dfe12d45SEvan Bacon /** Any extra props to pass to the install command. */ 20dfe12d45SEvan Bacon extras: any[]; 21dfe12d45SEvan Bacon } 22dfe12d45SEvan Bacon) { 23dfe12d45SEvan Bacon const valid = files.filter( 24dfe12d45SEvan Bacon (file) => !!TEMPLATES.find((template) => template.destination(props) === file) 25dfe12d45SEvan Bacon ); 26dfe12d45SEvan Bacon 27dfe12d45SEvan Bacon if (valid.length !== files.length) { 28dfe12d45SEvan Bacon const diff = files.filter( 29dfe12d45SEvan Bacon (file) => !TEMPLATES.find((template) => template.destination(props) === file) 30dfe12d45SEvan Bacon ); 31dfe12d45SEvan Bacon throw new CommandError( 32dfe12d45SEvan Bacon `Invalid files: ${diff.join(', ')}. Allowed: ${TEMPLATES.map((template) => 33dfe12d45SEvan Bacon template.destination(props) 34dfe12d45SEvan Bacon ).join(', ')}` 35dfe12d45SEvan Bacon ); 36dfe12d45SEvan Bacon } 37dfe12d45SEvan Bacon 38dfe12d45SEvan Bacon if (!valid.length) { 39dfe12d45SEvan Bacon return; 40dfe12d45SEvan Bacon } 41dfe12d45SEvan Bacon Log.log(`Generating: ${valid.join(', ')}`); 42dfe12d45SEvan Bacon return generateAsync(projectRoot, { 43dfe12d45SEvan Bacon answer: files.map((file) => 44dfe12d45SEvan Bacon TEMPLATES.findIndex((template) => template.destination(props) === file) 45dfe12d45SEvan Bacon ), 46dfe12d45SEvan Bacon props, 47dfe12d45SEvan Bacon extras, 48dfe12d45SEvan Bacon }); 49dfe12d45SEvan Bacon} 50dfe12d45SEvan Bacon 51dfe12d45SEvan Bacon/** Select templates to generate then generate and install. */ 52dfe12d45SEvan Baconexport async function selectAndGenerateAsync( 53dfe12d45SEvan Bacon projectRoot: string, 54dfe12d45SEvan Bacon { 55dfe12d45SEvan Bacon props, 56dfe12d45SEvan Bacon extras, 57dfe12d45SEvan Bacon }: { 58dfe12d45SEvan Bacon props: DestinationResolutionProps; 59dfe12d45SEvan Bacon /** Any extra props to pass to the install command. */ 60dfe12d45SEvan Bacon extras: any[]; 61dfe12d45SEvan Bacon } 62dfe12d45SEvan Bacon) { 63dfe12d45SEvan Bacon const answer = await selectTemplatesAsync(projectRoot, props); 64dfe12d45SEvan Bacon 65dfe12d45SEvan Bacon if (!answer?.length) { 66dfe12d45SEvan Bacon Log.exit('\n\u203A Exiting with no change...', 0); 67dfe12d45SEvan Bacon } 68dfe12d45SEvan Bacon 69dfe12d45SEvan Bacon await generateAsync(projectRoot, { 70dfe12d45SEvan Bacon answer, 71dfe12d45SEvan Bacon props, 72dfe12d45SEvan Bacon extras, 73dfe12d45SEvan Bacon }); 74dfe12d45SEvan Bacon} 75dfe12d45SEvan Bacon 76dfe12d45SEvan Baconasync function generateAsync( 77dfe12d45SEvan Bacon projectRoot: string, 78dfe12d45SEvan Bacon { 79dfe12d45SEvan Bacon answer, 80dfe12d45SEvan Bacon props, 81dfe12d45SEvan Bacon extras, 82dfe12d45SEvan Bacon }: { 83dfe12d45SEvan Bacon answer: number[]; 84dfe12d45SEvan Bacon props: DestinationResolutionProps; 85dfe12d45SEvan Bacon /** Any extra props to pass to the install command. */ 86dfe12d45SEvan Bacon extras: any[]; 87dfe12d45SEvan Bacon } 88dfe12d45SEvan Bacon) { 89dfe12d45SEvan Bacon // Copy files 90dfe12d45SEvan Bacon await Promise.all( 9187669a95SMark Lawlor answer.map(async (file) => { 92dfe12d45SEvan Bacon const template = TEMPLATES[file]; 9387669a95SMark Lawlor 9487669a95SMark Lawlor if (template.id === 'tsconfig.json') { 95*1a3a1db5SEvan Bacon const { typescript } = await import('./typescript.js'); 9687669a95SMark Lawlor return typescript(projectRoot); 9787669a95SMark Lawlor } 9887669a95SMark Lawlor 99dfe12d45SEvan Bacon const projectFilePath = path.resolve(projectRoot, template.destination(props)); 100dfe12d45SEvan Bacon // copy the file from template 101dfe12d45SEvan Bacon return copyAsync(template.file(projectRoot), projectFilePath, { 102dfe12d45SEvan Bacon overwrite: true, 103dfe12d45SEvan Bacon recursive: true, 104dfe12d45SEvan Bacon }); 105dfe12d45SEvan Bacon }) 106dfe12d45SEvan Bacon ); 107dfe12d45SEvan Bacon 108dfe12d45SEvan Bacon // Install dependencies 109dfe12d45SEvan Bacon const packages = answer 110dfe12d45SEvan Bacon .map((file) => TEMPLATES[file].dependencies) 111dfe12d45SEvan Bacon .flat() 112dfe12d45SEvan Bacon .filter((pkg) => !resolveFrom.silent(projectRoot, pkg)); 113dfe12d45SEvan Bacon if (packages.length) { 114dfe12d45SEvan Bacon Log.debug('Installing ' + packages.join(', ')); 115dfe12d45SEvan Bacon await installAsync(packages, {}, ['--dev', ...extras]); 116dfe12d45SEvan Bacon } 117dfe12d45SEvan Bacon} 118