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