| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289 |
- import { select, text } from '@clack/prompts';
- import { SUPER_ADMIN_USER_IDENTIFIER, SUPER_ADMIN_USER_PASSWORD } from '@vendure/common/lib/shared-constants';
- import { randomBytes } from 'crypto';
- import fs from 'fs-extra';
- import Handlebars from 'handlebars';
- import path from 'path';
- import { checkCancel, isDockerAvailable } from './helpers';
- import { DbType, FileSources, PackageManager, UserResponses } from './types';
- interface PromptAnswers {
- dbType: DbType;
- dbHost: string | symbol;
- dbPort: string | symbol;
- dbName: string | symbol;
- dbSchema?: string | symbol;
- dbUserName: string | symbol;
- dbPassword: string | symbol;
- dbSSL?: boolean | symbol;
- superadminIdentifier: string | symbol;
- superadminPassword: string | symbol;
- populateProducts: boolean | symbol;
- }
- /* eslint-disable no-console */
- export async function getQuickStartConfiguration(
- root: string,
- packageManager: PackageManager,
- ): Promise<UserResponses> {
- // First we want to detect whether Docker is running
- const { result: dockerStatus } = await isDockerAvailable();
- let usePostgres: boolean;
- switch (dockerStatus) {
- case 'running':
- usePostgres = true;
- break;
- case 'not-found':
- usePostgres = false;
- break;
- case 'not-running': {
- let useSqlite = false;
- let dockerIsNowRunning = false;
- do {
- const useSqliteResponse = await select({
- message: 'We could not automatically start Docker. How should we proceed?',
- options: [
- { label: `Let's use SQLite as the database`, value: true },
- { label: 'I have manually started Docker', value: false },
- ],
- initialValue: true,
- });
- checkCancel(useSqlite);
- useSqlite = useSqliteResponse as boolean;
- if (useSqlite === false) {
- const { result: dockerStatusManual } = await isDockerAvailable();
- dockerIsNowRunning = dockerStatusManual === 'running';
- }
- } while (dockerIsNowRunning !== true && useSqlite === false);
- usePostgres = !useSqlite;
- break;
- }
- }
- const quickStartAnswers: PromptAnswers = {
- dbType: usePostgres ? 'postgres' : 'sqlite',
- dbHost: usePostgres ? 'localhost' : '',
- dbPort: usePostgres ? '6543' : '',
- dbName: usePostgres ? 'vendure' : '',
- dbUserName: usePostgres ? 'vendure' : '',
- dbPassword: usePostgres ? randomBytes(16).toString('base64url') : '',
- dbSchema: usePostgres ? 'public' : '',
- populateProducts: true,
- superadminIdentifier: SUPER_ADMIN_USER_IDENTIFIER,
- superadminPassword: SUPER_ADMIN_USER_PASSWORD,
- };
- const responses = {
- ...(await generateSources(root, quickStartAnswers, packageManager)),
- dbType: quickStartAnswers.dbType,
- populateProducts: quickStartAnswers.populateProducts as boolean,
- superadminIdentifier: quickStartAnswers.superadminIdentifier as string,
- superadminPassword: quickStartAnswers.superadminPassword as string,
- };
- return responses;
- }
- /**
- * Prompts the user to determine how the new Vendure app should be configured.
- */
- export async function getManualConfiguration(
- root: string,
- packageManager: PackageManager,
- ): Promise<UserResponses> {
- const dbType = (await select({
- message: 'Which database are you using?',
- options: [
- { label: 'MySQL', value: 'mysql' },
- { label: 'MariaDB', value: 'mariadb' },
- { label: 'Postgres', value: 'postgres' },
- { label: 'SQLite', value: 'sqlite' },
- ],
- initialValue: 'sqlite' as DbType,
- })) as DbType;
- checkCancel(dbType);
- const hasConnection = dbType !== 'sqlite';
- const dbHost = hasConnection
- ? await text({
- message: "What's the database host address?",
- initialValue: 'localhost',
- })
- : '';
- checkCancel(dbHost);
- const dbPort = hasConnection
- ? await text({
- message: 'What port is the database listening on?',
- initialValue: defaultDBPort(dbType).toString(),
- })
- : '';
- checkCancel(dbPort);
- const dbName = hasConnection
- ? await text({
- message: "What's the name of the database?",
- initialValue: 'vendure',
- })
- : '';
- checkCancel(dbName);
- const dbSchema =
- dbType === 'postgres'
- ? await text({
- message: "What's the schema name we should use?",
- initialValue: 'public',
- })
- : '';
- checkCancel(dbSchema);
- const dbSSL =
- dbType === 'postgres'
- ? await select({
- message:
- 'Use SSL to connect to the database? (only enable if your database provider supports SSL)',
- options: [
- { label: 'no', value: false },
- { label: 'yes', value: true },
- ],
- initialValue: false,
- })
- : false;
- checkCancel(dbSSL);
- const dbUserName = hasConnection
- ? await text({
- message: "What's the database user name?",
- })
- : '';
- checkCancel(dbUserName);
- const dbPassword = hasConnection
- ? await text({
- message: "What's the database password?",
- defaultValue: '',
- })
- : '';
- checkCancel(dbPassword);
- const superadminIdentifier = await text({
- message: 'What identifier do you want to use for the superadmin user?',
- initialValue: SUPER_ADMIN_USER_IDENTIFIER,
- });
- checkCancel(superadminIdentifier);
- const superadminPassword = await text({
- message: 'What password do you want to use for the superadmin user?',
- initialValue: SUPER_ADMIN_USER_PASSWORD,
- });
- checkCancel(superadminPassword);
- const populateProducts = await select({
- message: 'Populate with some sample product data?',
- options: [
- { label: 'yes', value: true },
- { label: 'no', value: false },
- ],
- initialValue: true,
- });
- checkCancel(populateProducts);
- const answers: PromptAnswers = {
- dbType,
- dbHost,
- dbPort,
- dbName,
- dbSchema,
- dbUserName,
- dbPassword,
- dbSSL,
- superadminIdentifier,
- superadminPassword,
- populateProducts,
- };
- return {
- ...(await generateSources(root, answers, packageManager)),
- dbType,
- populateProducts: answers.populateProducts as boolean,
- superadminIdentifier: answers.superadminIdentifier as string,
- superadminPassword: answers.superadminPassword as string,
- };
- }
- /**
- * Returns mock "user response" without prompting, for use in CI
- */
- export async function getCiConfiguration(
- root: string,
- packageManager: PackageManager,
- ): Promise<UserResponses> {
- const ciAnswers = {
- dbType: 'sqlite' as const,
- dbHost: '',
- dbPort: '',
- dbName: 'vendure',
- dbUserName: '',
- dbPassword: '',
- populateProducts: true,
- superadminIdentifier: SUPER_ADMIN_USER_IDENTIFIER,
- superadminPassword: SUPER_ADMIN_USER_PASSWORD,
- };
- return {
- ...(await generateSources(root, ciAnswers, packageManager)),
- dbType: ciAnswers.dbType,
- populateProducts: ciAnswers.populateProducts,
- superadminIdentifier: ciAnswers.superadminIdentifier,
- superadminPassword: ciAnswers.superadminPassword,
- };
- }
- /**
- * Create the server index, worker and config source code based on the options specified by the CLI prompts.
- */
- async function generateSources(
- root: string,
- answers: PromptAnswers,
- packageManager: PackageManager,
- ): Promise<FileSources> {
- const assetPath = (fileName: string) => path.join(__dirname, '../assets', fileName);
- /**
- * Helper to escape single quotes only. Used when generating the config file since e.g. passwords
- * might use special chars (`< > ' "` etc) which Handlebars would be default convert to HTML entities.
- * Instead, we disable escaping and use this custom helper to escape only the single quote character.
- */
- Handlebars.registerHelper('escapeSingle', (aString: unknown) => {
- return typeof aString === 'string' ? aString.replace(/'/g, "\\'") : aString;
- });
- const templateContext = {
- ...answers,
- dbType: answers.dbType === 'sqlite' ? 'better-sqlite3' : answers.dbType,
- name: path.basename(root),
- isSQLite: answers.dbType === 'sqlite',
- requiresConnection: answers.dbType !== 'sqlite',
- cookieSecret: randomBytes(16).toString('base64url'),
- };
- async function createSourceFile(filename: string, noEscape = false): Promise<string> {
- const template = await fs.readFile(assetPath(filename), 'utf-8');
- return Handlebars.compile(template, { noEscape })(templateContext);
- }
- return {
- indexSource: await createSourceFile('index.hbs'),
- indexWorkerSource: await createSourceFile('index-worker.hbs'),
- configSource: await createSourceFile('vendure-config.hbs', true),
- envSource: await createSourceFile('.env.hbs', true),
- envDtsSource: await createSourceFile('environment.d.hbs', true),
- readmeSource: await createSourceFile('readme.hbs'),
- dockerfileSource: await createSourceFile('Dockerfile.hbs'),
- dockerComposeSource: await createSourceFile('docker-compose.hbs'),
- };
- }
- function defaultDBPort(dbType: DbType): number {
- switch (dbType) {
- case 'mysql':
- case 'mariadb':
- return 3306;
- case 'postgres':
- return 5432;
- default:
- return 3306;
- }
- }
|