From 511d8c36f6b26a41347b7e3a00fb363b802cb16c Mon Sep 17 00:00:00 2001 From: Michiel Degezelle Date: Tue, 25 Nov 2025 08:35:31 +0100 Subject: [PATCH 1/5] Run tests with status in parallel for multiple handles --- bin/cli.js | 16 +++++++---- lib/liquidTestRunner.js | 61 +++++++++++++++++++++++++++++------------ package.json | 2 +- 3 files changed, 56 insertions(+), 23 deletions(-) diff --git a/bin/cli.js b/bin/cli.js index d4e826c9..5700143f 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -451,15 +451,15 @@ program .command("run-test") .description("Run Liquid Tests for a reconciliation template from a YAML file") .requiredOption("-f, --firm ", "Specify the firm to be used", firmIdDefault) - .option("-h, --handle ", "Specify the reconciliation to be used (mandatory)") - .option("-at, --account-template ", "Specify the account template to be used (mandatory)") + .option("-h, --handle ", "Specify one or more reconciliations to be used (mandatory)") + .option("-at, --account-template ", "Specify one or more account templates to be used (mandatory)") .option("-t, --test ", "Specify the name of the test to be run (optional)", "") .option("--html-input", "Get a static html of the input-view of the template generated with the Liquid Test data (optional)", false) .option("--html-preview", "Get a static html of the export-view of the template generated with the Liquid Test data (optional)", false) .option("--preview-only", "Skip the checking of the results of the Liquid Test in case you only want to generate a preview template (optional)", false) .option("--status", "Only return the status of the test runs as PASSED/FAILED (optional)", false) - .action((options) => { + .action(async (options) => { if (!options.handle && !options.accountTemplate) { consola.error("You need to specify either a reconciliation handle or an account template"); process.exit(1); @@ -468,14 +468,20 @@ program const templateType = options.handle ? "reconciliationText" : "accountTemplate"; const templateName = options.handle ? options.handle : options.accountTemplate; + // Check if multiple handles are provided without --status flag + if (templateName.length > 1 && !options.status) { + consola.error("Multiple handles are only allowed when used with the --status flag"); + process.exit(1); + } + if (options.status) { - liquidTestRunner.runTestsStatusOnly(options.firm, templateType, templateName, options.test); + await liquidTestRunner.runTestsStatusOnly(options.firm, templateType, templateName, options.test); } else { if (options.previewOnly && !options.htmlInput && !options.htmlPreview) { consola.info(`When using "--preview-only" you need to specify at least one of the following options: "--html-input", "--html-preview"`); process.exit(1); } - liquidTestRunner.runTestsWithOutput(options.firm, templateType, templateName, options.test, options.previewOnly, options.htmlInput, options.htmlPreview); + await liquidTestRunner.runTestsWithOutput(options.firm, templateType, templateName, options.test, options.previewOnly, options.htmlInput, options.htmlPreview); } }); diff --git a/lib/liquidTestRunner.js b/lib/liquidTestRunner.js index 9ade06da..74238ecf 100644 --- a/lib/liquidTestRunner.js +++ b/lib/liquidTestRunner.js @@ -427,27 +427,54 @@ async function runTestsStatusOnly(firmId, templateType, handle, testName = "") { process.exit(1); } - let status = "FAILED"; - const testResult = await runTests(firmId, templateType, handle, testName, false, "none"); + // handle can be an array (multiple handles/templates) or a string (single handle/template) + const handles = Array.isArray(handle) ? handle : [handle]; - if (!testResult) { - status = "PASSED"; - consola.success(status); - return status; - } - - const testRun = testResult?.testRun; + const runSingleHandle = async (singleHandle) => { + let status = "FAILED"; + const failedTestNames = []; + const testResult = await runTests(firmId, templateType, singleHandle, testName, false, "none"); - if (testRun && testRun?.status === "completed") { - const errorsPresent = checkAllTestsErrorsPresent(testRun.tests); - if (errorsPresent === false) { + if (!testResult) { status = "PASSED"; - consola.success(status); - return status; + } else { + const testRun = testResult?.testRun; + + if (testRun && testRun?.status === "completed") { + const errorsPresent = checkAllTestsErrorsPresent(testRun.tests); + if (errorsPresent === false) { + status = "PASSED"; + } else { + // Extract failed test names + const testNames = Object.keys(testRun.tests).sort(); + testNames.forEach((testName) => { + const testErrorsPresent = checkTestErrorsPresent(testName, testRun.tests); + if (testErrorsPresent) { + failedTestNames.push(testName); + } + }); + } + } } - } - consola.error(status); - return status; + + if (status === "PASSED") { + consola.success(`${singleHandle}: ${status}`); + } else { + consola.error(`${singleHandle}: ${status}`); + // Display failed test names + failedTestNames.forEach((testName) => { + consola.log(` ${testName}: FAILED`); + }); + } + + return { handle: singleHandle, status, failedTestNames }; + }; + + const results = await Promise.all(handles.map(runSingleHandle)); + + const overallStatus = results.every((result) => result.status === "PASSED") ? "PASSED" : "FAILED"; + + return overallStatus; } module.exports = { diff --git a/package.json b/package.json index c05da0ff..5e247514 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "silverfin-cli", - "version": "1.47.0", + "version": "1.50.0", "description": "Command line tool for Silverfin template development", "main": "index.js", "license": "MIT", From a5eff1e3332d9b090c5a45420be437ad89be56be Mon Sep 17 00:00:00 2001 From: Michiel Degezelle Date: Wed, 26 Nov 2025 16:32:26 +0100 Subject: [PATCH 2/5] Don't transform the handle string into an array in case of a non-status test run --- bin/cli.js | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/bin/cli.js b/bin/cli.js index 5700143f..78a29294 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -468,21 +468,30 @@ program const templateType = options.handle ? "reconciliationText" : "accountTemplate"; const templateName = options.handle ? options.handle : options.accountTemplate; - // Check if multiple handles are provided without --status flag - if (templateName.length > 1 && !options.status) { - consola.error("Multiple handles are only allowed when used with the --status flag"); + // Normalize to array for uniform handling (Commander returns array for variadic, string otherwise) + const templateNames = Array.isArray(templateName) ? templateName : [templateName]; + + // Block multiple handles/templates without --status + if (templateNames.length > 1 && !options.status) { + consola.error("Multiple handles/templates are only allowed when used with the --status flag"); process.exit(1); } if (options.status) { - await liquidTestRunner.runTestsStatusOnly(options.firm, templateType, templateName, options.test); - } else { - if (options.previewOnly && !options.htmlInput && !options.htmlPreview) { - consola.info(`When using "--preview-only" you need to specify at least one of the following options: "--html-input", "--html-preview"`); - process.exit(1); - } - await liquidTestRunner.runTestsWithOutput(options.firm, templateType, templateName, options.test, options.previewOnly, options.htmlInput, options.htmlPreview); + // Status mode: allow multiple, pass array of template names + await liquidTestRunner.runTestsStatusOnly(options.firm, templateType, templateNames, options.test); + return; + } + + // Non-status mode: always run a single template, pass string handle/name + const singleTemplateName = templateNames[0]; + + if (options.previewOnly && !options.htmlInput && !options.htmlPreview) { + consola.info(`When using "--preview-only" you need to specify at least one of the following options: "--html-input", "--html-preview"`); + process.exit(1); } + + await liquidTestRunner.runTestsWithOutput(options.firm, templateType, singleTemplateName, options.test, options.previewOnly, options.htmlInput, options.htmlPreview); }); // Create Liquid Test From e9ee2790bb1e9b332ca81151f9db79b2c7bde8fe Mon Sep 17 00:00:00 2001 From: Michiel Degezelle Date: Wed, 3 Dec 2025 13:35:34 +0100 Subject: [PATCH 3/5] Bump version to correct number --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5e247514..6c39a3e4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "silverfin-cli", - "version": "1.50.0", + "version": "1.48.0", "description": "Command line tool for Silverfin template development", "main": "index.js", "license": "MIT", From 0345934a0460a5849bb4765d29489da6037a53c7 Mon Sep 17 00:00:00 2001 From: Michiel Degezelle Date: Fri, 5 Dec 2025 13:56:39 +0100 Subject: [PATCH 4/5] Review comments --- bin/cli.js | 10 +++------- lib/liquidTestRunner.js | 9 +++------ 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/bin/cli.js b/bin/cli.js index 78a29294..768a9200 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -467,24 +467,20 @@ program const templateType = options.handle ? "reconciliationText" : "accountTemplate"; const templateName = options.handle ? options.handle : options.accountTemplate; - - // Normalize to array for uniform handling (Commander returns array for variadic, string otherwise) - const templateNames = Array.isArray(templateName) ? templateName : [templateName]; - // Block multiple handles/templates without --status - if (templateNames.length > 1 && !options.status) { + if (templateName.length > 1 && !options.status) { consola.error("Multiple handles/templates are only allowed when used with the --status flag"); process.exit(1); } if (options.status) { // Status mode: allow multiple, pass array of template names - await liquidTestRunner.runTestsStatusOnly(options.firm, templateType, templateNames, options.test); + await liquidTestRunner.runTestsStatusOnly(options.firm, templateType, templateName, options.test); return; } // Non-status mode: always run a single template, pass string handle/name - const singleTemplateName = templateNames[0]; + const singleTemplateName = templateName[0]; if (options.previewOnly && !options.htmlInput && !options.htmlPreview) { consola.info(`When using "--preview-only" you need to specify at least one of the following options: "--html-input", "--html-preview"`); diff --git a/lib/liquidTestRunner.js b/lib/liquidTestRunner.js index 74238ecf..f44bfd24 100644 --- a/lib/liquidTestRunner.js +++ b/lib/liquidTestRunner.js @@ -421,15 +421,12 @@ async function runTestsWithOutput(firmId, templateType, handle, testName = "", p // RETURN (AND LOG) ONLY PASSED OR FAILED // CAN BE USED BY GITHUB ACTIONS -async function runTestsStatusOnly(firmId, templateType, handle, testName = "") { +async function runTestsStatusOnly(firmId, templateType, handles, testName = "") { if (templateType !== "reconciliationText" && templateType !== "accountTemplate") { consola.error(`Template type is missing or invalid`); process.exit(1); } - // handle can be an array (multiple handles/templates) or a string (single handle/template) - const handles = Array.isArray(handle) ? handle : [handle]; - const runSingleHandle = async (singleHandle) => { let status = "FAILED"; const failedTestNames = []; @@ -458,9 +455,9 @@ async function runTestsStatusOnly(firmId, templateType, handle, testName = "") { } if (status === "PASSED") { - consola.success(`${singleHandle}: ${status}`); + consola.log(`${singleHandle}: ${status}`); } else { - consola.error(`${singleHandle}: ${status}`); + consola.log(`${singleHandle}: ${status}`); // Display failed test names failedTestNames.forEach((testName) => { consola.log(` ${testName}: FAILED`); From 744946d7b1a97c1a05e1f834f30931dc0ac5231a Mon Sep 17 00:00:00 2001 From: Michiel Degezelle Date: Fri, 5 Dec 2025 16:49:32 +0100 Subject: [PATCH 5/5] Add additional warning in case no handles are provided --- bin/cli.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bin/cli.js b/bin/cli.js index 768a9200..21511eb1 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -467,6 +467,12 @@ program const templateType = options.handle ? "reconciliationText" : "accountTemplate"; const templateName = options.handle ? options.handle : options.accountTemplate; + + if (!templateName || templateName.length === 0) { + consola.error("You need to provide at least one handle or account template name"); + process.exit(1); + } + // Block multiple handles/templates without --status if (templateName.length > 1 && !options.status) { consola.error("Multiple handles/templates are only allowed when used with the --status flag");