| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338 |
- /* tslint:disable:no-console */
- import chalk from 'chalk';
- import { execSync } from 'child_process';
- import spawn from 'cross-spawn';
- import fs from 'fs-extra';
- import path from 'path';
- import semver from 'semver';
- import { DbType, LogLevel } from './types';
- /**
- * If project only contains files generated by GH, it’s safe.
- * Also, if project contains remnant error logs from a previous
- * installation, lets remove them now.
- * We also special case IJ-based products .idea because it integrates with CRA:
- * https://github.com/facebook/create-react-app/pull/368#issuecomment-243446094
- */
- export function isSafeToCreateProjectIn(root: string, name: string) {
- // These files should be allowed to remain on a failed install,
- // but then silently removed during the next create.
- const errorLogFilePatterns = ['npm-debug.log', 'yarn-error.log', 'yarn-debug.log'];
- const validFiles = [
- '.DS_Store',
- 'Thumbs.db',
- '.git',
- '.gitignore',
- '.idea',
- 'README.md',
- 'LICENSE',
- '.hg',
- '.hgignore',
- '.hgcheck',
- '.npmignore',
- 'mkdocs.yml',
- 'docs',
- '.travis.yml',
- '.gitlab-ci.yml',
- '.gitattributes',
- ];
- console.log();
- const conflicts = fs
- .readdirSync(root)
- .filter(file => !validFiles.includes(file))
- // IntelliJ IDEA creates module files before CRA is launched
- .filter(file => !/\.iml$/.test(file))
- // Don't treat log files from previous installation as conflicts
- .filter(file => !errorLogFilePatterns.some(pattern => file.indexOf(pattern) === 0));
- if (conflicts.length > 0) {
- console.log(`The directory ${chalk.green(name)} contains files that could conflict:`);
- console.log();
- for (const file of conflicts) {
- console.log(` ${file}`);
- }
- console.log();
- console.log('Either try using a new directory name, or remove the files listed above.');
- return false;
- }
- // Remove any remnant files from a previous installation
- const currentFiles = fs.readdirSync(path.join(root));
- currentFiles.forEach(file => {
- errorLogFilePatterns.forEach(errorLogFilePattern => {
- // This will catch `(npm-debug|yarn-error|yarn-debug).log*` files
- if (file.indexOf(errorLogFilePattern) === 0) {
- fs.removeSync(path.join(root, file));
- }
- });
- });
- return true;
- }
- export function checkNodeVersion(requiredVersion: string) {
- if (!semver.satisfies(process.version, requiredVersion)) {
- console.error(
- chalk.red(
- 'You are running Node %s.\n' +
- 'Vendure requires Node %s or higher. \n' +
- 'Please update your version of Node.',
- ),
- process.version,
- requiredVersion,
- );
- process.exit(1);
- }
- }
- export function shouldUseYarn() {
- try {
- execSync('yarnpkg --version', { stdio: 'ignore' });
- return true;
- } catch (e) {
- return false;
- }
- }
- export function checkThatNpmCanReadCwd() {
- const cwd = process.cwd();
- let childOutput = null;
- try {
- // Note: intentionally using spawn over exec since
- // the problem doesn't reproduce otherwise.
- // `npm config list` is the only reliable way I could find
- // to reproduce the wrong path. Just printing process.cwd()
- // in a Node process was not enough.
- childOutput = spawn.sync('npm', ['config', 'list']).output.join('');
- } catch (err) {
- // Something went wrong spawning node.
- // Not great, but it means we can't do this check.
- // We might fail later on, but let's continue.
- return true;
- }
- if (typeof childOutput !== 'string') {
- return true;
- }
- const lines = childOutput.split('\n');
- // `npm config list` output includes the following line:
- // "; cwd = C:\path\to\current\dir" (unquoted)
- // I couldn't find an easier way to get it.
- const prefix = '; cwd = ';
- const line = lines.find(l => l.indexOf(prefix) === 0);
- if (typeof line !== 'string') {
- // Fail gracefully. They could remove it.
- return true;
- }
- const npmCWD = line.substring(prefix.length);
- if (npmCWD === cwd) {
- return true;
- }
- console.error(
- chalk.red(
- `Could not start an npm process in the right directory.\n\n` +
- `The current directory is: ${chalk.bold(cwd)}\n` +
- `However, a newly started npm process runs in: ${chalk.bold(npmCWD)}\n\n` +
- `This is probably caused by a misconfigured system terminal shell.`,
- ),
- );
- if (process.platform === 'win32') {
- console.error(
- chalk.red(`On Windows, this can usually be fixed by running:\n\n`) +
- ` ${chalk.cyan(
- 'reg',
- )} delete "HKCU\\Software\\Microsoft\\Command Processor" /v AutoRun /f\n` +
- ` ${chalk.cyan(
- 'reg',
- )} delete "HKLM\\Software\\Microsoft\\Command Processor" /v AutoRun /f\n\n` +
- chalk.red(`Try to run the above two lines in the terminal.\n`) +
- chalk.red(
- `To learn more about this problem, read: https://blogs.msdn.microsoft.com/oldnewthing/20071121-00/?p=24433/`,
- ),
- );
- }
- return false;
- }
- /**
- * Install packages via npm or yarn.
- * Based on the install function from https://github.com/facebook/create-react-app
- */
- export function installPackages(
- root: string,
- useYarn: boolean,
- dependencies: string[],
- isDev: boolean,
- logLevel: LogLevel,
- ): Promise<void> {
- return new Promise((resolve, reject) => {
- let command: string;
- let args: string[];
- if (useYarn) {
- command = 'yarnpkg';
- args = ['add', '--exact', '--ignore-engines'];
- if (isDev) {
- args.push('--dev');
- }
- args = args.concat(dependencies);
- // Explicitly set cwd() to work around issues like
- // https://github.com/facebook/create-react-app/issues/3326.
- // Unfortunately we can only do this for Yarn because npm support for
- // equivalent --prefix flag doesn't help with this issue.
- // This is why for npm, we run checkThatNpmCanReadCwd() early instead.
- args.push('--cwd');
- args.push(root);
- } else {
- command = 'npm';
- args = ['install', '--save', '--save-exact', '--loglevel', 'error'].concat(dependencies);
- if (isDev) {
- args.push('--save-dev');
- }
- }
- if (logLevel === 'verbose') {
- args.push('--verbose');
- }
- const child = spawn(command, args, { stdio: logLevel === 'silent' ? 'ignore' : 'inherit' });
- child.on('close', code => {
- if (code !== 0) {
- reject({
- message:
- 'An error occurred when installing dependencies. Try running with `--log-level info` to diagnose.',
- command: `${command} ${args.join(' ')}`,
- });
- return;
- }
- resolve();
- });
- });
- }
- export function getDependencies(
- usingTs: boolean,
- dbType: DbType,
- ): { dependencies: string[]; devDependencies: string[] } {
- const dependencies = [
- '@vendure/core',
- '@vendure/email-plugin',
- '@vendure/asset-server-plugin',
- '@vendure/admin-ui-plugin',
- dbDriverPackage(dbType),
- ];
- const devDependencies = ['concurrently'];
- if (usingTs) {
- devDependencies.push('ts-node');
- }
- return { dependencies, devDependencies };
- }
- /**
- * Returns the name of the npm driver package for the
- * selected database.
- */
- function dbDriverPackage(dbType: DbType): string {
- switch (dbType) {
- case 'mysql':
- return 'mysql';
- case 'postgres':
- return 'pg';
- case 'sqlite':
- return 'sqlite3';
- case 'sqljs':
- return 'sql.js';
- case 'mssql':
- return 'mssql';
- case 'oracle':
- return 'oracledb';
- default:
- const n: never = dbType;
- console.error(chalk.red(`No driver package configured for type "${dbType}"`));
- return '';
- }
- }
- /**
- * Checks that the specified DB connection options are working (i.e. a connection can be
- * established) and that the named database exists.
- */
- export function checkDbConnection(options: any, root: string): Promise<true> {
- switch (options.type) {
- case 'mysql':
- return checkMysqlDbExists(options, root);
- case 'postgres':
- return checkPostgresDbExists(options, root);
- default:
- return Promise.resolve(true);
- }
- }
- async function checkMysqlDbExists(options: any, root: string): Promise<true> {
- const mysql = await import(path.join(root, 'node_modules/mysql'));
- const connectionOptions = {
- host: options.host,
- user: options.username,
- password: options.password,
- port: options.port,
- database: options.database,
- };
- const connection = mysql.createConnection(connectionOptions);
- return new Promise<boolean>((resolve, reject) => {
- connection.connect((err: any) => {
- if (err) {
- if (err.code === 'ER_BAD_DB_ERROR') {
- throwDatabaseDoesNotExist(options.database);
- }
- throwConnectionError(err);
- }
- resolve(true);
- });
- }).then(() => {
- return new Promise((resolve, reject) => {
- connection.end((err: any) => {
- resolve(true);
- });
- });
- });
- }
- async function checkPostgresDbExists(options: any, root: string): Promise<true> {
- const { Client } = await import(path.join(root, 'node_modules/pg'));
- const connectionOptions = {
- host: options.host,
- user: options.username,
- password: options.password,
- port: options.port,
- database: options.database,
- };
- const client = new Client(connectionOptions);
- try {
- await client.connect();
- } catch (e) {
- if (e.code === '3D000') {
- throwDatabaseDoesNotExist(options.database);
- }
- throwConnectionError(e);
- await client.end();
- throw e;
- }
- await client.end();
- return true;
- }
- function throwConnectionError(err: any) {
- throw new Error(
- `Could not connect to the database. ` +
- `Please check the connection settings in your Vendure config.\n[${err.message ||
- err.toString()}]`,
- );
- }
- function throwDatabaseDoesNotExist(name: string) {
- throw new Error(`Database "${name}" does not exist. Please create the database and then try again.`);
- }
|