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