Explorar o código

feat(create): Improved logging

Michael Bromley %!s(int64=6) %!d(string=hai) anos
pai
achega
85be3a3fa0

+ 3 - 0
packages/create/package.json

@@ -15,6 +15,7 @@
   },
   "devDependencies": {
     "@types/cross-spawn": "^6.0.0",
+    "@types/detect-port": "^1.1.0",
     "@types/fs-extra": "^5.0.4",
     "@types/handlebars": "^4.1.0",
     "@types/listr": "^0.13.0",
@@ -29,10 +30,12 @@
     "chalk": "^2.4.2",
     "commander": "^2.19.0",
     "cross-spawn": "^6.0.5",
+    "detect-port": "^1.3.0",
     "fs-extra": "^7.0.1",
     "handlebars": "^4.1.1",
     "listr": "^0.14.3",
     "prompts": "^2.0.1",
+    "rxjs": "^6.4.0",
     "semver": "^6.0.0"
   }
 }

+ 111 - 47
packages/create/src/create-vendure-app.ts

@@ -1,13 +1,23 @@
 /* tslint:disable:no-console */
 import chalk from 'chalk';
 import program from 'commander';
+import detectPort from 'detect-port';
 import fs from 'fs-extra';
 import Listr from 'listr';
 import os from 'os';
 import path from 'path';
+import { Observable } from 'rxjs';
 
 import { gatherUserResponses } from './gather-user-responses';
-import { checkNodeVersion, checkThatNpmCanReadCwd, getDependencies, installPackages, isSafeToCreateProjectIn, shouldUseYarn } from './helpers';
+import {
+    checkNodeVersion,
+    checkThatNpmCanReadCwd,
+    getDependencies,
+    installPackages,
+    isSafeToCreateProjectIn,
+    shouldUseYarn,
+} from './helpers';
+import { LogLevel } from './types';
 
 // tslint:disable-next-line:no-var-requires
 const packageJson = require('../package.json');
@@ -23,17 +33,25 @@ program
     .action(name => {
         projectName = name;
     })
-    .option('--verbose', 'print additional logs')
-    .option('--use-npm')
+    .option(
+        '-log-level <logLevel>',
+        'Log level, either \'silent\', \'info\', or \'verbose\'',
+        /^(silent|info|verbose)$/i,
+        'silent',
+    )
+    .option('--use-npm', 'Uses npm rather than Yarn as the default package manager')
     .parse(process.argv);
 
-createApp(projectName, program.useNpm, program.verbose);
+createApp(projectName, program.useNpm, program.logLevel || 'silent');
 
