1
0

gather-user-responses.ts 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. import { SUPER_ADMIN_USER_IDENTIFIER, SUPER_ADMIN_USER_PASSWORD } from '@vendure/common/lib/shared-constants';
  2. import fs from 'fs-extra';
  3. import Handlebars from 'handlebars';
  4. import path from 'path';
  5. import prompts, { PromptObject } from 'prompts';
  6. import { DbType, FileSources, UserResponses } from './types';
  7. // tslint:disable:no-console
  8. /**
  9. * Prompts the user to determine how the new Vendure app should be configured.
  10. */
  11. export async function gatherUserResponses(
  12. root: string,
  13. alreadyRanScaffold: boolean,
  14. useYarn: boolean,
  15. ): Promise<UserResponses> {
  16. function onSubmit(prompt: PromptObject, answer: any) {
  17. if (prompt.name === 'dbType') {
  18. dbType = answer;
  19. }
  20. }
  21. let dbType: DbType;
  22. const scaffoldPrompts: Array<prompts.PromptObject<any>> = [
  23. {
  24. type: 'select',
  25. name: 'dbType',
  26. message: 'Which database are you using?',
  27. choices: [
  28. { title: 'MySQL', value: 'mysql' },
  29. { title: 'MariaDB', value: 'mariadb' },
  30. { title: 'Postgres', value: 'postgres' },
  31. { title: 'SQLite', value: 'sqlite' },
  32. { title: 'SQL.js', value: 'sqljs' },
  33. // Don't show these until they have been tested.
  34. // { title: 'MS SQL Server', value: 'mssql' },
  35. // { title: 'Oracle', value: 'oracle' },
  36. ],
  37. initial: 0 as any,
  38. },
  39. {
  40. type: (() => (dbType === 'sqlite' || dbType === 'sqljs' ? null : 'text')) as any,
  41. name: 'dbHost',
  42. message: `What's the database host address?`,
  43. initial: 'localhost',
  44. },
  45. {
  46. type: (() => (dbType === 'sqlite' || dbType === 'sqljs' ? null : 'text')) as any,
  47. name: 'dbPort',
  48. message: `What port is the database listening on?`,
  49. initial: (() => defaultDBPort(dbType)) as any,
  50. },
  51. {
  52. type: (() => (dbType === 'sqlite' || dbType === 'sqljs' ? null : 'text')) as any,
  53. name: 'dbName',
  54. message: `What's the name of the database?`,
  55. initial: 'vendure',
  56. },
  57. {
  58. type: (() => (dbType === 'postgres' ? 'text' : null)) as any,
  59. name: 'dbSchema',
  60. message: `What's the schema name we should use?`,
  61. initial: 'public',
  62. },
  63. {
  64. type: (() => (dbType === 'sqlite' || dbType === 'sqljs' ? null : 'text')) as any,
  65. name: 'dbUserName',
  66. message: `What's the database user name?`,
  67. initial: 'root',
  68. },
  69. {
  70. type: (() => (dbType === 'sqlite' || dbType === 'sqljs' ? null : 'password')) as any,
  71. name: 'dbPassword',
  72. message: `What's the database password?`,
  73. },
  74. {
  75. type: 'text',
  76. name: 'superadminIdentifier',
  77. message: 'What identifier do you want to use for the superadmin user?',
  78. initial: SUPER_ADMIN_USER_IDENTIFIER,
  79. },
  80. {
  81. type: 'text',
  82. name: 'superadminPassword',
  83. message: 'What password do you want to use for the superadmin user?',
  84. initial: SUPER_ADMIN_USER_PASSWORD,
  85. },
  86. ];
  87. const initPrompts: Array<prompts.PromptObject<any>> = [
  88. {
  89. type: 'toggle',
  90. name: 'populateProducts',
  91. message: 'Populate with some sample product data?',
  92. initial: true,
  93. active: 'yes',
  94. inactive: 'no',
  95. },
  96. ];
  97. const answers = await prompts(alreadyRanScaffold ? initPrompts : [...scaffoldPrompts, ...initPrompts], {
  98. onSubmit,
  99. onCancel() {
  100. /* */
  101. console.log(`Setup cancelled`);
  102. process.exit(1);
  103. },
  104. });
  105. return {
  106. ...(await generateSources(root, answers, useYarn)),
  107. dbType: answers.dbType,
  108. populateProducts: answers.populateProducts,
  109. superadminIdentifier: answers.superadminIdentifier,
  110. superadminPassword: answers.superadminPassword,
  111. };
  112. }
  113. /**
  114. * Returns mock "user response" without prompting, for use in CI
  115. */
  116. export async function gatherCiUserResponses(root: string, useYarn: boolean): Promise<UserResponses> {
  117. const ciAnswers = {
  118. dbType: 'sqlite' as const,
  119. dbHost: '',
  120. dbPort: '',
  121. dbName: 'vendure',
  122. dbUserName: '',
  123. dbPassword: '',
  124. populateProducts: true,
  125. superadminIdentifier: SUPER_ADMIN_USER_IDENTIFIER,
  126. superadminPassword: SUPER_ADMIN_USER_PASSWORD,
  127. };
  128. return {
  129. ...(await generateSources(root, ciAnswers, useYarn)),
  130. dbType: ciAnswers.dbType,
  131. populateProducts: ciAnswers.populateProducts,
  132. superadminIdentifier: ciAnswers.superadminIdentifier,
  133. superadminPassword: ciAnswers.superadminPassword,
  134. };
  135. }
  136. /**
  137. * Create the server index, worker and config source code based on the options specified by the CLI prompts.
  138. */
  139. async function generateSources(root: string, answers: any, useYarn: boolean): Promise<FileSources> {
  140. const assetPath = (fileName: string) => path.join(__dirname, '../assets', fileName);
  141. /**
  142. * Helper to escape single quotes only. Used when generating the config file since e.g. passwords
  143. * might use special chars (`< > ' "` etc) which Handlebars would be default convert to HTML entities.
  144. * Instead, we disable escaping and use this custom helper to escape only the single quote character.
  145. */
  146. Handlebars.registerHelper('escapeSingle', (aString: unknown) => {
  147. return typeof aString === 'string' ? aString.replace(/'/g, `\\'`) : aString;
  148. });
  149. const templateContext = {
  150. ...answers,
  151. useYarn,
  152. dbType: answers.dbType === 'sqlite' ? 'better-sqlite3' : answers.dbType,
  153. name: path.basename(root),
  154. isSQLite: answers.dbType === 'sqlite',
  155. isSQLjs: answers.dbType === 'sqljs',
  156. requiresConnection: answers.dbType !== 'sqlite' && answers.dbType !== 'sqljs',
  157. cookieSecret: Math.random().toString(36).substr(2),
  158. };
  159. async function createSourceFile(filename: string, noEscape = false): Promise<string> {
  160. const template = await fs.readFile(assetPath(filename), 'utf-8');
  161. return Handlebars.compile(template, { noEscape })(templateContext);
  162. }
  163. return {
  164. indexSource: await createSourceFile('index.hbs'),
  165. indexWorkerSource: await createSourceFile('index-worker.hbs'),
  166. configSource: await createSourceFile('vendure-config.hbs', true),
  167. envSource: await createSourceFile('.env.hbs', true),
  168. envDtsSource: await createSourceFile('environment.d.hbs', true),
  169. migrationSource: await createSourceFile('migration.hbs'),
  170. readmeSource: await createSourceFile('readme.hbs'),
  171. dockerfileSource: await createSourceFile('Dockerfile.hbs'),
  172. dockerComposeSource: await createSourceFile('docker-compose.hbs'),
  173. };
  174. }
  175. function defaultDBPort(dbType: DbType): number {
  176. switch (dbType) {
  177. case 'mysql':
  178. case 'mariadb':
  179. return 3306;
  180. case 'postgres':
  181. return 5432;
  182. case 'mssql':
  183. return 1433;
  184. case 'oracle':
  185. return 1521;
  186. default:
  187. return 3306;
  188. }
  189. }