Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions lib/utils/fsUtils.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const fs = require("fs");
const path = require("path");
const yaml = require("yaml");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Update the yaml package to version 2.8.2 or later to address known vulnerabilities.

The yaml library version 2.2.1 is outdated and contains known security vulnerabilities. Upgrade to version 2.8.2 or later, as 2.8.2 is the latest non-vulnerable version.

🤖 Prompt for AI Agents
In lib/utils/fsUtils.js at line 3 the project imports the outdated yaml package
(currently 2.2.1); update the dependency in package.json to "yaml": ">=2.8.2",
run npm install (or yarn install) to refresh node_modules and the lockfile
(package-lock.json or yarn.lock), run the test suite and lint to ensure no
breaking API changes, and commit the updated package.json and lockfile so the
project uses yaml v2.8.2 or later to address the security vulnerability.

const { consola } = require("consola");

const FOLDERS = {
Expand Down Expand Up @@ -355,6 +356,125 @@ function listExistingRelatedLiquidFiles(firmId, handle, templateType) {
return relatedLiquidFiles;
}

// Find all templates with liquid test YAML files in reconciliation_texts directories
// Only matches files whose basename ends with exactly `_liquid_test.yml`
// Also excludes files with extra suffixes like `_TY21_liquid_test.yml`, `_TY23_liquid_test.yml`, etc.
// Returns an array of template handles that have liquid test files
function findTemplatesWithLiquidTests() {
const results = [];
const folderPath = path.join(process.cwd(), FOLDERS.reconciliationText);

if (!fs.existsSync(folderPath)) {
return results;
}

const templateDirs = fs.readdirSync(folderPath);
for (const handle of templateDirs) {
const templateDir = path.join(folderPath, handle);
const stats = fs.statSync(templateDir);
if (!stats.isDirectory()) {
continue;
}

const testsDir = path.join(templateDir, "tests");
if (!fs.existsSync(testsDir)) {
continue;
}

const testFiles = fs.readdirSync(testsDir);
for (const fileName of testFiles) {
// Match files ending with exactly `_liquid_test.yml` (no extra suffix)
// Pattern: any handle followed by `_liquid_test.yml`, but exclude variants with extra suffixes like `_TY21`, `_TY23`
const mainPattern = /^(.+)_liquid_test\.yml$/;
const match = fileName.match(mainPattern);

if (match) {
const fileHandle = match[1];
// Exclude files with variant suffixes (e.g., `_TY21`, `_TY23`, etc.)
// These patterns typically have uppercase letters followed by digits before `_liquid_test`
const variantPattern = /_[A-Z]{2,}\d+$/; // Matches patterns like `_TY21`, `_TY23`, `_TY2021`, etc.
if (variantPattern.test(fileHandle)) {
continue; // Skip variant files
}

// Add the handle to results (avoid duplicates)
if (!results.includes(handle)) {
results.push(handle);
}
break; // Found liquid test file for this template, move to next template
}
}
}

return results;
}

// Find all templates that have a dependency on the given handle.
// Loops through all templates with liquid test files and checks if their YAML files
// mention the given handle in the data subtree.
// Currently only checks reconciliation texts (excludes account templates).
// @param {string} target_handle - The handle to search for in other templates' test files
// @returns {Array<string>} Array of handles that depend on the target_handle
function check_liquid_test_dependencies(target_handle) {
const dependentHandles = [];
const allHandlesWithTests = findTemplatesWithLiquidTests();

// Recursively check if target_handle appears in the data subtree
const containsHandle = (obj, handle) => {
if (obj === null || obj === undefined) return false;
if (typeof obj === "string") {
return obj === handle;
}
if (Array.isArray(obj)) {
return obj.some((item) => containsHandle(item, handle));
}
if (typeof obj === "object") {
// Check keys
if (Object.keys(obj).some((key) => key === handle)) {
return true;
}
// Check values
return Object.values(obj).some((value) => containsHandle(value, handle));
}
return false;
};

for (const handle of allHandlesWithTests) {
const liquidTestPath = path.join(
process.cwd(),
FOLDERS.reconciliationText,
handle,
"tests",
`${handle}_liquid_test.yml`
);

try {
const testContent = fs.readFileSync(liquidTestPath, "utf-8");
const testYAML = yaml.parse(testContent, { maxAliasCount: 10000 });

if (!testYAML || typeof testYAML !== "object") {
continue;
}

// Check each test case's data subtree
for (const testCaseName of Object.keys(testYAML)) {
const testCase = testYAML[testCaseName];
if (testCase && typeof testCase === "object" && testCase.data) {
if (containsHandle(testCase.data, target_handle)) {
dependentHandles.push(handle);
break; // Found in this template, move to next template
}
}
}
} catch (error) {
// Skip templates with parsing errors or missing files
continue;
}
}

return dependentHandles;
}

// Recursive option for fs.watch is not available in every OS (e.g. Linux)
function recursiveInspectDirectory({ basePath, collection, pathsArray = [], typeCheck = "liquid" }) {
collection.forEach((filePath) => {
Expand Down Expand Up @@ -437,7 +557,9 @@ module.exports = {
listSharedPartsUsedInTemplate,
listExistingFiles,
listExistingRelatedLiquidFiles,
findTemplatesWithLiquidTests,
identifyTypeAndHandle,
getTemplateId,
setTemplateId,
check_liquid_test_dependencies,
};
Loading
Loading