-async function createApp(name: string | undefined, useNpm: boolean, verbose: boolean) {
+async function createApp(name: string | undefined, useNpm: boolean, logLevel: LogLevel) {
     if (!runPreChecks(name, useNpm)) {
         return;
     }
 
+    console.log(`Let's configure a new Vendure project. First a few questions:`);
+    console.log();
+
     const root = path.resolve(name);
     const appName = path.basename(root);
     const { dbType, usingTs, configSource, indexSource, populateProducts } = await gatherUserResponses(root);
@@ -53,58 +71,101 @@ async function createApp(name: string | undefined, useNpm: boolean, verbose: boo
         process.exit(1);
     }
 
+    console.log();
+    console.log(`Setting up your new Vendure project in ${chalk.green(root)}`);
+    console.log('This may take a few minutes...');
+    console.log();
+
     const tasks = new Listr([
         {
             title: 'Installing dependencies',
-            task: async () => {
-                fs.writeFileSync(
-                    path.join(root, 'package.json'),
-                    JSON.stringify(packageJsonContents, null, 2) + os.EOL,
-                );
-                const { dependencies, devDependencies } = getDependencies(usingTs, dbType);
-                await installPackages(root, useYarn, dependencies, false, verbose);
-                await installPackages(root, useYarn, devDependencies, true, verbose);
-            },
+            task: (() => {
+                return new Observable(subscriber => {
+                    fs.writeFileSync(
+                        path.join(root, 'package.json'),
+                        JSON.stringify(packageJsonContents, null, 2) + os.EOL,
+                    );
+                    subscriber.next('Created package.json');
+                    const { dependencies, devDependencies } = getDependencies(usingTs, dbType);
+
+                    installPackages(root, useYarn, dependencies, false, logLevel)
+                        .then(() => subscriber.next(`Installed ${dependencies.join(', ')}`))
+                        .then(() => {
+                            if (devDependencies.length) {
+                                return installPackages(root, useYarn, devDependencies, true, logLevel)
+                                    .then(() => subscriber.next(`Installed ${devDependencies.join(', ')}`));
+                            }
+                        })
+                        .then(() => subscriber.complete());
+                });
+            }) as any,
         },
         {
             title: 'Generating app scaffold',
-            task: async (ctx) => {
-                const assetPath = (fileName: string) => path.join(__dirname, '../assets', fileName);
-                const rootPathScript = (fileName: string): string => path.join(root, `${fileName}.${usingTs ? 'ts' : 'js'}`);
-                ctx.configFile = rootPathScript('vendure-config');
-                await fs.writeFile(ctx.configFile, configSource);
-                await fs.writeFile(rootPathScript('index'), indexSource);
-                if (usingTs) {
-                    await fs.copyFile(assetPath('tsconfig.template.json'), path.join(root, 'tsconfig.json'));
-                }
-                await createDirectoryStructure(root);
-                await copyEmailTemplates(root);
+            task: ctx => {
+                return new Observable(subscriber => {
+                    const assetPath = (fileName: string) => path.join(__dirname, '../assets', fileName);
+                    const rootPathScript = (fileName: string): string =>
+                        path.join(root, `${fileName}.${usingTs ? 'ts' : 'js'}`);
+                    ctx.configFile = rootPathScript('vendure-config');
+
+                    fs.writeFile(ctx.configFile, configSource)
+                        .then(() => {
+                            subscriber.next(`Created config`);
+                            return fs.writeFile(rootPathScript('index'), indexSource);
+                        })
+                        .then(() => {
+                            subscriber.next(`Created index`);
+                            if (usingTs) {
+                                return fs.copyFile(assetPath('tsconfig.template.json'), path.join(root, 'tsconfig.json'));
+                            }
+                        })
+                        .then(() => createDirectoryStructure(root))
+                        .then(() => {
+                            subscriber.next(`Created directory structure`);
+                            return copyEmailTemplates(root);
+                        })
+                        .then(() => {
+                            subscriber.next(`Copied email templates`);
+                            subscriber.complete();
+                        });
+                });
             },
         },
         {
             title: 'Initializing server',
-            task: async (ctx) => {
+            task: async ctx => {
                 try {
                     if (usingTs) {
                         // register ts-node so that the config file can be loaded
                         require(path.join(root, 'node_modules/ts-node')).register();
                     }
-                    const { populate } = await import(path.join(root, 'node_modules/@vendure/core/dist/cli/populate'));
-                    const { bootstrap } = await import(path.join(root, 'node_modules/@vendure/core/dist/bootstrap'));
+                    const { populate } = await import(path.join(
+                        root,
+                        'node_modules/@vendure/core/dist/cli/populate',
+                    ));
+                    const { bootstrap } = await import(path.join(
+                        root,
+                        'node_modules/@vendure/core/dist/bootstrap',
+                    ));
                     const { config } = await import(ctx.configFile);
                     const assetsDir = path.join(__dirname, '../assets');
 
                     const initialDataPath = path.join(assetsDir, 'initial-data.json');
-                    const bootstrapFn = () => bootstrap({
-                        ...config,
-                        dbConnectionOptions: {
-                            ...config.dbConnectionOptions,
-                            synchronize: true,
-                        },
-                        importExportOptions: {
-                            importAssetsDir: path.join(assetsDir, 'images'),
-                        },
-                    });
+                    const port = await detectPort(3000);
+                    const bootstrapFn = () =>
+                        bootstrap({
+                            ...config,
+                            port,
+                            silent: logLevel === 'silent',
+                            dbConnectionOptions: {
+                                ...config.dbConnectionOptions,
+                                synchronize: true,
+                            },
+                            importExportOptions: {
+                                importAssetsDir: path.join(assetsDir, 'images'),
+                            },
+                        });
                     let app: any;
                     if (populateProducts) {
                         app = await populate(
@@ -114,10 +175,7 @@ async function createApp(name: string | undefined, useNpm: boolean, verbose: boo
                             path.join(assetsDir, 'images'),
                         );
                     } else {
-                        app = await populate(
-                            bootstrapFn,
-                            initialDataPath,
-                        );
+                        app = await populate(bootstrapFn, initialDataPath);
                     }
                     await app.close();
                 } catch (e) {
@@ -133,6 +191,16 @@ async function createApp(name: string | undefined, useNpm: boolean, verbose: boo
     } catch (e) {
         console.error(chalk.red(e));
     }
+    const startCommand = useYarn ? 'yarn start' : 'npm run start';
+    console.log();
+    console.log(chalk.green(`Success! Created a new Vendure server at ${root}`));
+    console.log();
+    console.log(`We suggest that you start by typing:`);
+    console.log();
+    console.log(chalk.green(`    cd ${name}`));
+    console.log(chalk.green(`    ${startCommand}`));
+    console.log();
+    console.log('Happy hacking!');
     process.exit(0);
 }
 
@@ -143,9 +211,7 @@ async function createApp(name: string | undefined, useNpm: boolean, verbose: boo
 function runPreChecks(name: string | undefined, useNpm: boolean): name is string {
     if (typeof name === 'undefined') {
         console.error('Please specify the project directory:');
-        console.log(
-            `  ${chalk.cyan(program.name())} ${chalk.green('<project-directory>')}`,
-        );
+        console.log(`  ${chalk.cyan(program.name())} ${chalk.green('<project-directory>')}`);
         console.log();
         console.log('For example:');
         console.log(`  ${chalk.cyan(program.name())} ${chalk.green('my-vendure-app')}`);
@@ -158,8 +224,6 @@ function runPreChecks(name: string | undefined, useNpm: boolean): name is string
     if (!isSafeToCreateProjectIn(root, name)) {
         process.exit(1);
     }
-    console.log(`Creating a new Vendure app in ${chalk.green(root)}.`);
-    console.log();
     return true;
 }
 

+ 3 - 14
packages/create/src/gather-user-responses.ts

@@ -1,20 +1,11 @@
-import chalk from 'chalk';
 import fs from 'fs-extra';
 import Handlebars from 'handlebars';
 import path from 'path';
-import { PromptObject } from 'prompts';
-import prompts from 'prompts';
+import prompts, { PromptObject } from 'prompts';
 
-// tslint:disable:no-console
+import { DbType, UserResponses } from './types';
 
-export type DbType = 'mysql' | 'postgres' | 'sqlite' | 'sqljs' | 'mssql' | 'oracle';
-export interface UserResponses {
-    usingTs: boolean;
-    dbType: DbType;
-    populateProducts: boolean;
-    indexSource: string;
-    configSource: string;
-}
+// tslint:disable:no-console
 
 /**
  * Prompts the user to determine how the new Vendure app should be configured.
@@ -28,8 +19,6 @@ export async function gatherUserResponses(root: string): Promise<UserResponses>
 
     let dbType: DbType;
 
-    console.log(`Let's get started with a new Vendure server!\n`);
-
     const answers = await prompts(
         [
             {

+ 4 - 7
packages/create/src/helpers.ts

@@ -6,7 +6,7 @@ import fs from 'fs-extra';
 import path from 'path';
 import semver from 'semver';
 
-import { DbType } from './gather-user-responses';
+import { DbType, LogLevel } from './types';
 
 /**
  * If project only contains files generated by GH, it’s safe.
@@ -171,7 +171,7 @@ export function checkThatNpmCanReadCwd() {
  * 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, verbose: boolean): Promise<void> {
+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[];
@@ -204,13 +204,11 @@ export function installPackages(root: string, useYarn: boolean, dependencies: st
             }
         }
 
-        if (verbose) {
+        if (logLevel === 'verbose') {
             args.push('--verbose');
-        } else {
-            args.push('--loglevel info')
         }
 
-        const child = spawn(command, args, {stdio: 'inherit'});
+        const child = spawn(command, args, { stdio: logLevel === 'silent' ? 'ignore' : 'inherit' });
         child.on('close', code => {
             if (code !== 0) {
                 reject({
@@ -218,7 +216,6 @@ export function installPackages(root: string, useYarn: boolean, dependencies: st
                 });
                 return;
             }
-            console.log(chalk.green(`Successfully installed ${dependencies.join(', ')}`));
             resolve();
         });
     });

+ 11 - 0
packages/create/src/types.ts

@@ -0,0 +1,11 @@
+export type DbType = 'mysql' | 'postgres' | 'sqlite' | 'sqljs' | 'mssql' | 'oracle';
+
+export interface UserResponses {
+    usingTs: boolean;
+    dbType: DbType;
+    populateProducts: boolean;
+    indexSource: string;
+    configSource: string;
+}
+
+export type LogLevel = 'silent' | 'info' | 'verbose';

+ 3 - 3
packages/create/templates/vendure-config.hbs

@@ -1,7 +1,7 @@
 {{#if isTs }}import{{ else }}const{{/if}} {
     examplePaymentHandler,
-    DefaultSearchPlugin,
-    {{#if isTs}}VendureConfig,{{/if}}
+    DefaultSearchPlugin,{{#if isTs}}
+    VendureConfig,{{/if}}
 } {{#if isTs}}from '@vendure/core'; {{ else }}= require('@vendure/core');{{/if}}
 {{#if isTs }}
 import { EmailPlugin } from '@vendure/email-plugin';
@@ -39,7 +39,7 @@ const path = require('path');
         synchronize: false, // not working with SQLite/SQL.js, see https://github.com/typeorm/typeorm/issues/2576
         {{/if}}
         logging: false,
-        database: {{#if isSQLjs}}new Uint8Array([]){{else}}'{{ dbName }}'{{/if}},
+        database: {{#if isSQLjs}}new Uint8Array([]){{else if isSQLite}}path.join(__dirname, 'vendure.sqlite'){{else}}'{{ dbName }}'{{/if}},
         {{#if isSQLjs}}
         location: path.join(__dirname, 'vendure.sqlite'),
         autoSave: true,