chore: improve logs

This commit is contained in:
Alexandre Bodin 2024-08-06 12:41:33 +02:00
parent c641525963
commit b686fa9e04
7 changed files with 177 additions and 129 deletions

View File

@ -4,7 +4,6 @@ import chalk from 'chalk';
import execa from 'execa';
import fse from 'fs-extra';
import { stopProcess } from './utils/stop-process';
import { copyTemplate } from './utils/template';
import { tryGitInit } from './utils/git';
import { trackUsage } from './utils/usage';
@ -13,6 +12,7 @@ import { generateDotEnv } from './utils/dot-env';
import { isStderrError } from './types';
import type { Scope } from './types';
import { logger } from './utils/logger';
async function createStrapi(scope: Scope) {
const { rootPath } = scope;
@ -41,7 +41,7 @@ async function createApp(scope: Scope) {
await trackUsage({ event: 'willCreateProject', scope });
console.log(`Creating a new Strapi application at ${chalk.green(rootPath)}.`);
logger.title('Strapi', `Creating a new application at ${chalk.green(rootPath)}`);
if (!isQuickstart) {
await trackUsage({ event: 'didChooseCustomDatabase', scope });
@ -62,19 +62,21 @@ async function createApp(scope: Scope) {
}
} else {
try {
console.log(`Using template: ${chalk.green(template)}`);
logger.info(`${chalk.cyan('Installing template')} ${template}`);
await copyTemplate(scope, rootPath);
console.log('Template copied successfully.');
logger.success('Template copied successfully.');
} catch (error) {
if (error instanceof Error) {
throw new Error(`⛔️ Template installation failed: ${error.message}`);
logger.fatal(`Template installation failed: ${error.message}`);
}
throw error;
}
if (!fse.existsSync(join(rootPath, 'package.json'))) {
throw new Error('Missing package.json in template');
logger.fatal(`Missing ${chalk.bold('package.json')} in template`);
}
}
@ -99,13 +101,15 @@ async function createApp(scope: Scope) {
if (installDependencies) {
try {
logger.title('deps', `Installing dependencies with ${chalk.cyan(packageManager)}`);
await trackUsage({ event: 'willInstallProjectDependencies', scope });
console.log(`Installing dependencies with ${chalk.bold(packageManager)}\n`);
await runInstall(scope);
console.log(`Dependencies installed ${chalk.green('successfully')}.`);
await trackUsage({ event: 'didInstallProjectDependencies', scope });
logger.success(`Dependencies installed`);
} catch (error) {
const stderr = isStderrError(error) ? error.stderr : '';
@ -115,20 +119,17 @@ async function createApp(scope: Scope) {
error: stderr.slice(-1024),
});
console.log(
logger.fatal([
chalk.bold(
'Oh, it seems that you encountered errors while installing dependencies in your project.'
)
);
console.log(`Don't give up, your project was created correctly.`);
console.log(
`Fix the issues mentioned in the installation errors and try to run the following command`
);
console.log();
console.log(`cd ${chalk.green(rootPath)} && ${chalk.cyan(packageManager)} install`);
console.log();
stopProcess();
'Oh, it seems that you encountered an error while installing dependencies in your project'
),
'',
`Don't give up, your project was created correctly`,
'',
`Fix the issues mentioned in the installation errors and try to run the following command:`,
'',
`cd ${chalk.green(rootPath)} && ${chalk.cyan(packageManager)} install`,
]);
}
}
@ -136,54 +137,48 @@ async function createApp(scope: Scope) {
// Init git
if (gitInit) {
console.log('Initializing git repository.');
logger.title('git', 'Initializing git repository.');
await tryGitInit(rootPath);
console.log('Initialized a git repository.');
console.log();
logger.success('Initialized a git repository.');
}
console.log();
console.log(`Your application was created at ${chalk.green(rootPath)}.\n`);
logger.title('Strapi', `Your application was created!`);
const cmd = chalk.cyan(`${packageManager} run`);
console.log('Available commands in your project:');
console.log();
console.log(` ${cmd} develop`);
console.log(
' Start Strapi in watch mode. (Changes in Strapi project files will trigger a server restart)'
);
console.log();
console.log(` ${cmd} start`);
console.log(' Start Strapi without watch mode.');
console.log();
console.log(` ${cmd} build`);
console.log(' Build Strapi admin panel.');
console.log();
console.log(` ${cmd} deploy`);
console.log(' Deploy Strapi project.');
console.log();
console.log(` ${cmd} strapi`);
console.log(` Display all available commands.`);
console.log();
logger.log([
'Available commands in your project:',
'',
'Start Strapi in watch mode. (Changes in Strapi project files will trigger a server restart)',
`${cmd} develop`,
'',
'Start Strapi without watch mode.',
`${cmd} start`,
'',
'Build Strapi admin panel.',
`${cmd} build`,
'',
'Deploy Strapi project.',
`${cmd} deploy`,
'',
'Display all available commands.',
`${cmd} strapi\n`,
]);
if (installDependencies) {
console.log('You can start by doing:');
console.log();
console.log(` ${chalk.cyan('cd')} ${rootPath}`);
console.log(` ${cmd} develop`);
console.log();
logger.log(['To get start run', '', `${chalk.cyan('cd')} ${rootPath}`, `${cmd} develop`]);
} else {
console.log('You can start by doing:');
console.log();
console.log(` ${chalk.cyan('cd')} ${rootPath}`);
console.log(` ${chalk.cyan(packageManager)} install`);
console.log(` ${cmd} develop`);
console.log();
logger.log([
'To get start run',
'',
`${chalk.cyan('cd')} ${rootPath}`,
`${chalk.cyan(packageManager)} install`,
`${cmd} develop`,
]);
}
if (runApp && installDependencies) {
console.log(`Running your Strapi application.`);
logger.title('Starting', 'Running your Strapi application');
try {
await trackUsage({ event: 'willStartServer', scope });
@ -204,7 +199,7 @@ async function createApp(scope: Scope) {
});
}
stopProcess();
logger.fatal('Failed to start your Strapi application');
}
}
}
@ -231,7 +226,9 @@ function runInstall({ rootPath, packageManager }: Scope) {
installArguments.push(...(installArgumentsMap[packageManager] ?? []));
}
return execa(packageManager, installArguments, options);
const proc = execa(packageManager, installArguments, options);
return proc;
}
export { createStrapi };

