From 6f8b13ab8688e44c5d5264935e6bdd8aad13ac00 Mon Sep 17 00:00:00 2001 From: Pelle Wessman Date: Tue, 1 Nov 2022 20:11:24 +0100 Subject: [PATCH 1/3] Add `chalk-markdown` from `compare-eslint-configs` See https://github.com/voxpelli/compare-eslint-configs/blob/196c468ade299000a7834a6c651de006631ad7b8/lib/chalk-markdown.js --- lib/utils/chalk-markdown.js | 87 +++++++++++++++++++++++++++++++++++++ package.json | 3 +- 2 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 lib/utils/chalk-markdown.js diff --git a/lib/utils/chalk-markdown.js b/lib/utils/chalk-markdown.js new file mode 100644 index 000000000..b600fd544 --- /dev/null +++ b/lib/utils/chalk-markdown.js @@ -0,0 +1,87 @@ +import chalk from 'chalk'; +import terminalLink from 'terminal-link'; + +export class ChalkOrMarkdown { + /** @type {boolean} */ + useMarkdown; + /** + * @param {boolean} useMarkdown + */ + constructor (useMarkdown) { + this.useMarkdown = !!useMarkdown; + } + + /** + * @param {string} text + * @param {number} [level] + * @returns {string} + */ + header (text, level = 1) { + return this.useMarkdown + ? `\n${''.padStart(level, '#')} ${text}\n` + : chalk.underline(`\n${level === 1 ? chalk.bold(text) : text}\n`); + } + + /** + * @param {string} text + * @returns {string} + */ + bold (text) { + return this.useMarkdown + ? `**${text}**` + : chalk.bold(`${text}`); + } + + /** + * @param {string} text + * @returns {string} + */ + italic (text) { + return this.useMarkdown + ? `_${text}_` + : chalk.italic(`${text}`); + } + + /** + * @param {string} text + * @param {string|undefined} url + * @returns {string} + */ + hyperlink (text, url) { + if (!url) return text; + return this.useMarkdown + ? `[${text}](${url})` + : terminalLink(text, url, { fallback: false }); + } + + /** + * @param {string[]} items + * @returns {string} + */ + list (items) { + const indentedContent = items.map(item => this.indent(item).trimStart()); + return this.useMarkdown + ? '* ' + indentedContent.join('\n* ') + '\n' + : indentedContent.join('\n') + '\n'; + } + + /** + * @param {string} text + * @param {number} [level] + * @returns {string} + */ + indent (text, level = 1) { + const indent = ''.padStart(level * 2, ' '); + return indent + text.split('\n').join('\n' + indent); + } + + /** + * @param {unknown} value + * @returns {string} + */ + json (value) { + return this.useMarkdown + ? '```json\n' + JSON.stringify(value) + '\n```' + : JSON.stringify(value); + } +} diff --git a/package.json b/package.json index 8b3e5a153..21089eb56 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "is-interactive": "^2.0.0", "meow": "^11.0.0", "pony-cause": "^2.1.4", - "prompts": "^2.4.2" + "prompts": "^2.4.2", + "terminal-link": "^3.0.0" } } From 2a58bcc001d15064a6850a5e8bc3d33a9352882a Mon Sep 17 00:00:00 2001 From: Pelle Wessman Date: Tue, 1 Nov 2022 21:16:16 +0100 Subject: [PATCH 2/3] Actual formatting on the output of `create` --- cli.js | 6 ++-- lib/commands/report/create.js | 64 ++++++++++++++++++++++++++--------- lib/utils/chalk-markdown.js | 43 +++++++++++++++-------- package.json | 2 ++ 4 files changed, 83 insertions(+), 32 deletions(-) diff --git a/cli.js b/cli.js index 938123632..fc5de452b 100644 --- a/cli.js +++ b/cli.js @@ -2,6 +2,7 @@ /* eslint-disable no-console */ import chalk from 'chalk' +import logSymbols from 'log-symbols' import { messageWithCauses, stackWithCauses } from 'pony-cause' import * as cliCommands from './lib/commands/index.js' @@ -20,8 +21,9 @@ try { ) } catch (err) { console.error( - chalk.bgRed('Unexpected error:') + - (err instanceof Error ? ' ' + messageWithCauses(err) + '\n\n' + stackWithCauses(err) : '') + + logSymbols.error + ' ' + + chalk.white.bgRed('Unexpected error:') + + (err instanceof Error ? ' ' + messageWithCauses(err) + '\n\n' + stackWithCauses(err) : ` ${logSymbols.warning} Unknown error`) + '\n' ) process.exit(1) diff --git a/lib/commands/report/create.js b/lib/commands/report/create.js index fcdd9fe7c..e0edfb49a 100644 --- a/lib/commands/report/create.js +++ b/lib/commands/report/create.js @@ -1,5 +1,10 @@ +/* eslint-disable no-console */ +import chalk from 'chalk' import meow from 'meow' +import ora from 'ora' +import { ErrorWithCause } from 'pony-cause' +import { ChalkOrMarkdown } from '../../utils/chalk-markdown.js' import { printFlagList } from '../../utils/formatting.js' import { setupSdk } from '../../utils/sdk.js' @@ -12,43 +17,70 @@ const run = async (argv, importMeta, { parentName }) => { // ...else provide basic instructions and help const cli = meow(` Usage - $ ${name} + $ ${name} Options ${printFlagList({ - // FIXME: Remove rainbow - '--rainbow': 'Foobar' + '--json': 'Output result as json', + '--markdown': 'Output result as markdown', }, 6)} Examples - $ ${name} --help + $ ${name} . `, { argv, description, importMeta, flags: { - // FIXME: Remove rainbow - rainbow: { + json: { type: 'boolean', - alias: 'r' - } + alias: 'j', + }, + markdown: { + type: 'boolean', + alias: 'm', + }, } }) - // TODO: Remove - console.log('LETS CREATE!', cli.flags, cli.input) + const { + json: outputJson, + markdown: outputMarkdown, + } = cli.flags const socketSdk = await setupSdk() + const spinner = ora('Creating report').start() + + /** @type {Awaited>} */ + let result + try { // FIXME: Take files from input? - const result = await socketSdk.createReportFromFilePaths(['package.json']) - // TODO: Remove - console.log('RESULT!', result) - } catch (err) { - // TODO: Remove - console.log('err!', err) + result = await socketSdk.createReportFromFilePaths(['package.json']) + } catch (cause) { + spinner.fail() + throw new ErrorWithCause('Failed creating report', { cause }) + } + + if (result.success === false) { + spinner.fail(chalk.white.bgRed('API returned an error:') + ' ' + result.error.message) + process.exit(1) } + + spinner.stop() + + if (outputJson) { + console.log(JSON.stringify(result.data, undefined, 2)) + return + } + + const format = new ChalkOrMarkdown(!!outputMarkdown) + + console.log(format.header(format.logSymbols.success + ' Report created')) + console.log(format.list([ + format.bold('Report URL:') + ' ' + format.hyperlink('report on socket.dev', result.data.url), + ])) } /** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */ diff --git a/lib/utils/chalk-markdown.js b/lib/utils/chalk-markdown.js index b600fd544..a8fcfab85 100644 --- a/lib/utils/chalk-markdown.js +++ b/lib/utils/chalk-markdown.js @@ -1,14 +1,22 @@ -import chalk from 'chalk'; -import terminalLink from 'terminal-link'; +import chalk from 'chalk' +import logSymbols from 'log-symbols' +import terminalLink from 'terminal-link' + +const markdownLogSymbols = { + info: ':information_source:', + error: ':stop_sign:', + success: ':white_check_mark:', + warning: ':warning:', +} export class ChalkOrMarkdown { /** @type {boolean} */ - useMarkdown; + useMarkdown /** * @param {boolean} useMarkdown */ constructor (useMarkdown) { - this.useMarkdown = !!useMarkdown; + this.useMarkdown = !!useMarkdown } /** @@ -19,7 +27,7 @@ export class ChalkOrMarkdown { header (text, level = 1) { return this.useMarkdown ? `\n${''.padStart(level, '#')} ${text}\n` - : chalk.underline(`\n${level === 1 ? chalk.bold(text) : text}\n`); + : chalk.underline(`\n${level === 1 ? chalk.bold(text) : text}\n`) } /** @@ -29,7 +37,7 @@ export class ChalkOrMarkdown { bold (text) { return this.useMarkdown ? `**${text}**` - : chalk.bold(`${text}`); + : chalk.bold(`${text}`) } /** @@ -39,7 +47,7 @@ export class ChalkOrMarkdown { italic (text) { return this.useMarkdown ? `_${text}_` - : chalk.italic(`${text}`); + : chalk.italic(`${text}`) } /** @@ -48,10 +56,10 @@ export class ChalkOrMarkdown { * @returns {string} */ hyperlink (text, url) { - if (!url) return text; + if (!url) return text return this.useMarkdown ? `[${text}](${url})` - : terminalLink(text, url, { fallback: false }); + : terminalLink(text, url, { fallback: false }) } /** @@ -59,10 +67,17 @@ export class ChalkOrMarkdown { * @returns {string} */ list (items) { - const indentedContent = items.map(item => this.indent(item).trimStart()); + const indentedContent = items.map(item => this.indent(item).trimStart()) return this.useMarkdown ? '* ' + indentedContent.join('\n* ') + '\n' - : indentedContent.join('\n') + '\n'; + : indentedContent.join('\n') + '\n' + } + + /** + * @returns {typeof logSymbols} + */ + get logSymbols () { + return this.useMarkdown ? markdownLogSymbols : logSymbols } /** @@ -71,8 +86,8 @@ export class ChalkOrMarkdown { * @returns {string} */ indent (text, level = 1) { - const indent = ''.padStart(level * 2, ' '); - return indent + text.split('\n').join('\n' + indent); + const indent = ''.padStart(level * 2, ' ') + return indent + text.split('\n').join('\n' + indent) } /** @@ -82,6 +97,6 @@ export class ChalkOrMarkdown { json (value) { return this.useMarkdown ? '```json\n' + JSON.stringify(value) + '\n```' - : JSON.stringify(value); + : JSON.stringify(value) } } diff --git a/package.json b/package.json index 21089eb56..f9e131221 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,9 @@ "got": "^12.5.2", "hpagent": "^1.2.0", "is-interactive": "^2.0.0", + "log-symbols": "^5.1.0", "meow": "^11.0.0", + "ora": "^6.1.2", "pony-cause": "^2.1.4", "prompts": "^2.4.2", "terminal-link": "^3.0.0" From c516d31cbcae3e861bd3e1dab32bebd8a5c626d0 Mon Sep 17 00:00:00 2001 From: Pelle Wessman Date: Wed, 2 Nov 2022 00:19:54 +0100 Subject: [PATCH 3/3] Improved output --- cli.js | 35 ++++++++++++++++++++++++++++------- lib/commands/report/create.js | 11 +++++++---- lib/utils/chalk-markdown.js | 29 ++++++++++++++++++++++++++--- lib/utils/errors.js | 2 ++ lib/utils/sdk.js | 6 +++--- lib/utils/socket-sdk.js | 3 ++- package.json | 2 +- 7 files changed, 69 insertions(+), 19 deletions(-) create mode 100644 lib/utils/errors.js diff --git a/cli.js b/cli.js index fc5de452b..e9913d6f3 100644 --- a/cli.js +++ b/cli.js @@ -2,10 +2,11 @@ /* eslint-disable no-console */ import chalk from 'chalk' -import logSymbols from 'log-symbols' import { messageWithCauses, stackWithCauses } from 'pony-cause' import * as cliCommands from './lib/commands/index.js' +import { logSymbols } from './lib/utils/chalk-markdown.js' +import { AuthError, InputError } from './lib/utils/errors.js' import { meowWithSubcommands } from './lib/utils/meow-with-subcommands.js' // TODO: Add autocompletion using https://www.npmjs.com/package/omelette @@ -20,11 +21,31 @@ try { } ) } catch (err) { - console.error( - logSymbols.error + ' ' + - chalk.white.bgRed('Unexpected error:') + - (err instanceof Error ? ' ' + messageWithCauses(err) + '\n\n' + stackWithCauses(err) : ` ${logSymbols.warning} Unknown error`) + - '\n' - ) + /** @type {string} */ + let errorTitle + /** @type {string} */ + let errorMessage = '' + /** @type {string|undefined} */ + let errorBody + + if (err instanceof AuthError) { + errorTitle = 'Authentication error' + errorMessage = err.message + } else if (err instanceof InputError) { + errorTitle = 'Invalid input' + errorMessage = err.message + } else if (err instanceof Error) { + errorTitle = 'Unexpected error' + errorMessage = messageWithCauses(err) + errorBody = stackWithCauses(err) + } else { + errorTitle = 'Unexpected error with no details' + } + + console.error(`${logSymbols.error} ${chalk.white.bgRed(errorTitle + ':')} ${errorMessage}`) + if (errorBody) { + console.error('\n' + errorBody) + } + process.exit(1) } diff --git a/lib/commands/report/create.js b/lib/commands/report/create.js index e0edfb49a..07011e829 100644 --- a/lib/commands/report/create.js +++ b/lib/commands/report/create.js @@ -5,6 +5,7 @@ import ora from 'ora' import { ErrorWithCause } from 'pony-cause' import { ChalkOrMarkdown } from '../../utils/chalk-markdown.js' +import { AuthError } from '../../utils/errors.js' import { printFlagList } from '../../utils/formatting.js' import { setupSdk } from '../../utils/sdk.js' @@ -14,7 +15,6 @@ const description = 'Create a project report' const run = async (argv, importMeta, { parentName }) => { const name = parentName + ' create' - // ...else provide basic instructions and help const cli = meow(` Usage $ ${name} @@ -31,6 +31,7 @@ const run = async (argv, importMeta, { parentName }) => { argv, description, importMeta, + // TODO: Add a --verbose? flags: { json: { type: 'boolean', @@ -64,6 +65,10 @@ const run = async (argv, importMeta, { parentName }) => { } if (result.success === false) { + if (result.status === 401 || result.status === 403) { + spinner.stop() + throw new AuthError(result.error.message) + } spinner.fail(chalk.white.bgRed('API returned an error:') + ' ' + result.error.message) process.exit(1) } @@ -78,9 +83,7 @@ const run = async (argv, importMeta, { parentName }) => { const format = new ChalkOrMarkdown(!!outputMarkdown) console.log(format.header(format.logSymbols.success + ' Report created')) - console.log(format.list([ - format.bold('Report URL:') + ' ' + format.hyperlink('report on socket.dev', result.data.url), - ])) + console.log('New report: ' + format.hyperlink(result.data.id, result.data.url, { fallbackToUrl: true })) } /** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */ diff --git a/lib/utils/chalk-markdown.js b/lib/utils/chalk-markdown.js index a8fcfab85..3dbd7faef 100644 --- a/lib/utils/chalk-markdown.js +++ b/lib/utils/chalk-markdown.js @@ -1,7 +1,26 @@ import chalk from 'chalk' -import logSymbols from 'log-symbols' +import isUnicodeSupported from 'is-unicode-supported' import terminalLink from 'terminal-link' +// From the 'log-symbols' module +const unicodeLogSymbols = { + info: chalk.blue('ℹ'), + success: chalk.green('✔'), + warning: chalk.yellow('⚠'), + error: chalk.red('✖'), +} + +// From the 'log-symbols' module +const fallbackLogSymbols = { + info: chalk.blue('i'), + success: chalk.green('√'), + warning: chalk.yellow('‼'), + error: chalk.red('×'), +} + +// From the 'log-symbols' module +export const logSymbols = isUnicodeSupported() ? unicodeLogSymbols : fallbackLogSymbols + const markdownLogSymbols = { info: ':information_source:', error: ':stop_sign:', @@ -12,6 +31,7 @@ const markdownLogSymbols = { export class ChalkOrMarkdown { /** @type {boolean} */ useMarkdown + /** * @param {boolean} useMarkdown */ @@ -53,13 +73,16 @@ export class ChalkOrMarkdown { /** * @param {string} text * @param {string|undefined} url + * @param {{ fallback?: boolean, fallbackToUrl?: boolean }} options * @returns {string} */ - hyperlink (text, url) { + hyperlink (text, url, { fallback = true, fallbackToUrl } = {}) { if (!url) return text return this.useMarkdown ? `[${text}](${url})` - : terminalLink(text, url, { fallback: false }) + : terminalLink(text, url, { + fallback: fallbackToUrl ? (_text, url) => url : fallback + }) } /** diff --git a/lib/utils/errors.js b/lib/utils/errors.js new file mode 100644 index 000000000..728be91f7 --- /dev/null +++ b/lib/utils/errors.js @@ -0,0 +1,2 @@ +export class AuthError extends Error {} +export class InputError extends Error {} diff --git a/lib/utils/sdk.js b/lib/utils/sdk.js index 14f689be0..d4fdb3545 100644 --- a/lib/utils/sdk.js +++ b/lib/utils/sdk.js @@ -1,6 +1,7 @@ import isInteractive from 'is-interactive' import prompts from 'prompts' +import { AuthError } from './errors.js' import { SocketSdk } from './socket-sdk.js' export async function setupSdk () { @@ -17,8 +18,7 @@ export async function setupSdk () { } if (!apiKey) { - // FIXME: Throw a detailed error that is properly shown - throw new Error('Missing API-key') + throw new AuthError('You need to provide an API key') } /** @type {import('./socket-sdk').SocketSdkOptions["agent"]} */ @@ -38,5 +38,5 @@ export async function setupSdk () { baseUrl: process.env['SOCKET_SECURITY_API_BASE_URL'], } - return new SocketSdk(apiKey, sdkOptions) + return new SocketSdk(apiKey || '', sdkOptions) } diff --git a/lib/utils/socket-sdk.js b/lib/utils/socket-sdk.js index d250fa444..bd8835f61 100644 --- a/lib/utils/socket-sdk.js +++ b/lib/utils/socket-sdk.js @@ -33,6 +33,7 @@ export class SocketSdk { /** * @param {string} apiKey * @param {SocketSdkOptions} options + * @throws {SocketSdkAuthError} */ constructor (apiKey, options = {}) { const { @@ -42,7 +43,7 @@ export class SocketSdk { // FIXME: Handle rate limit! Seems like got is handling that now? // TODO: Add timeout - // TODO: Add debug() + // TODO: Add debug() and/or take a logging function this.#client = got.extend({ prefixUrl: baseUrl, diff --git a/package.json b/package.json index f9e131221..caaff1a1f 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "got": "^12.5.2", "hpagent": "^1.2.0", "is-interactive": "^2.0.0", - "log-symbols": "^5.1.0", + "is-unicode-supported": "^1.3.0", "meow": "^11.0.0", "ora": "^6.1.2", "pony-cause": "^2.1.4",