UNPKG

esa-cli

Version:

A CLI for operating Alibaba Cloud ESA Functions and Pages.

313 lines (312 loc) 10.3 kB
import os from 'os'; import path from 'path'; import chalk from 'chalk'; import Table from 'cli-table3'; import ora from 'ora'; import { format, createLogger } from 'winston'; import DailyRotateFile from 'winston-daily-rotate-file'; import t from '../i18n/index.js'; import { getProjectConfig } from '../utils/fileUtils/index.js'; const transport = new DailyRotateFile({ filename: path.join(os.homedir(), '.esa-logs/esa-debug-%DATE%.log'), level: 'info', datePattern: 'YYYY-MM-DD-HH', zippedArchive: true, maxSize: '10m', maxFiles: '7d' }); class Logger { constructor() { this.spinnerText = ''; const { combine, timestamp, label, printf } = format; const customFormat = printf(({ level, message, label: printLabel, timestamp: printTimestamp }) => { var _a; let colorizedLevel; const projName = ((_a = getProjectConfig()) === null || _a === void 0 ? void 0 : _a.name) || 'Outside'; switch (level) { case 'warn': colorizedLevel = chalk.yellow(level); break; case 'info': colorizedLevel = chalk.green(level); break; case 'error': colorizedLevel = chalk.red(level); case 'verbose': colorizedLevel = chalk.magenta(level); break; case 'debug': colorizedLevel = chalk.grey(level); break; case 'silly': colorizedLevel = chalk.white(level); break; default: colorizedLevel = level; } return `${printTimestamp} [${chalk.green(printLabel)}] ${colorizedLevel} in ${chalk.italic(projName)}: ${message}`; }); this.logger = createLogger({ level: 'info', format: combine(label({ label: 'ESA' }), timestamp(), customFormat), transports: [transport] }); this.spinner = ora('Loading...'); } static getInstance() { if (!Logger.instance) { Logger.instance = new Logger(); // Object.freeze(Logger.instance); } return Logger.instance; } get ora() { return this.spinner; } setLogLevel(level) { this.logger.level = level; } /** * Start a sub-step: show a spinner with the provided message. * If a spinner is already running, just update its text. */ startSubStep(message) { this.spinnerText = message; this.spinner.text = message; if (!this.spinner.isSpinning) { this.spinner.start(); } } /** * End a sub-step: stop loading and replace spinner with `├` and final message. * This overwrites the previous spinner line with the provided message. */ endSubStep(message) { // console.log(chalk.gray('├') + ' ' + this.spinnerText); try { if (this.spinner && this.spinner.isSpinning) { this.spinner.stop(); } } catch (_a) { } console.log(chalk.gray(`│ `)); console.log(chalk.gray('├ ') + this.spinnerText); console.log(chalk.gray(`│ ${message}`)); } stopSpinner() { try { if (this.spinner && this.spinner.isSpinning) { this.spinner.stop(); } } catch (_a) { } } /** * Prepare terminal output just before showing an interactive prompt. * - Stops any active spinner * - Replaces the previous line with a clean `╰ <text>` indicator */ prepareForPrompt(text) { this.stopSpinner(); const content = `╰ ${text || ''}`; this.replacePrevLine(content); } /** * Consolidate interactive prompt output after completion by replacing * the previous N lines with a concise summary line. * Defaults to 2 lines (prompt + answer line in most cases). */ consolidateAfterPrompt(summary, linesToReplace = 2) { const content = `├ ${summary}`; this.replacePrevLines(linesToReplace, content); } log(message) { console.log(message); } subLog(message) { console.log(`\t${message}`); } success(message) { console.log(`🎉 ${chalk.bgGreen(' SUCCESS ')} ${chalk.green(message)}`); } debug(message) { this.logger.debug(message); if (this.logger.level === 'debug') { console.log(`${chalk.grey('[DEBUG]')} ${message}`); } } info(message) { this.logger.info(message); } ask(message) { console.log(`❓ ${message}`); } point(message) { console.log(`👉🏻 ${chalk.green(message)}`); } block() { console.log('\n'); } warn(message) { this.logger.warn(message); console.log(`\n${chalk.bgYellow(' WARNING ')} ${chalk.yellow(message)}`); } error(message) { this.logger.error(message); console.log(`\n❌ ${chalk.bgRed(' ERROR ')} ${chalk.red(message)}`); } subError(message) { console.log(`\n${chalk.red(message)}`); } http(message) { this.logger.http(message); } url(message) { console.log(`🔗 ${chalk.blue(message)}`); } verbose(message) { this.logger.verbose(message); } silly(message) { this.logger.silly(message); } announcement(message) { // todo console.log(message); } notInProject() { this.block(); this.error('Missing ESA project configuration (esa.jsonc or esa.toml)'); this.block(); this.log('If there is code to deploy, you can either:'); this.subLog(`- Specify an entry-point to your Routine via the command line (ex: ${chalk.green('esa-cli deploy src/index.ts')})`); this.subLog('- Or create an "esa.jsonc" file (recommended):'); console.log('```jsonc\n' + '{\n' + ' "name": "my-routine",\n' + ' "entry": "src/index.ts",\n' + ' "dev": { "port": 18080 }\n' + '}\n' + '```'); this.subLog('- Or, if you prefer TOML, create an "esa.toml" file:'); console.log('```toml\n' + 'name = "my-routine"\n' + 'entry = "src/index.ts"\n' + '\n' + '[dev]\n' + 'port = 18080\n' + '```\n'); this.log('If you are deploying a directory of static assets, you can either:'); this.subLog(`- Create an "esa.jsonc" file (recommended) and run ${chalk.green('esa-cli deploy -a ./dist')}`); console.log('```jsonc\n' + '{\n' + ' "name": "my-routine",\n' + ' "assets": {\n' + ' "directory": "./dist"\n' + ' }\n' + '}\n' + '```'); this.subLog(`- Or create an "esa.toml" file and run ${chalk.green('esa-cli deploy -a ./dist')}`); console.log('```toml\n' + 'name = "my-routine"\n' + '\n' + '[assets]\n' + 'directory = "./dist"\n' + '```\n'); this.log('Alternatively, initialize a new ESA project:'); this.log(chalk.green('$ esa-cli init my-project')); this.block(); } pathEacces(localPath) { this.block(); this.log(chalk.yellow(t('common_eacces_intro', { localPath }).d(`You do not have permission to ${localPath}, please use`))); this.block(); this.log(chalk.green(`$ ${chalk.red('sudo')} esa-cli <Command>`)); this.block(); this.subLog(chalk.yellow('OR')); this.block(); this.log(chalk.green(`$ sudo chmod -R 777 ${localPath}`)); } table(head, data, width = []) { const table = new Table({ head, colWidths: width }); table.push(...data); this.log(table.toString()); this.block(); } tree(messages) { if (messages.length === 0) return; const lines = []; lines.push(`╭ ${messages[0]}`); for (let i = 1; i < messages.length - 1; i++) { lines.push(`│ ${messages[i]}`); } if (messages.length > 1) { lines.push(`╰ ${messages[messages.length - 1]}`); } console.log(lines.join('\n')); } StepHeader(title, step, total) { console.log(`\n╭ ${title} ${chalk.green(`Step ${step} of ${total}`)}`); console.log('│'); } StepItem(prompt) { console.log(`├ ${prompt}`); } StepStart(prompt) { console.log(`╭ ${prompt}`); } StepKV(key, value) { const orange = chalk.hex('#FFA500'); console.log(`│ ${orange(key)} ${value}`); } StepSpacer() { console.log('│'); } StepEnd(str) { console.log(`╰ ${str || ''}`); } StepEndInline() { try { process.stdout.write('╰ '); } catch (_a) { console.log('╰'); } } divider() { console.log(chalk.yellow('--------------------------------------------------------')); } // Replace the previous single terminal line with new content replacePrevLine(content) { try { // Move cursor up 1 line, clear it, carriage return, print new content process.stdout.write('\x1b[1A'); process.stdout.write('\x1b[2K'); process.stdout.write('\r'); console.log(content); } catch (_a) { console.log(content); } } // Replace multiple previous lines with one consolidated line replacePrevLines(linesToReplace, content) { try { for (let i = 0; i < linesToReplace; i++) { process.stdout.write('\x1b[1A'); // move up process.stdout.write('\x1b[2K'); // clear line } process.stdout.write('\r'); console.log(content); } catch (_a) { console.log(content); } } } const logger = Logger.getInstance(); export default logger;