View File

@ -15,7 +15,7 @@ import { trackError } from './utils/usage';
import { addDatabaseDependencies, getDatabaseInfos } from './utils/database';
import type { Options, Scope } from './types';
import { stopProcess } from './utils/stop-process';
import { logger } from './utils/logger';
const command = new commander.Command('create-strapi-app')
.version(version)
@ -69,18 +69,49 @@ async function run(args: string[]): Promise<void> {
const options = command.parse(args).opts<Options>();
const directory = command.args[0];
console.log(
`\n${chalk.bgBlueBright(` ${chalk.black('Strapi')} `)} ${chalk.green(
chalk.bold(`v${version}`)
)} ${chalk.bold("🚀 Let's create your new project")}`
logger.title(
'Strapi',
`${chalk.green(chalk.bold(`v${version}`))} ${chalk.bold("🚀 Let's create your new project")}\n`
);
checkNodeRequirements();
if (
(options.javascript !== undefined || options.typescript !== undefined) &&
options.template !== undefined
) {
logger.fatal(
`You cannot use ${chalk.bold('--javascript')} or ${chalk.bold('--typescript')} with ${chalk.bold('--template')}`
);
}
if (options.javascript === true && options.typescript === true) {
logger.fatal(
`You cannot use both ${chalk.bold('--typescript')} (--ts) and ${chalk.bold('--javascript')} (--js) flags together`
);
}
// Only prompt the example app option if there is no template option
if (options.example === true && options.template !== undefined) {
logger.fatal(`You cannot use ${chalk.bold('--example')} with ${chalk.bold('--template')}`);
}
if (options.template !== undefined && options.template.startsWith('-')) {
logger.fatal(`Template name ${chalk.bold(`"${options.template}"`)} is invalid`);
}
if ([options.useNpm, options.usePnpm, options.useYarn].filter(Boolean).length > 1) {
logger.fatal(
`You cannot specify multiple package managers at the same time ${chalk.bold('(--use-npm, --use-pnpm, --use-yarn)')}`
);
}
if (options.quickstart && !directory) {
stopProcess('Please specify the <directory> of your project when using --quickstart');
logger.fatal(
`Please specify the ${chalk.bold('<directory>')} of your project when using ${chalk.bold('--quickstart')}`
);
}
checkNodeRequirements();
const appDirectory = directory || (await prompts.directory());
const rootPath = await checkInstallPath(appDirectory);
@ -123,27 +154,6 @@ async function run(args: string[]): Promise<void> {
},
};
if ((options.javascript !== undefined || options.typescript) && options.template !== undefined) {
stopProcess('You cannot use --javascript or --typescript with --template');
}
if (options.javascript === true && options.typescript === true) {
stopProcess('You cannot use both --typescript (--ts) and --javascript (--js) flags together');
}
// Only prompt the example app option if there is no template option
if (options.example === true && options.template !== undefined) {
stopProcess('You cannot use --example with --template');
}
// Only prompt the example app option if there is no template option
if (
(options.javascript === true || options.typescript === true) &&
options.template !== undefined
) {
stopProcess('You cannot use --javascript or --typescript with --template');
}
if (options.template !== undefined) {
scope.useExampleApp = false;
} else if (options.example === true || options.quickstart) {
@ -199,17 +209,11 @@ async function run(args: string[]): Promise<void> {
await trackError({ scope, error });
stopProcess(`Error: ${error.message}`);
logger.fatal(error.message);
}
}
function getPkgManager(options: Options) {
if ([options.useNpm, options.usePnpm, options.useYarn].filter(Boolean).length > 1) {
stopProcess(
'You cannot specify multiple package managers at the same time (--use-npm, --use-pnpm, --use-yarn)'
);
}
if (options.useNpm === true) {
return 'npm';
}

View File

@ -1,23 +1,18 @@
import { resolve } from 'node:path';
import chalk from 'chalk';
import fse from 'fs-extra';
import { stopProcess } from './stop-process';
import { logger } from './logger';
// Checks if the an empty directory exists at rootPath
export async function checkInstallPath(directory: string): Promise<string> {
if (!directory) {
stopProcess(`⛔️ Please provide a project name.`);
}
const rootPath = resolve(directory);
if (await fse.pathExists(rootPath)) {
const stat = await fse.stat(rootPath);
if (!stat.isDirectory()) {
stopProcess(
`⛔️ ${chalk.green(
logger.fatal(
`${chalk.green(
rootPath
)} is not a directory. Make sure to create a Strapi application in an empty directory.`
);
@ -25,11 +20,10 @@ export async function checkInstallPath(directory: string): Promise<string> {
const files = await fse.readdir(rootPath);
if (files.length > 1) {
stopProcess(
`⛔️ You can only create a Strapi app in an empty directory.\nMake sure ${chalk.green(
rootPath
)} is empty.`
);
logger.fatal([
'You can only create a Strapi app in an empty directory',
`Make sure ${chalk.green(rootPath)} is empty.`,
]);
}
}

View File

@ -2,26 +2,25 @@ import chalk from 'chalk';
import semver from 'semver';
import { engines } from './engines';
import { stopProcess } from './stop-process';
import { logger } from './logger';
export function checkNodeRequirements() {
const currentNodeVersion = process.versions.node;
// error if the node version isn't supported
if (!semver.satisfies(currentNodeVersion, engines.node)) {
console.error(chalk.red(`You are running ${chalk.bold(`Node.js ${currentNodeVersion}`)}`));
console.error(`Strapi requires ${chalk.bold(chalk.green(`Node.js ${engines.node}`))}`);
console.error('Please make sure to use the right version of Node.');
stopProcess();
logger.fatal([
chalk.red(`You are running ${chalk.bold(`Node.js ${currentNodeVersion}`)}`),
`Strapi requires ${chalk.bold(chalk.green(`Node.js ${engines.node}`))}`,
'Please make sure to use the right version of Node.',
]);
}
// warn if not using a LTS version
else if (semver.major(currentNodeVersion) % 2 !== 0) {
console.warn(chalk.yellow(`You are running ${chalk.bold(`Node.js ${currentNodeVersion}`)}`));
console.warn(
`Strapi only supports ${chalk.bold(
chalk.green('LTS versions of Node.js')
)}, other versions may not be compatible.`
);
logger.warn([
chalk.yellow(`You are running ${chalk.bold(`Node.js ${currentNodeVersion}`)}`),
`Strapi only supports ${chalk.bold(chalk.green('LTS versions of Node.js'))}, other versions may not be compatible.`,
]);
}
}

View File

@ -2,7 +2,7 @@ import inquirer from 'inquirer';
import type { Question } from 'inquirer';
import type { Scope, Options, DBClient, DBConfig } from '../types';
import { stopProcess } from './stop-process';
import { logger } from './logger';
const DBOptions = ['dbclient', 'dbhost', 'dbport', 'dbname', 'dbusername', 'dbpassword'];
@ -55,7 +55,7 @@ export async function getDatabaseInfos(options: Options): Promise<DBConfig> {
}
if (options.dbclient && !VALID_CLIENTS.includes(options.dbclient)) {
stopProcess(
logger.fatal(
`Invalid --dbclient: ${options.dbclient}, expected one of ${VALID_CLIENTS.join(', ')}`
);
}
@ -68,7 +68,7 @@ export async function getDatabaseInfos(options: Options): Promise<DBConfig> {
matchingArgs.length !== DBOptions.length &&
options.dbclient !== 'sqlite'
) {
stopProcess(`Required database arguments are missing: ${missingArgs.join(', ')}.`);
logger.fatal(`Required database arguments are missing: ${missingArgs.join(', ')}.`);
}
const hasDBOptions = DBOptions.some((key) => key in options);
@ -82,7 +82,7 @@ export async function getDatabaseInfos(options: Options): Promise<DBConfig> {
}
if (!options.dbclient) {
stopProcess('Please specify the database client');
return logger.fatal('Please specify the database client');
}
const database: DBConfig = {

View File

@ -0,0 +1,61 @@
import chalk from 'chalk';
import type { ChalkFunction } from 'chalk';
const MAX_PREFIX_LENGTH = 8;
const badge = (text: string, bgColor: ChalkFunction, textColor: ChalkFunction = chalk.black) => {
const wrappedText = ` ${text} `;
return ' '.repeat(MAX_PREFIX_LENGTH - wrappedText.length) + bgColor(textColor(wrappedText));
};
const textIndent = (
text: string | string[],
indentFirst = true,
indent: number = MAX_PREFIX_LENGTH + 2
) => {
const parts = Array.isArray(text) ? text : [text];
return parts
.map((part, i) => {
if (i === 0 && !indentFirst) {
return part;
}
return ' '.repeat(indent) + part;
})
.join('\n');
};
export const logger = {
log(message: string | string[]): void {
console.log(textIndent(message));
},
title(title: string, message: string): void {
const prefix = badge(title, chalk.bgBlueBright);
console.log(`\n${prefix} ${message}`);
},
info(message: string): void {
console.log(`${' '.repeat(7)}${chalk.cyan('●')} ${message}`);
},
success(message: string): void {
console.log(`\n${' '.repeat(7)}${chalk.green('✓')} ${chalk.green(message)}`);
},
fatal(message?: string | string[]): never {
const prefix = badge('Error', chalk.bgRed);
if (message) {
console.error(`\n${prefix} ${textIndent(message, false)}\n`);
}
process.exit(1);
},
error(message: string | string[]): void {
const prefix = badge('Error', chalk.bgRed);
console.error(`\n${prefix} ${textIndent(message, false)}\n`);
},
warn(message: string | string[]): void {
const prefix = badge('Warn', chalk.bgYellow);
console.warn(`\n${prefix} ${textIndent(message, false)}\n`);
},
};

View File

@ -1,7 +0,0 @@
export function stopProcess(message?: string): never {
if (message) {
console.error(message);
}
process.exit(1);
}