diff --git a/bin/cli.js b/bin/cli.js index 6cc5b92..5e1b160 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -13,15 +13,19 @@ const devMode = require("../lib/cli/devMode"); const { firmCredentials } = require("../lib/api/firmCredentials"); const SF = require("../lib/api/sfApi"); const path = require("path"); +const { consola } = require("consola"); let firmIdDefault = cliUtils.loadDefaultFirmId(); cliUtils.handleUncaughtErrors(); // Name & Version program.name("silverfin"); -if (pkg.version) { - program.version(pkg.version); -} +pkg.version ? program.version(pkg.version) : undefined; +// Verbose Option +program.option("-v, --verbose", "Verbose output"); +program.on("option:verbose", () => { + consola.level = "debug"; // default: "info" +}); // READ reconciliations program @@ -459,7 +463,7 @@ program ); } else { if (options.previewOnly && !options.htmlInput && !options.htmlPreview) { - console.log( + consola.info( `When using "--preview-only" you need to specify at least one of the following options: "--html-input", "--html-preview"` ); process.exit(1); @@ -545,22 +549,22 @@ program if (options.setFirm) { firmCredentials.setDefaultFirmId(options.setFirm); const currentDirectory = path.basename(process.cwd()); - console.log(`${currentDirectory}: firm id set to ${options.setFirm}`); + consola.success(`${currentDirectory}: firm id set to ${options.setFirm}`); } if (options.getFirm) { const storedFirmId = firmCredentials.getDefaultFirmId(); if (storedFirmId) { - console.log(`Firm id previously stored: ${storedFirmId}`); + consola.info(`Firm id previously stored: ${storedFirmId}`); } else { - console.log("There is no firm id previously stored"); + consola.info("There is no firm id previously stored"); } } if (options.listAll) { const firms = firmCredentials.listAuthorizedFirms() || []; if (firms) { - console.log("List of authorized firms"); + consola.info("List of authorized firms"); firms.forEach((element) => - console.log(`- ${element[0]}${element[1] ? ` (${element[1]})` : ""}`) + consola.log(`- ${element[0]}${element[1] ? ` (${element[1]})` : ""}`) ); } } @@ -710,5 +714,5 @@ if (pkg.repository && pkg.repository.url) { (async function () { // Check if there is a new version available await cliUpdates.checkVersions(); - program.parse(); + await program.parseAsync(); })(); diff --git a/index.js b/index.js index b55cec2..61e66eb 100644 --- a/index.js +++ b/index.js @@ -1,12 +1,12 @@ const SF = require("./lib/api/sfApi"); const fsUtils = require("./lib/utils/fsUtils"); const fs = require("fs"); -const chalk = require("chalk"); const errorUtils = require("./lib/utils/errorUtils"); const { ReconciliationText } = require("./lib/templates/reconciliationText"); const { SharedPart } = require("./lib/templates/sharedPart"); const { firmCredentials } = require("./lib/api/firmCredentials"); const { ExportFile } = require("./lib/templates/exportFile"); +const { consola } = require("consola"); async function fetchReconciliation(firmId, handle) { const templateConfig = fsUtils.readConfig("reconciliationText", handle); @@ -20,17 +20,21 @@ async function fetchReconciliation(firmId, handle) { async function fetchReconciliationByHandle(firmId, handle) { const template = await SF.findReconciliationTextByHandle(firmId, handle); if (!template) { - throw `Reconciliation ${handle} wasn't found`; + consola.error(`Reconciliation "${handle}" wasn't found`); + process.exit(1); } ReconciliationText.save(firmId, template); + consola.success(`Reconciliation "${handle}" imported`); } async function fetchReconciliationById(firmId, id) { const template = await SF.readReconciliationTextById(firmId, id); if (!template || !template.data) { - throw `Reconciliation with id ${id} wasn't found`; + consola.error(`Reconciliation with id ${id} wasn't found`); + process.exit(1); } ReconciliationText.save(firmId, template.data); + consola.success(`Reconciliation "${template.data.handle}" imported`); } // Import all reconciliations @@ -38,7 +42,7 @@ async function fetchAllReconciliations(firmId, page = 1) { const templates = await SF.readReconciliationTexts(firmId, page); if (templates.length == 0) { if (page == 1) { - console.log("No reconciliations found"); + consola.error(`No reconciliations found in firm ${firmId}`); } return; } @@ -59,7 +63,7 @@ async function publishReconciliationByHandle( errorUtils.missingReconciliationId(handle); } let templateId = templateConfig.id[firmId]; - console.log(`Updating reconciliation ${handle}...`); + consola.debug(`Updating reconciliation ${handle}...`); const template = await ReconciliationText.read(handle); if (!template) return; template.version_comment = message; @@ -69,10 +73,10 @@ async function publishReconciliationByHandle( template ); if (response && response.data && response.data.handle) { - console.log(`Reconciliation updated: ${response.data.handle}`); + consola.success(`Reconciliation updated: ${response.data.handle}`); return true; } else { - console.log(`Reconciliation update failed: ${handle}`); + consola.error(`Reconciliation update failed: ${handle}`); return false; } } catch (error) { @@ -102,8 +106,8 @@ async function newReconciliation( handle ); if (existingTemplate) { - console.log( - `Reconciliation ${handle} already exists. Skipping its creation` + consola.info( + `Reconciliation "${handle}" already exists. Skipping its creation` ); return; } @@ -115,6 +119,7 @@ async function newReconciliation( // Store new id if (response && response.status == 201) { ReconciliationText.updateTemplateId(firmId, handle, response.data.id); + consola.success(`Reconciliation "${handle}" created`); } } catch (error) { errorUtils.errorHandler(error); @@ -143,24 +148,28 @@ async function fetchExportFile(firmId, name) { async function fetchExportFileByName(firmId, name) { const template = await SF.findExportFileByName(firmId, name); if (!template) { - throw `Export file ${name} wasn't found`; + consola.error(`Export file "${name}" wasn't found`); + process.exit(1); } ExportFile.save(firmId, template); + consola.success(`Export file "${name}" imported`); } async function fetchExportFileById(firmId, id) { const template = await SF.readExportFileById(firmId, id); if (!template) { - throw `Export file with id ${id} wasn't found`; + consola.error(`Export file with id ${id} wasn't found`); + process.exit(1); } ExportFile.save(firmId, template); + consola.success(`Export file "${template.name}" imported`); } async function fetchAllExportFiles(firmId, page = 1) { const templates = await SF.readExportFiles(firmId, page); if (templates.length == 0) { if (page == 1) { - console.log("No export files found"); + consola.error(`No export files found in firm ${firmId}`); } return; } @@ -181,16 +190,16 @@ async function publishExportFileByName( errorUtils.missingExportFileId(name); } let templateId = templateConfig.id[firmId]; - console.log(`Updating export file ${name}...`); + consola.debug(`Updating export file ${name}...`); const template = await ExportFile.read(name); if (!template) return; template.version_comment = message; const response = await SF.updateExportFile(firmId, templateId, template); if (response && response.data && response.data.name) { - console.log(`Export file updated: ${response.data.name}`); + consola.success(`Export file updated: ${response.data.name}`); return true; } else { - console.log(`Export file update failed: ${name}`); + consola.error(`Export file update failed: ${name}`); return false; } } catch (error) { @@ -217,8 +226,8 @@ async function newExportFile( try { const existingTemplate = await SF.findExportFileByName(firmId, name); if (existingTemplate) { - console.log( - `Reconciliation ${name} already exists. Skipping its creation` + consola.info( + `Export file "${name}" already exists. Skipping its creation` ); return; } @@ -230,6 +239,7 @@ async function newExportFile( // Store new id if (response && response.status == 201) { ExportFile.updateTemplateId(firmId, name, response.data.id); + consola.success(`Export file "${name}" created`); } } catch (error) { errorUtils.errorHandler(error); @@ -253,17 +263,19 @@ async function fetchSharedPart(firmId, name) { } async function fetchSharedPartById(firmId, id) { - const sharedPart = await SF.readSharedPartById(firmId, id); - - if (!sharedPart) { - throw `Shared part ${id} wasn't found.`; + const template = await SF.readSharedPartById(firmId, id); + if (!template || !template.data) { + consola.error(`Shared part ${id} wasn't found.`); + process.exit(1); } + await SharedPart.save(firmId, template.data); + consola.success(`Shared part "${template.data.name}" imported`); } async function fetchSharedPartByName(firmId, name) { const sharedPartByName = await SF.findSharedPartByName(firmId, name); if (!sharedPartByName) { - throw `Shared part with name ${name} wasn't found.`; + consola.error(`Shared part "${name}" wasn't found.`); } return fetchSharedPartById(firmId, sharedPartByName.id); } @@ -282,7 +294,7 @@ async function fetchAllSharedParts(firmId, page = 1) { const sharedParts = response.data; if (sharedParts.length == 0) { if (page == 1) { - console.log(`No shared parts found`); + consola.error(`No shared parts found in firm ${firmId}`); } return; } @@ -302,7 +314,7 @@ async function publishSharedPartByName( if (!templateConfig || !templateConfig.id[firmId]) { errorUtils.missingSharedPartId(name); } - console.log(`Updating shared part ${name}...`); + consola.debug(`Updating shared part ${name}...`); const template = await SharedPart.read(name); if (!template) return; template.version_comment = message; @@ -312,10 +324,10 @@ async function publishSharedPartByName( template ); if (response && response.data && response.data.name) { - console.log(`Shared part updated: ${response.data.name}`); + consola.success(`Shared part updated: ${response.data.name}`); return true; } else { - console.log(`Shared part update failed: ${name}`); + consola.error(`Shared part update failed: ${name}`); return false; } } catch (error) { @@ -342,7 +354,9 @@ async function newSharedPart( try { const existingSharedPart = await SF.findSharedPartByName(firmId, name); if (existingSharedPart) { - console.log(`Shared part ${name} already exists. Skipping its creation`); + consola.info( + `Shared part "${name}" already exists. Skipping its creation` + ); return; } const template = await SharedPart.read(name); @@ -353,6 +367,7 @@ async function newSharedPart( // Store new firm id if (response && response.status == 201) { SharedPart.updateTemplateId(firmId, name, response.data.id); + consola.success(`Shared part "${name}" created`); } } catch (error) { errorUtils.errorHandler(error); @@ -422,12 +437,12 @@ async function addSharedPart( // Success or failure if (!response || !response.status || !response.status === 201) { - console.log( + consola.warn( `Adding shared part "${sharedPartName}" to "${templateHandle}" failed (${templateType}).` ); return false; } - console.log( + consola.success( `Shared part "${sharedPartName}" added to "${templateHandle}" (${templateType}).` ); @@ -477,13 +492,15 @@ async function addAllSharedParts(firmId) { for await (let template of configSharedPart.used_in) { template = SharedPart.checkReconciliationType(template); if (!template.handle && !template.name) { - console.log(`Template has no handle or name. Skipping.`); + consola.warn(`Template has no handle or name. Skipping.`); continue; } const folder = fsUtils.FOLDERS[template.type]; const handle = template.handle || template.name; if (!fs.existsSync(`./${folder}/${handle}`)) { - console.log(`Template ${template.type} ${handle} not found. Skipping.`); + consola.warn( + `Template ${template.type} ${handle} not found. Skipping.` + ); continue; } addSharedPart(firmId, configSharedPart.name, handle, template.type); @@ -501,13 +518,13 @@ async function removeSharedPart( const configTemplate = fsUtils.readConfig(templateType, templateHandle); const configSharedPart = fsUtils.readConfig("sharedPart", sharedPartHandle); if (!configTemplate.id[firmId]) { - console.log( + consola.warn( `Template id not found for ${templateHandle} (${templateType}). Skipping.` ); return false; } if (!configSharedPart.id[firmId]) { - console.log(`Shared part id not found for ${templateHandle}. Skipping.`); + consola.warn(`Shared part id not found for ${templateHandle}. Skipping.`); return false; } // Remove shared part from template @@ -532,7 +549,7 @@ async function removeSharedPart( ); if (response.status === 200) { - console.log( + consola.success( `Shared part "${sharedPartHandle}" removed from template "${templateHandle}" (${templateType}).` ); } @@ -560,6 +577,7 @@ async function removeSharedPart( // Look for the template in Silverfin with the handle/name and get it's ID // Type has to be either "reconciliationText", "exportFile". "accountTemplate" or "sharedPart" async function getTemplateId(firmId, type, handle) { + consola.debug(`Getting ID for ${handle}...`); let templateText; switch (type) { case "reconciliationText": @@ -576,7 +594,7 @@ async function getTemplateId(firmId, type, handle) { break; } if (!templateText) { - console.log(`Template ${handle} wasn't found (${type})`); + consola.warn(`Template ${handle} wasn't found (${type})`); return false; } const config = fsUtils.readConfig(type, handle); @@ -585,7 +603,7 @@ async function getTemplateId(firmId, type, handle) { } config.id[firmId] = templateText.id; fsUtils.writeConfig(type, handle, config); - console.log(`Template ${handle}: ID updated (${type})`); + consola.success(`Template ${handle}: ID updated (${type})`); return true; } @@ -599,7 +617,6 @@ async function getAllTemplatesId(firmId, type) { if (!handle) { continue; } - console.log(`Getting ID for ${handle}...`); await getTemplateId(firmId, type, handle); } } catch (error) { @@ -611,11 +628,11 @@ async function updateFirmName(firmId) { try { const firmDetails = await SF.getFirmDetails(firmId); if (!firmDetails) { - console.log(`Firm ${firmId} not found.`); + consola.warn(`Firm ${firmId} not found.`); return false; } firmCredentials.storeFirmName(firmId, firmDetails.name); - console.log(`Firm ${firmId} name set to ${firmDetails.name}`); + consola.info(`Firm ${firmId} name set to ${firmDetails.name}`); return true; } catch (error) { errorUtils.errorHandler(error); diff --git a/lib/api/firmCredentials.js b/lib/api/firmCredentials.js index 8a13dcf..7644f10 100644 --- a/lib/api/firmCredentials.js +++ b/lib/api/firmCredentials.js @@ -1,6 +1,7 @@ const fs = require("fs"); const path = require("path"); const homedir = require("os").homedir(); +const { consola } = require("consola"); /** * Class to manage the credentials for the firms @@ -39,7 +40,9 @@ class FirmCredentials { "utf8", (err) => { if (err) { - console.log(`Error while writing credentials file: ${err}`); + consola.error( + new Error(`Error while writing credentials file: ${err}`) + ); } } ); diff --git a/lib/api/sfApi.js b/lib/api/sfApi.js index 1553c2a..7241903 100644 --- a/lib/api/sfApi.js +++ b/lib/api/sfApi.js @@ -2,13 +2,14 @@ const axios = require("axios"); const open = require("open"); const prompt = require("prompt-sync")({ sigint: true }); const apiUtils = require("../utils/apiUtils"); +const { consola } = require("consola"); apiUtils.checkRequiredEnvVariables(); async function authorizeApp(firmId = undefined) { try { - console.log( - `Note: if you need to exit this process you can press "Ctrl/Cmmd + C"` + consola.info( + `NOTE: if you need to exit this process you can press "Ctrl/Cmmd + C"` ); const redirectUri = "urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob"; const scope = @@ -26,20 +27,20 @@ async function authorizeApp(firmId = undefined) { const url = `${apiUtils.BASE_URL}/f/${firmIdPrompt}/oauth/authorize?client_id=${process.env.SF_API_CLIENT_ID}&redirect_uri=${redirectUri}&response_type=code&scope=${scope}`; await open(url); - console.log(`You need to authorize your APP in the browser`); - console.log("Insert your credentials..."); + consola.info(`You need to authorize your APP in the browser`); + consola.log("Insert your credentials..."); const authCodePrompt = prompt("Enter your API authorization code: ", { echo: "*", }); // Get tokens const tokens = await apiUtils.getAccessToken(firmIdPrompt, authCodePrompt); if (tokens) { - console.log("Authentication successful"); + consola.success("Authentication successful"); } // Get firm name await apiUtils.getFirmName(firmIdPrompt); } catch (error) { - console.log(error); + consola.error(error); process.exit(1); } } @@ -134,7 +135,7 @@ async function findReconciliationTextByHandle(firmId, handle, page = 1) { const reconciliations = await readReconciliationTexts(firmId, page); // No data. Not found if (reconciliations.length == 0) { - console.log(`Reconciliation ${handle} not found`); + consola.warn(`Reconciliation "${handle}" not found`); return null; } let reconciliationTexts = reconciliations.filter( @@ -345,7 +346,7 @@ async function findSharedPartByName(firmId, sharedPartName, page = 1) { const sharedParts = response.data; // No data if (sharedParts.length == 0) { - console.log(`Shared part ${sharedPartName} not found`); + consola.warn(`Shared part "${sharedPartName}" not found`); return; } const sharedPart = sharedParts.find( @@ -579,7 +580,7 @@ async function findExportFileByName(firmId, exportFileName, page = 1) { const exportFiles = await readExportFiles(firmId, page); // No data if (exportFiles.length == 0) { - console.log(`Export file "${exportFileName}" not found`); + consola.warn(`Export file "${exportFileName}" not found`); return; } const exportFile = exportFiles.find( @@ -876,7 +877,7 @@ async function findReconciliationInWorkflow( const workflowArray = response.data; // No data if (workflowArray.length == 0) { - console.log( + consola.log( `Reconciliation ${reconciliationHandle} not found in workflow id ${workflowId}` ); return; @@ -921,8 +922,8 @@ async function findReconciliationInWorkflows( } } // Not found - console.log( - `Reconciliation ${reconciliationHandle} not found in any workflow` + consola.warn( + `Reconciliation "${reconciliationHandle}" not found in any workflow` ); } diff --git a/lib/cli/cliUpdates.js b/lib/cli/cliUpdates.js index f8d095e..c25dadb 100644 --- a/lib/cli/cliUpdates.js +++ b/lib/cli/cliUpdates.js @@ -3,6 +3,7 @@ const pkg = require("../../package.json"); const chalk = require("chalk"); const { promisify } = require("util"); const exec = promisify(require("child_process").exec); +const { consola } = require("consola"); const PACKAGE_URL = "https://raw.githubusercontent.com/silverfin/silverfin-cli/main/package.json"; @@ -24,37 +25,37 @@ async function checkVersions() { return; } if (latestVersion > currentVersion) { - console.log(`--------------`); - console.log( + consola.log(`--------------`); + consola.log( "There is a new version available of this CLI (" + chalk.red(`${currentVersion}`) + " -> " + chalk.green(`${latestVersion}`) + ")" ); - console.log( + consola.log( "Run " + chalk.italic.bold(`silverfin update`) + " to get the latest version" ); - console.log(`--------------`); + consola.log(`--------------`); } } async function performUpdate() { - console.log(`Updating npm package from GitHub repository...`); + consola.info(`Updating npm package from GitHub repository...`); try { // Exec output contains both stderr and stdout outputs const updateCommand = `sudo npm install -g ${pkg.repository.url}`; - console.log(`Running command: ${chalk.italic(updateCommand)}`); + consola.log(`Running command: ${chalk.italic(updateCommand)}`); const updateOutput = await exec(updateCommand); const updatedPkgVersion = await exec("silverfin --version"); - console.log(`--------------`); - console.log(updateOutput.stdout); - console.log(`--------------`); - console.log( + consola.log(`--------------`); + consola.log(updateOutput.stdout); + consola.log(`--------------`); + consola.success( chalk.bold( `Silverfin CLI succesfully updated to version ${updatedPkgVersion.stdout}` ) @@ -62,14 +63,14 @@ async function performUpdate() { return updateOutput; } catch (error) { - console.log(`--------------`); - console.error(`${chalk.red("ERROR.")} Update of Silverfin CLI failed`); - console.log( + consola.log(`--------------`); + consola.error(`${chalk.red("ERROR.")} Update of Silverfin CLI failed`); + consola.log( `You can try running the following command: ${chalk.bold( `npm install -g ${pkg.repository.url}` )}` ); - console.log(`If that still fails, try updating NPM first.`); + consola.log(`If that still fails, try updating NPM first.`); } } diff --git a/lib/cli/devMode.js b/lib/cli/devMode.js index d65f401..630e6a7 100644 --- a/lib/cli/devMode.js +++ b/lib/cli/devMode.js @@ -3,6 +3,7 @@ const path = require("path"); const toolkit = require("../../index"); const liquidTestRunner = require("../liquidTestRunner"); const fsUtils = require("../utils/fsUtils"); +const { consola } = require("consola"); /** * Watch for changes in an specific YAML and their related liquid files. Run a new test when file is saved. @@ -12,10 +13,10 @@ const fsUtils = require("../utils/fsUtils"); * @param {boolean} renderInput - Open browser and show the HTML from input view */ async function watchLiquidTest(firmId, handle, testName, renderInput) { - console.log( + consola.info( `Watching for changes related to reconciliation "${handle}" to run a new test...` ); - console.log( + consola.warn( `Don't forget to terminate this process when you don't need it anymore! (Ctrl + C)` ); const filePath = path.resolve( @@ -26,7 +27,7 @@ async function watchLiquidTest(firmId, handle, testName, renderInput) { `${handle}_liquid_test.yml` ); if (!fs.existsSync(filePath)) { - console.log("YAML file not found: ", filePath); + consola.warn("YAML file not found: ", filePath); return; } const lastUpdates = {}; @@ -78,10 +79,10 @@ async function watchLiquidTest(firmId, handle, testName, renderInput) { * @param {Number} firmId */ function watchLiquidFiles(firmId) { - console.log( + consola.info( "Watching for changes in all liquid files to publish their updates..." ); - console.log( + consola.warn( `Don't forget to terminate this process when you don't need it anymore! (Ctrl + C)` ); const files = fsUtils.listExistingFiles("liquid"); diff --git a/lib/cli/stats.js b/lib/cli/stats.js index 180687a..e6ac350 100644 --- a/lib/cli/stats.js +++ b/lib/cli/stats.js @@ -2,6 +2,7 @@ const exec = require("child_process"); const fs = require("fs"); const fsUtils = require("../utils/fsUtils"); const yaml = require("yaml"); +const { consola } = require("consola"); const YAML_EXPRESSION = `.*reconciliation_texts\/.*\/tests\/.*_liquid_test.*\.y(a)?ml`; const MAIN_EXPRESSION = `.*reconciliation_texts\/.*\/main\.liquid`; @@ -11,7 +12,7 @@ async function yamlFilesActivity(sinceDate) { `git whatchanged --since="${sinceDate}" --name-status --pretty="format:"` ); if (!filesChanged) { - console.log("No files were changed since the date provided"); + consola.info("No files were changed since the date provided"); process.exit(0); } @@ -19,7 +20,7 @@ async function yamlFilesActivity(sinceDate) { const nonEmptyRows = rows.filter(Boolean); if (!nonEmptyRows || nonEmptyRows.length === 0) { - console.log("No files were changed since the date provided"); + consola.info("No files were changed since the date provided"); process.exit(0); } @@ -116,7 +117,7 @@ async function countYamlFiles() { countTests += unitTestsCount; } catch (e) { // Error while parsing the YAML file - // console.log(e); + // consola.log(e); } } } @@ -143,22 +144,23 @@ async function generateStatsOverview(sinceDate) { const today = new Date().toJSON().toString().slice(0, 10); // Overview - console.log(`Summary ( ${sinceDate} - ${today} ):`); - console.log(""); + consola.log(`Summary ( ${sinceDate} - ${today} ):`); + consola.log("------------------------------------"); + consola.log(""); // Added - Deleted - console.log( + consola.log( `New YAML files created: ${ (countByType["A"] || 0) - (countByType["D"] || 0) }` ); // Modified - console.log(`Updates to existing YAML files: ${countByType["M"] || 0}`); + consola.log(`Updates to existing YAML files: ${countByType["M"] || 0}`); // Not showing any information about moved/renamed - console.log(""); - console.log(`Total Reconciliations stored: ${fileCount.reconciliations}`); - console.log(`Total YAML files stored: ${fileCount.yaml}`); - console.log(`Total Unit Tests stored: ${fileCount.tests}`); + consola.log(""); + consola.log(`Total Reconciliations stored: ${fileCount.reconciliations}`); + consola.log(`Total YAML files stored: ${fileCount.yaml}`); + consola.log(`Total Unit Tests stored: ${fileCount.tests}`); // Row to append constRowContent = `\r\n${sinceDate};${today};${ @@ -176,7 +178,7 @@ async function generateStatsOverview(sinceDate) { } if (!fs.existsSync(csvPath)) { fs.writeFileSync(csvPath, constRowHeader, (err) => { - console.log(err); + consola.error(err); }); } // Append content diff --git a/lib/cli/utils.js b/lib/cli/utils.js index 123ffaf..e68d000 100644 --- a/lib/cli/utils.js +++ b/lib/cli/utils.js @@ -1,6 +1,7 @@ const errorUtils = require("../utils/errorUtils"); const prompt = require("prompt-sync")({ sigint: true }); const { firmCredentials } = require("../api/firmCredentials"); +const { consola } = require("consola"); // Load default firm id from Config Object or ENV function loadDefaultFirmId() { @@ -18,7 +19,7 @@ function loadDefaultFirmId() { function checkDefaultFirm(firmUsed, firmIdDefault) { if (firmUsed === firmIdDefault) { - console.log(`Firm ID to be used: ${firmIdDefault}`); + consola.info(`Firm ID to be used: ${firmIdDefault}`); } } @@ -39,7 +40,7 @@ function promptConfirmation() { "This will overwrite existing templates. Do you want to proceed? (y/n): " ); if (confirm.toLocaleLowerCase() !== "yes" && confirm.toLowerCase() !== "y") { - console.log("Operation cancelled"); + consola.warn("Operation cancelled"); process.exit(1); } return true; @@ -70,7 +71,7 @@ function checkUniqueOption(uniqueParameters = [], options) { let formattedParameters = uniqueParameters.map((parameter) => formatOption(parameter) ); - console.log( + consola.error( "Only one of the following options must be used: " + formattedParameters.join(", ") ); @@ -92,7 +93,7 @@ function checkUsedTogether(parameters = [], options) { let formattedParameters = parameters.map((parameter) => formatOption(parameter) ); - console.log( + consola.error( "The following options must be used together: " + formattedParameters.join(", ") ); diff --git a/lib/liquidTestGenerator.js b/lib/liquidTestGenerator.js index c770995..ba5762b 100644 --- a/lib/liquidTestGenerator.js +++ b/lib/liquidTestGenerator.js @@ -1,6 +1,7 @@ const SF = require("./api/sfApi"); const { firmCredentials } = require("../lib/api/firmCredentials"); const Utils = require("./utils/liquidTestUtils"); +const { consola } = require("consola"); // MainProcess async function testGenerator(url, testName) { @@ -12,9 +13,10 @@ async function testGenerator(url, testName) { // Check if firm is authhorized if (!firmCredentials.data.hasOwnProperty(parameters.firmId)) { - console.error( + consola.error( `You have no authorization to access firm id ${parameters.firmId}` ); + process.exit(1); } firmId = parameters.firmId; @@ -207,7 +209,7 @@ async function testGenerator(url, testName) { } } } catch (err) { - console.error(err); + consola.error(err); } } } @@ -262,7 +264,7 @@ async function testGenerator(url, testName) { ].reconciliations[handle].custom = filteredCustomDrops; } } catch (err) { - console.log(err); + consola.error(err); } } } @@ -349,7 +351,7 @@ async function testGenerator(url, testName) { }; } } catch (error) { - console.log(error); + consola.error(error); // Previous Period // Should we include this ? } diff --git a/lib/liquidTestRunner.js b/lib/liquidTestRunner.js index 39bb19b..ef374bf 100644 --- a/lib/liquidTestRunner.js +++ b/lib/liquidTestRunner.js @@ -13,6 +13,7 @@ const SF = require("./api/sfApi"); const fsUtils = require("./utils/fsUtils"); const { ReconciliationText } = require("./templates/reconciliationText"); const runTestUtils = require("./utils/runTestUtils"); +const { consola } = require("consola"); function findTestRows(testContent) { const options = { maxAliasCount: 10000 }; @@ -36,7 +37,7 @@ function buildTestParams(firmId, handle, testName = "", renderMode) { // Empty YAML check if (testContent.split("\n").length <= 1) { - console.log(`${handle}: there are no tests stored in the YAML file`); + consola.info(`${handle}: there are no tests stored in the YAML file`); return false; } @@ -72,7 +73,7 @@ function buildTestParams(firmId, handle, testName = "", renderMode) { if (testName) { const indexes = findTestRows(testContent); if (!Object.keys(indexes).includes(testName)) { - console.log(`Test ${testName} not found in YAML`); + consola.error(`Test ${testName} not found in YAML`); process.exit(1); } testParams.test_line = indexes[testName] + 1; @@ -97,7 +98,7 @@ async function fetchResult(firmId, testRunId) { pollingDelay *= 1.05; if (waitingTime >= waitingLimit) { spinner.stop(); - console.log("Timeout. Try to run your test again"); + consola.error("Timeout. Try to run your test again"); break; } } @@ -107,7 +108,7 @@ async function fetchResult(firmId, testRunId) { function listErrors(items, type) { const itemsKeys = Object.keys(items); - console.log( + consola.log( chalk.red( `${itemsKeys.length} ${type} expectation${ itemsKeys.length > 1 ? "s" : "" @@ -116,8 +117,8 @@ function listErrors(items, type) { ); itemsKeys.forEach((itemName) => { let itemDetails = items[itemName]; - console.log(`At line number ${itemDetails.line_number}`); - console.log( + consola.log(`At line number ${itemDetails.line_number}`); + consola.log( `For ${type} ${chalk.blue.bold(itemName)} got ${chalk.blue.bold( itemDetails.got )} (${chalk.italic( @@ -127,7 +128,7 @@ function listErrors(items, type) { )})` ); }); - console.log(""); + consola.log(""); } // Find at least one error in the all tests @@ -162,28 +163,27 @@ function processTestRunResponse(testRun, previewOnly) { // Possible status: started, completed, test_error, internal_error switch (testRun.status) { case "internal_error": - console.log( + consola.error( "Internal error. Try to run the test again or contact support if the issue persists." ); break; case "test_error": - console.log("Ran into an error an couldn't complete test run"); - console.log(chalk.red(testRun.error_message)); + consola.error("Ran into an error an couldn't complete test run"); + consola.log(chalk.red(testRun.error_message)); break; case "completed": const errorsPresent = checkAllTestsErrorsPresent(testRun.tests); if (errorsPresent === false) { if (previewOnly) { - console.log( + consola.success( chalk.green("SUCCESSFULLY RENDERED HTML (SKIPPED TESTS)") ); } else { - console.log(chalk.green("ALL TESTS HAVE PASSED")); + consola.success(chalk.green("ALL TESTS HAVE PASSED")); } } else { - console.log(""); - - console.log( + consola.log(""); + consola.log( chalk.red( `${Object.keys(testRun.tests).length} TEST${ Object.keys(testRun.tests).length > 1 ? "S" : "" @@ -201,42 +201,42 @@ function processTestRunResponse(testRun, previewOnly) { if (!testErrorsPresent) { return; } - console.log( + consola.log( "---------------------------------------------------------------" ); - console.log(chalk.bold(testName)); + consola.log(chalk.bold(testName)); let testElements = testRun.tests[testName]; // Display success messages of test if (testElements.reconciled === null) { - console.log(chalk.green("Reconciliation expectation passed")); + consola.success(chalk.green("Reconciliation expectation passed")); } if (Object.keys(testElements.results).length === 0) { - console.log(chalk.green("All result expectations passed")); + consola.success(chalk.green("All result expectations passed")); } if (Object.keys(testElements.rollforwards).length === 0) { - console.log(chalk.green("All rollforward expectations passed")); + consola.success(chalk.green("All rollforward expectations passed")); } // Display error messages of test // Reconciled if (testElements.reconciled !== null) { - console.log(chalk.red("Reconciliation expectation failed")); - console.log( + consola.log(chalk.red("Reconciliation expectation failed")); + consola.log( `At line number ${testElements.reconciled.line_number}` ); - console.log( + consola.log( `got ${chalk.blue.bold( testElements.reconciled.got )} but expected ${chalk.blue.bold( testElements.reconciled.expected )}` ); - console.log(""); + consola.log(""); } // Results @@ -274,14 +274,14 @@ async function getHTML(url, testName, openBrowser = false, htmlMode) { if (commandExistsSync("wsl-open")) { exec(`wsl-open ${filePath}`); } else { - console.log( + consola.info( "In order to automatically open HTML files on WSL, we need to install the wsl-open script." ); - console.log( + consola.log( "You might be prompted for your password in order for us to install 'sudo npm install -g wsl-open'" ); execSync("sudo npm install -g wsl-open"); - console.log("Installed wsl-open script"); + consola.log("Installed wsl-open script"); exec(`wsl-open ${filePath}`); } } else { @@ -414,7 +414,7 @@ async function runTestsStatusOnly(firmId, handle, testName = "") { if (!testResult) { status = "PASSED"; - console.log(status); + consola.success(status); return status; } @@ -424,11 +424,11 @@ async function runTestsStatusOnly(firmId, handle, testName = "") { const errorsPresent = checkAllTestsErrorsPresent(testRun.tests); if (errorsPresent === false) { status = "PASSED"; - console.log(status); + consola.success(status); return status; } } - console.log(status); + consola.error(status); return status; } diff --git a/lib/templates/reconciliationText.js b/lib/templates/reconciliationText.js index b09d1e5..f0b4a5c 100644 --- a/lib/templates/reconciliationText.js +++ b/lib/templates/reconciliationText.js @@ -1,6 +1,7 @@ const fs = require("fs"); const fsUtils = require("../utils/fsUtils"); const templateUtils = require("../utils/templateUtils"); +const { consola } = require("consola"); class ReconciliationText { // To be added: marketplace_template_id @@ -103,7 +104,7 @@ class ReconciliationText { static #missingHandle(template) { if (!template.handle) { - console.log( + consola.warn( `Template has no handle, add a handle before importing it from Silverfin. Skipped` ); return true; @@ -139,7 +140,7 @@ class ReconciliationText { if ( !this.RECONCILIATION_TYPE_OPTIONS.includes(attributes.reconciliation_type) ) { - console.log( + consola.warn( `Wrong reconciliation type. It must be one of the following: ${this.RECONCILIATION_TYPE_OPTIONS.join( ", " )}. Skipping it's definition.` diff --git a/lib/utils/apiUtils.js b/lib/utils/apiUtils.js index 9365633..5965be8 100644 --- a/lib/utils/apiUtils.js +++ b/lib/utils/apiUtils.js @@ -1,6 +1,7 @@ const { firmCredentials } = require("../api/firmCredentials"); const pkg = require("../../package.json"); const axios = require("axios"); +const { consola } = require("consola"); const BASE_URL = process.env.SF_HOST || "https://live.getsilverfin.com"; @@ -9,10 +10,10 @@ function checkRequiredEnvVariables() { (key) => !process.env[key] ); if (missingVariables.length) { - console.log(`Error: Missing API credentials: [${missingVariables}]`); - console.log(`Credentials should be defined as environmental variables.`); - console.log(`Call export ${missingVariables[0]}=...`); - console.log( + consola.error(`Error: Missing API credentials: [${missingVariables}]`); + consola.log(`Credentials should be defined as environmental variables.`); + consola.log(`Call export ${missingVariables[0]}=...`); + consola.log( `If you don't have credentials yet, you need to register your app with Silverfin to get them` ); process.exit(1); @@ -33,10 +34,10 @@ async function getAccessToken(firmId, authCode) { await getFirmName(firmId); return true; } catch (error) { - console.log( + consola.error( `Response Status: ${error.response.status} (${error.response.statusText})` ); - console.log( + consola.error( `Error description: ${JSON.stringify( error.response.data.error_description )}` @@ -49,7 +50,7 @@ async function getAccessToken(firmId, authCode) { async function refreshTokens(firmId) { try { const firmTokens = firmCredentials.getTokenPair(firmId); - console.log(`Requesting new pair of tokens`); + consola.debug(`Requesting new pair of tokens`); let data = { client_id: process.env.SF_API_CLIENT_ID, client_secret: process.env.SF_API_SECRET, @@ -69,15 +70,12 @@ async function refreshTokens(firmId) { await getFirmName(firmId); } } catch (error) { - console.log( - `Response Status: ${error.response.status} (${error.response.statusText})` - ); - console.log( - `Error description: ${JSON.stringify( - error.response.data.error_description - )}` - ); - console.log( + const description = JSON.stringify(error.response.data.error_description); + consola.error( + `Response Status: ${error.response.status} (${error.response.statusText})`, + "\n", + `Error description: ${description}`, + "\n", `Error refreshing the tokens. Try running the authentication process again` ); process.exit(1); @@ -87,7 +85,7 @@ async function refreshTokens(firmId) { function setAxiosDefaults(firmId) { const firmTokens = firmCredentials.getTokenPair(firmId); if (!firmTokens) { - console.log(`Missing authorization for firm id: ${firmId}`); + consola.error(`Missing authorization for firm id: ${firmId}`); process.exit(1); } axios.defaults.baseURL = `${BASE_URL}/api/v4/f/${firmId}`; @@ -98,7 +96,7 @@ function setAxiosDefaults(firmId) { } function responseSuccessHandler(response) { - console.log( + consola.debug( `Response Status: ${response.status} (${response.statusText}) - method: ${response.config.method} - url: ${response.config.url}` ); } @@ -111,27 +109,27 @@ async function responseErrorHandler( callbackParameters ) { if (error && error.response) { - console.log( + consola.error( `Response Status: ${error.response.status} (${error.response.statusText}) - method: ${error.response.config.method} - url: ${error.response.config.url}` ); } // Valid Request. Not Found if (error.response.status === 404) { - console.log( + consola.error( `Response Error (404): ${JSON.stringify(error.response.data.error)}` ); return; } // Bad Request if (error.response.status === 400) { - console.log( + consola.error( `Response Error (400): ${JSON.stringify(error.response.data.error)}` ); return; } // No access credentials if (error.response.status === 401) { - console.log( + consola.debug( `Response Error (401): ${JSON.stringify(error.response.data.error)}` ); if (refreshToken) { @@ -140,7 +138,7 @@ async function responseErrorHandler( // Call the original function again return callbackFunction(...Object.values(callbackParameters)); } else { - console.log( + consola.error( `Error 401: API calls failed, try to run the authorization process again` ); process.exit(1); @@ -148,13 +146,16 @@ async function responseErrorHandler( } // Unprocessable Entity if (error.response.status === 422) { - console.log(`Response Error (422): ${JSON.stringify(error.response.data)}`); - console.log(`You don't have the rights to update the previous parameters`); + consola.error( + `Response Error (422): ${JSON.stringify(error.response.data)}`, + "\n", + `You don't have the rights to update the previous parameters` + ); process.exit(1); } // Forbidden if (error.response.status === 403) { - console.log("Error (403): Forbidden access. Terminating process"); + consola.error("Error (403): Forbidden access. Terminating process"); process.exit(1); } // Not handled diff --git a/lib/utils/errorUtils.js b/lib/utils/errorUtils.js index a4f30f1..c16ba67 100644 --- a/lib/utils/errorUtils.js +++ b/lib/utils/errorUtils.js @@ -1,36 +1,38 @@ const pkg = require("../../package.json"); const chalk = require("chalk"); +const { consola } = require("consola"); // Uncaught Errors. Open Issue in GitHub function uncaughtErrors(error) { if (error.stack) { - console.error(""); + console.error("----------------------------"); console.error( `!!! Please open an issue including this log on ${pkg.bugs.url}` ); console.error(""); - console.error(error.message); + consola.error(error.message); console.error(`silverfin: v${pkg.version}, node: ${process.version}`); console.error(""); console.error(error.stack); + console.error("----------------------------"); } process.exit(1); } function errorHandler(error) { if (error.code == "ENOENT") { - console.log( + consola.log( `The path ${error.path} was not found, please ensure you've imported or created all required files` ); - process.exit(); + process.exit(1); } else { uncaughtErrors(error); } } function missingReconciliationId(handle) { - console.log(`Reconciliation ${handle}: ID is missing. Aborted`); - console.log( + consola.error(`Reconciliation ${handle}: ID is missing. Aborted`); + consola.info( `Try running: ${chalk.bold( `silverfin get-reconciliation-id --handle ${handle}` )} or ${chalk.bold(`silverfin get-reconciliation-id --all`)}` @@ -39,8 +41,8 @@ function missingReconciliationId(handle) { } function missingSharedPartId(name) { - console.log(`Shared part ${name}: ID is missing. Aborted`); - console.log( + consola.error(`Shared part ${name}: ID is missing. Aborted`); + consola.info( `Try running: ${chalk.bold( `silverfin get-shared-part-id --shared-part ${name}` )} or ${chalk.bold(`silverfin get-shared-part-id --all`)}` @@ -49,7 +51,7 @@ function missingSharedPartId(name) { } function missingExportFileId(name) { - console.log(`Export file ${name}: ID is missing. Aborted`); + consola.error(`Export file ${name}: ID is missing. Aborted`); process.exit(1); } diff --git a/lib/utils/fsUtils.js b/lib/utils/fsUtils.js index 854bb9b..913eb42 100644 --- a/lib/utils/fsUtils.js +++ b/lib/utils/fsUtils.js @@ -1,5 +1,6 @@ const fs = require("fs"); const path = require("path"); +const { consola } = require("consola"); const FOLDERS = { reconciliationText: "reconciliation_texts", @@ -42,8 +43,11 @@ function createSharedPartFolders(handle) { const errorCallbackLiquidTest = (error) => { if (error) { - console.log("An error occurred when creating the liquid testing file"); - console.log(error); + consola.error( + "An error occurred when creating the liquid testing file", + "\n", + error + ); } }; @@ -62,7 +66,7 @@ async function createLiquidTestYaml(templateType, handle, testContent) { ); const existingFile = fs.existsSync(liquidTestPath); if (existingFile) { - console.log( + consola.debug( `Liquid testing file ${handle}_liquid_test.yml already exists, so the file content was not overwritten` ); return; @@ -71,7 +75,7 @@ async function createLiquidTestYaml(templateType, handle, testContent) { if (error) { errorCallbackLiquidTest(error); } else { - console.log(`Liquid testing YAML file created for ${handle}`); + consola.debug(`Liquid testing YAML file created for ${handle}`); } }); } @@ -119,7 +123,7 @@ async function createTemplateFiles(templateType, handle, textMain, textParts) { ); fs.writeFile(partPath, textParts[textPartName], emptyCallback); }); - console.log(`Liquid template file(s) created for ${handle}`); + consola.debug(`Liquid template file(s) created for ${handle}`); } async function createLiquidFile(relativePath, fileName, textContent) { @@ -129,7 +133,7 @@ async function createLiquidFile(relativePath, fileName, textContent) { textContent, emptyCallback ); - console.log(`${fileName} file created`); + consola.debug(`${fileName} file created`); } function writeConfig(templateType, handle, config) { diff --git a/lib/utils/liquidTestUtils.js b/lib/utils/liquidTestUtils.js index ef6d836..60d5184 100644 --- a/lib/utils/liquidTestUtils.js +++ b/lib/utils/liquidTestUtils.js @@ -1,6 +1,7 @@ const YAML = require("yaml"); const fs = require("fs"); const fsUtils = require("./fsUtils"); +const { consola } = require("consola"); // Create base Liquid Test object function createBaseLiquidTest(testName) { @@ -35,7 +36,7 @@ function extractURL(url) { } else if (parts.indexOf("account_entry") !== -1) { type = "accountId"; } else { - console.log( + consola.error( "Not possible to identify if it's a reconciliation text or account entry." ); process.exit(1); @@ -48,7 +49,7 @@ function extractURL(url) { [type]: parts[7], }; } catch (err) { - console.log( + consola.error( "The URL provided is not correct. Double check it and run the command again." ); process.exit(1); @@ -83,11 +84,12 @@ function exportYAML(handle, liquidTestObject) { lineWidth: 0, }, }), - (err, data) => { + (err, _) => { if (err) { - console.error(err); + consola.error(err); + process.exit(1); } else { - console.log(`File saved: ${filePath}`); + consola.info(`File saved: ${filePath}`); } } ); @@ -114,7 +116,7 @@ function getCompanyDependencies(reconcilationObject, reconciliationHandle) { // No main part ? if (!reconcilationObject || !reconcilationObject.text) { - console.log( + consola.warn( `Reconciliation "${reconciliationHandle}": no liquid code found` ); return { standardDropElements: [], customDropElements: [] }; @@ -163,7 +165,7 @@ function searchForResultsFromDependenciesInLiquid( // No main part ? if (!reconcilationObject || !reconcilationObject.text) { - console.log( + consola.warn( `Reconciliation "${reconciliationHandle}": no liquid code found` ); return resultsCollection; @@ -260,7 +262,7 @@ function searchForCustomsFromDependenciesInLiquid( // No main part ? if (!reconcilationObject || !reconcilationObject.text) { - console.log( + consola.warn( `Reconciliation "${reconciliationHandle}": no liquid code found` ); return customCollection; @@ -311,7 +313,7 @@ function lookForSharedPartsInLiquid(reconcilationObject, reconciliationHandle) { // No main part ? if (!reconcilationObject || !reconcilationObject.text) { - console.log( + consola.warn( `Reconciliation "${reconciliationHandle}": no liquid code found` ); return; @@ -355,7 +357,7 @@ function lookForAccountsIDs(obj) { function lookForDefaultAccounts(reconcilationObject) { // No main part ? if (!reconcilationObject.text) { - console.log(`Reconciliation "${reconciliationHandle}": no liquid code found`); + consola.warn(`Reconciliation "${reconciliationHandle}": no liquid code found`); return; }; // Main diff --git a/lib/utils/liquidUtils.js b/lib/utils/liquidUtils.js index 9dcd5fa..f83b9ce 100644 --- a/lib/utils/liquidUtils.js +++ b/lib/utils/liquidUtils.js @@ -1,3 +1,5 @@ +const { consola } = require("consola"); + // Search for all "input" tags in Liquid function lookForInputTags(liquidCode, input_type = "") { const input_types = [ @@ -13,7 +15,7 @@ function lookForInputTags(liquidCode, input_type = "") { "", ]; if (!input_types.includes(input_type)) { - console.log("Input type defined not supported"); + consola.error("Input type defined not supported"); process.exit(1); } let reInput; diff --git a/lib/utils/templateUtils.js b/lib/utils/templateUtils.js index 877c8c9..7add5a4 100644 --- a/lib/utils/templateUtils.js +++ b/lib/utils/templateUtils.js @@ -1,8 +1,10 @@ +const { consola } = require("consola"); + /** Check if the name is valid. If not, log it and return false. Valid names are alphanumeric and underscore */ function checkValidName(name) { const nameCheck = /^[a-zA-Z0-9_]*$/.test(name); if (!nameCheck) { - console.log( + consola.warn( `Template name contains invalid characters. Skipping. Valid template names only include alphanumeric characters and underscores. Current name: ${name}` ); return false; @@ -21,7 +23,7 @@ function filterParts(template) { function missingLiquidCode(template) { if (!template.text) { - console.log( + consola.warn( `Template ${ template.handle || template.name || "" }: this template's liquid code was empty or hidden so it was not imported.` diff --git a/package-lock.json b/package-lock.json index f0070fa..1abdbb6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,19 @@ { "name": "silverfin-cli", - "version": "1.14.1", + "version": "1.17.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "silverfin-cli", - "version": "1.14.1", + "version": "1.17.0", "license": "MIT", "dependencies": { "axios": "^0.26.1", "chalk": "4.1.2", "command-exists": "^1.2.9", "commander": "^9.4.0", + "consola": "^3.2.3", "is-wsl": "^2.2.0", "open": "^8.4.0", "prompt-sync": "^4.2.0", @@ -363,6 +364,14 @@ "dev": true, "license": "MIT" }, + "node_modules/consola": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz", + "integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", diff --git a/package.json b/package.json index afb30be..877b94e 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "chalk": "4.1.2", "command-exists": "^1.2.9", "commander": "^9.4.0", + "consola": "^3.2.3", "is-wsl": "^2.2.0", "open": "^8.4.0", "prompt-sync": "^4.2.0", diff --git a/yarn.lock b/yarn.lock index dce1f41..0ead70b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -161,6 +161,11 @@ concat-map@0.0.1: resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== +consola@^3.2.3: + version "3.2.3" + resolved "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz" + integrity sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ== + cross-spawn@^7.0.2: version "7.0.3" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz"