From 4178f1ed50093fafa82363fc1d787261f018a7b8 Mon Sep 17 00:00:00 2001 From: Michiel Degezelle Date: Mon, 12 Jan 2026 14:11:32 +0100 Subject: [PATCH 1/3] Revert "Revert "Run multiple handles in parallel (#233)" (#238)" This reverts commit e0736b0c1f168fdf998190e40af41bcafa962f68. --- CHANGELOG.md | 4 +++ bin/cli.js | 44 ++++++++++++++++++++++-------- lib/liquidTestRunner.js | 60 ++++++++++++++++++++++++++++------------- package-lock.json | 22 +++++++-------- package.json | 2 +- 5 files changed, 91 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41ed43c..33d9e2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## [1.52.0] (12/01/2026) +This update improves test execution performance when running tests with status checks across multiple template handles. +Tests are now run in parallel for multiple handles when using the `--status` flag, significantly reducing the overall execution time. Previously, tests with status checks for multiple handles would run sequentially, but now they leverage parallel processing for better efficiency. This change only affects the `silverfin run-test` command when both multiple handles and the status flag are used together. + ## [1.51.0] (08/01/2026) This update should have no user impact whatsoever. diff --git a/bin/cli.js b/bin/cli.js index 5dd5385..92337f7 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -453,8 +453,8 @@ 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) @@ -462,7 +462,7 @@ program .option("--status", "Only return the status of the test runs as PASSED/FAILED (optional)", false) .option("-p, --pattern ", "Run all tests that match this pattern (optional)", "") - .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); @@ -474,17 +474,39 @@ program } const templateType = options.handle ? "reconciliationText" : "accountTemplate"; - const templateName = options.handle ? options.handle : options.accountTemplate; + let templateName = options.handle ? options.handle : options.accountTemplate; + + // Support pipe-separated values: if single string contains pipes, split it + if (templateName.length === 1 && typeof templateName[0] === 'string' && templateName[0].includes('|')) { + templateName = templateName[0].split('|').map(name => name.trim()).filter(name => name.length > 0); + } + + 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"); + process.exit(1); + } if (options.status) { - liquidTestRunner.runTestsStatusOnly(options.firm, templateType, templateName, options.test, options.pattern); - } 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, options.pattern); + // Status mode: allow multiple, pass array of template names + await liquidTestRunner.runTestsStatusOnly(options.firm, templateType, templateName, options.test, options.pattern); + return; + } + + // Non-status mode: always run a single template, pass string handle/name + 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"`); + process.exit(1); } + + await liquidTestRunner.runTestsWithOutput(options.firm, templateType, singleTemplateName, options.test, options.previewOnly, options.htmlInput, options.htmlPreview, options.pattern); }); // Create Liquid Test diff --git a/lib/liquidTestRunner.js b/lib/liquidTestRunner.js index b8716a2..5e43b15 100644 --- a/lib/liquidTestRunner.js +++ b/lib/liquidTestRunner.js @@ -469,33 +469,57 @@ 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 = "", pattern = "") { +async function runTestsStatusOnly(firmId, templateType, handles, testName = "", pattern = "") { if (templateType !== "reconciliationText" && templateType !== "accountTemplate") { consola.error(`Template type is missing or invalid`); process.exit(1); } - let status = "FAILED"; - const testResult = await runTests(firmId, templateType, handle, testName, false, "none", pattern); + const runSingleHandle = async (singleHandle) => { + let status = "FAILED"; + const failedTestNames = []; + const testResult = await runTests(firmId, templateType, singleHandle, testName, false, "none", pattern); - if (!testResult) { - status = "PASSED"; - consola.success(status); - return status; - } + if (!testResult) { + status = "PASSED"; + } else { + const testRun = testResult?.testRun; - 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); + } + }); + } + } + } - if (testRun && testRun?.status === "completed") { - const errorsPresent = checkAllTestsErrorsPresent(testRun.tests); - if (errorsPresent === false) { - status = "PASSED"; - consola.success(status); - return status; + if (status === "PASSED") { + consola.log(`${singleHandle}: ${status}`); + } else { + consola.log(`${singleHandle}: ${status}`); + // Display failed test names + failedTestNames.forEach((testName) => { + consola.log(` ${testName}: FAILED`); + }); } - } - consola.error(status); - return status; + + 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-lock.json b/package-lock.json index 446f8bb..687d65f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "silverfin-cli", - "version": "1.51.0", + "version": "1.52.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "silverfin-cli", - "version": "1.51.0", + "version": "1.52.0", "license": "MIT", "dependencies": { "axios": "^1.6.2", @@ -1265,9 +1265,9 @@ } }, "node_modules/@types/node": { - "version": "25.0.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz", - "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", + "version": "25.0.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.6.tgz", + "integrity": "sha512-NNu0sjyNxpoiW3YuVFfNz7mxSQ+S4X2G28uqg2s+CzoqoQjLPsWSbsFFyztIAqt2vb8kfEAsJNepMGPTxFDx3Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1561,9 +1561,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.9.11", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", - "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", + "version": "2.9.14", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz", + "integrity": "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -1690,9 +1690,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001762", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001762.tgz", - "integrity": "sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw==", + "version": "1.0.30001764", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz", + "integrity": "sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==", "dev": true, "funding": [ { diff --git a/package.json b/package.json index 875bce4..2f115c6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "silverfin-cli", - "version": "1.51.0", + "version": "1.52.0", "description": "Command line tool for Silverfin template development", "main": "index.js", "license": "MIT", From 261fe7ced96b135633841977423e6f76f4a8084d Mon Sep 17 00:00:00 2001 From: Michiel Degezelle Date: Mon, 12 Jan 2026 14:37:58 +0100 Subject: [PATCH 2/3] Remove the logic to accept pipes in between handles --- bin/cli.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/bin/cli.js b/bin/cli.js index 92337f7..4586a4b 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -474,12 +474,7 @@ program } const templateType = options.handle ? "reconciliationText" : "accountTemplate"; - let templateName = options.handle ? options.handle : options.accountTemplate; - - // Support pipe-separated values: if single string contains pipes, split it - if (templateName.length === 1 && typeof templateName[0] === 'string' && templateName[0].includes('|')) { - templateName = templateName[0].split('|').map(name => name.trim()).filter(name => name.length > 0); - } + 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"); @@ -506,7 +501,16 @@ program process.exit(1); } - await liquidTestRunner.runTestsWithOutput(options.firm, templateType, singleTemplateName, options.test, options.previewOnly, options.htmlInput, options.htmlPreview, options.pattern); + await liquidTestRunner.runTestsWithOutput( + options.firm, + templateType, + singleTemplateName, + options.test, + options.previewOnly, + options.htmlInput, + options.htmlPreview, + options.pattern + ); }); // Create Liquid Test From e37358c08af5d82547e8beac95e73f954f130142 Mon Sep 17 00:00:00 2001 From: Michiel Degezelle Date: Mon, 12 Jan 2026 15:13:58 +0100 Subject: [PATCH 3/3] Make sure status is FAILED if there is no test result for a handle --- lib/liquidTestRunner.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/liquidTestRunner.js b/lib/liquidTestRunner.js index 5e43b15..25f84df 100644 --- a/lib/liquidTestRunner.js +++ b/lib/liquidTestRunner.js @@ -481,7 +481,8 @@ async function runTestsStatusOnly(firmId, templateType, handles, testName = "", const testResult = await runTests(firmId, templateType, singleHandle, testName, false, "none", pattern); if (!testResult) { - status = "PASSED"; + status = "FAILED"; + consola.error(`Error running tests for ${singleHandle}`); } else { const testRun = testResult?.testRun;