1import inquirer, { Question } from 'inquirer';
2
3import { ModuleConfiguration } from './ModuleConfiguration';
4
5/**
6 * Generates CocoaPod name in format `Namepart1Namepart2Namepart3`.
7 * For these with `expo` as `partname1` would generate `EXNamepart2...`.
8 * @param {string} moduleName - provided module name, expects format: `namepart1-namepart2-namepart3`
9 */
10const generateCocoaPodDefaultName = (moduleName: string) => {
11  const wordsToUpperCase = (s: string) =>
12    s
13      .toLowerCase()
14      .split('-')
15      .map((s) => s.charAt(0).toUpperCase() + s.substring(1))
16      .join('');
17
18  if (moduleName.toLowerCase().startsWith('expo')) {
19    return `EX${wordsToUpperCase(moduleName.substring(4))}`;
20  }
21  return `EX${wordsToUpperCase(moduleName)}`;
22};
23
24/**
25 * Generates java package name in format `namepart1.namepart2.namepart3`.
26 * @param moduleName - provided module name, expects format: `namepart1-namepart2-namepart3`
27 */
28const generateJavaModuleDefaultName = (moduleName: string) => {
29  const wordsToJavaModule = (s: string) => s.toLowerCase().split('-').join('');
30
31  if (moduleName.toLowerCase().startsWith('expo')) {
32    return `expo.modules.${wordsToJavaModule(moduleName.substring(4))}`;
33  }
34  return wordsToJavaModule(moduleName);
35};
36
37/**
38 * Generates JS/TS module name in format `Namepart1Namepart2Namepart3`.
39 * @param moduleName - provided module name, expects format: `namepart1-namepart2-namepart3`
40 */
41const generateInCodeModuleDefaultName = (moduleName: string) => {
42  return moduleName
43    .toLowerCase()
44    .split('-')
45    .map((s) => s.charAt(0).toUpperCase() + s.substring(1))
46    .join('');
47};
48
49/**
50 * Generates questions
51 */
52const generateQuestions = (suggestedModuleName: string): Question[] => [
53  {
54    name: 'npmModuleName',
55    message: 'How would you like to call your module in JS/npm? (eg. expo-camera)',
56    default: suggestedModuleName,
57    validate: (answer: string) => {
58      return !answer.length
59        ? 'Module name cannot be empty'
60        : /[A-Z]/.test(answer)
61        ? 'Module name cannot contain any upper case characters'
62        : /\s/.test(answer)
63        ? 'Module name cannot contain any whitespaces'
64        : true;
65    },
66  },
67  {
68    name: 'podName',
69    message: 'How would you like to call your module in CocoaPods? (eg. EXCamera)',
70    default: ({ npmModuleName }) => generateCocoaPodDefaultName(npmModuleName),
71    validate: (answer: string) =>
72      !answer.length
73        ? 'CocoaPod name cannot be empty'
74        : /\s/.test(answer)
75        ? 'CocoaPod name cannot contain any whitespaces'
76        : true,
77  },
78  {
79    name: 'javaPackage',
80    message: 'How would you like to call your module in Java? (eg. expo.modules.camera)',
81    default: ({ npmModuleName }) => generateJavaModuleDefaultName(npmModuleName),
82    validate: (answer: string) =>
83      !answer.length
84        ? 'Java Package name cannot be empty'
85        : /\s/.test(answer)
86        ? 'Java Package name cannot contain any whitespaces'
87        : true,
88  },
89  {
90    name: 'jsPackageName',
91    message: 'How would you like to call your module in JS/TS codebase (eg. ExpoCamera)?',
92    default: ({ npmModuleName }) => generateInCodeModuleDefaultName(npmModuleName),
93    validate: (answer: string) =>
94      !answer.length
95        ? 'Module name cannot be empty'
96        : /\s/.test(answer)
97        ? 'Module name cannot contain any whitespaces'
98        : true,
99  },
100  {
101    name: 'viewManager',
102    message: 'Would you like to create a NativeViewManager?',
103    default: false,
104    type: 'confirm',
105  },
106];
107
108/**
109 * Prompt user about new module namings.
110 * @param suggestedModuleName - suggested module name that would be used to generate all suggestions for each question
111 */
112export default async function promptQuestionsAsync(
113  suggestedModuleName: string
114): Promise<ModuleConfiguration> {
115  const questions = generateQuestions(suggestedModuleName);
116  // non interactive check
117  if (!process.stdin.isTTY) {
118    let message = `Input is required, but expotools is in a non-interactive shell.\n`;
119    const firstQuestion = (questions[0].message || '') as string;
120    message += `Required input:\n${firstQuestion.trim().replace(/^/gm, '> ')}`;
121    throw new Error(message);
122  }
123  // TODO: Migrate to prompts and remove inquirer
124  return (await inquirer.prompt(questions)) as ModuleConfiguration;
125}
126