Skip to content

Commit

Permalink
fix: added concurrency limit in markdown file processing to improve p…
Browse files Browse the repository at this point in the history
…erformance
  • Loading branch information
Aditya0733 committed Dec 11, 2024
1 parent 86dbaad commit 55353d0
Showing 1 changed file with 123 additions and 153 deletions.
276 changes: 123 additions & 153 deletions scripts/markdown/check-markdown.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
const fs = require('fs');
const path = require('path');
const matter = require('gray-matter');
const path = require('path');
const pLimit = require('p-limit');

Check warning on line 4 in scripts/markdown/check-markdown.js

View check run for this annotation

Codecov / codecov/patch

scripts/markdown/check-markdown.js#L4

Added line #L4 was not covered by tests

// Configuration
const parsedLimit = process.env.CONCURRENCY_LIMIT ? parseInt(process.env.CONCURRENCY_LIMIT, 10) : 10;
const CONCURRENCY_LIMIT = Number.isInteger(parsedLimit) && parsedLimit > 0 ? parsedLimit : 10;
const parsedLimit = process.env.CONCURRENCY_LIMIT

Check warning on line 7 in scripts/markdown/check-markdown.js

View check run for this annotation

Codecov / codecov/patch

scripts/markdown/check-markdown.js#L7

Added line #L7 was not covered by tests
? parseInt(process.env.CONCURRENCY_LIMIT, 10)
: 10;
const CONCURRENCY_LIMIT =
Number.isInteger(parsedLimit) && parsedLimit > 0 ? parsedLimit : 10;

Check warning on line 11 in scripts/markdown/check-markdown.js

View check run for this annotation

Codecov / codecov/patch

scripts/markdown/check-markdown.js#L11

Added line #L11 was not covered by tests

/**
* Checks if a given string is a valid URL.
* @param {string} str - The string to validate as a URL.
* @returns {boolean} True if the string is a valid URL, false otherwise.
*/
function isValidURL(str) {
try {
new URL(str);
return true;
} catch (err) {
return false;
}
try {
new URL(str);
return true;

Check warning on line 21 in scripts/markdown/check-markdown.js

View check run for this annotation

Codecov / codecov/patch

scripts/markdown/check-markdown.js#L19-L21

Added lines #L19 - L21 were not covered by tests
} catch (err) {
return false;

Check warning on line 23 in scripts/markdown/check-markdown.js

View check run for this annotation

Codecov / codecov/patch

scripts/markdown/check-markdown.js#L23

Added line #L23 was not covered by tests
}
}

/**
Expand All @@ -28,51 +31,60 @@ function isValidURL(str) {
* @returns {string[]|null} An array of validation error messages, or null if no errors.
*/
function validateBlogs(frontmatter) {
const requiredAttributes = ['title', 'date', 'type', 'tags', 'cover', 'authors'];
const errors = [];

// Check for required attributes
requiredAttributes.forEach(attr => {
if (!frontmatter.hasOwnProperty(attr)) {
errors.push(`${attr} is missing`);
}
});

// Validate date format
if (frontmatter.date && Number.isNaN(Date.parse(frontmatter.date))) {
errors.push(`Invalid date format: ${frontmatter.date}`);
const requiredAttributes = [

Check warning on line 34 in scripts/markdown/check-markdown.js

View check run for this annotation

Codecov / codecov/patch

scripts/markdown/check-markdown.js#L34

Added line #L34 was not covered by tests
'title',
'date',
'type',
'tags',
'cover',
'authors',
];
const errors = [];

Check warning on line 42 in scripts/markdown/check-markdown.js

View check run for this annotation

Codecov / codecov/patch

scripts/markdown/check-markdown.js#L42

Added line #L42 was not covered by tests

// Check for required attributes
requiredAttributes.forEach((attr) => {
if (!frontmatter.hasOwnProperty(attr)) {
errors.push(`${attr} is missing`);

Check warning on line 47 in scripts/markdown/check-markdown.js

View check run for this annotation

Codecov / codecov/patch

scripts/markdown/check-markdown.js#L45-L47

Added lines #L45 - L47 were not covered by tests
}

// Validate tags format (must be an array)
if (frontmatter.tags && !Array.isArray(frontmatter.tags)) {
errors.push(`Tags should be an array`);
}

// Validate cover is a string
if (frontmatter.cover && typeof frontmatter.cover !== 'string') {
errors.push(`Cover must be a string`);
}

// Validate authors (must be an array with valid attributes)
if (frontmatter.authors) {
if (!Array.isArray(frontmatter.authors)) {
errors.push('Authors should be an array');
} else {
frontmatter.authors.forEach((author, index) => {
if (!author.name) {
errors.push(`Author at index ${index} is missing a name`);
}
if (author.link && !isValidURL(author.link)) {
errors.push(`Invalid URL for author at index ${index}: ${author.link}`);
}
if (!author.photo) {
errors.push(`Author at index ${index} is missing a photo`);
}
});
});

// Validate date format
if (frontmatter.date && Number.isNaN(Date.parse(frontmatter.date))) {
errors.push(`Invalid date format: ${frontmatter.date}`);

Check warning on line 53 in scripts/markdown/check-markdown.js

View check run for this annotation

Codecov / codecov/patch

scripts/markdown/check-markdown.js#L52-L53

Added lines #L52 - L53 were not covered by tests
}

// Validate tags format (must be an array)
if (frontmatter.tags && !Array.isArray(frontmatter.tags)) {
errors.push(`Tags should be an array`);

Check warning on line 58 in scripts/markdown/check-markdown.js

View check run for this annotation

Codecov / codecov/patch

scripts/markdown/check-markdown.js#L57-L58

Added lines #L57 - L58 were not covered by tests
}

// Validate cover is a string
if (frontmatter.cover && typeof frontmatter.cover !== 'string') {
errors.push(`Cover must be a string`);

Check warning on line 63 in scripts/markdown/check-markdown.js

View check run for this annotation

Codecov / codecov/patch

scripts/markdown/check-markdown.js#L62-L63

Added lines #L62 - L63 were not covered by tests
}

// Validate authors (must be an array with valid attributes)
if (frontmatter.authors) {
if (!Array.isArray(frontmatter.authors)) {
errors.push('Authors should be an array');

Check warning on line 69 in scripts/markdown/check-markdown.js

View check run for this annotation

Codecov / codecov/patch

scripts/markdown/check-markdown.js#L67-L69

Added lines #L67 - L69 were not covered by tests
} else {
frontmatter.authors.forEach((author, index) => {
if (!author.name) {
errors.push(`Author at index ${index} is missing a name`);

Check warning on line 73 in scripts/markdown/check-markdown.js

View check run for this annotation

Codecov / codecov/patch

scripts/markdown/check-markdown.js#L71-L73

Added lines #L71 - L73 were not covered by tests
}
if (author.link && !isValidURL(author.link)) {
errors.push(

Check warning on line 76 in scripts/markdown/check-markdown.js

View check run for this annotation

Codecov / codecov/patch

scripts/markdown/check-markdown.js#L75-L76

Added lines #L75 - L76 were not covered by tests
`Invalid URL for author at index ${index}: ${author.link}`,
);
}
if (!author.photo) {
errors.push(`Author at index ${index} is missing a photo`);

Check warning on line 81 in scripts/markdown/check-markdown.js

View check run for this annotation

Codecov / codecov/patch

scripts/markdown/check-markdown.js#L80-L81

Added lines #L80 - L81 were not covered by tests
}
});
}
}

return errors.length ? errors : null;
return errors.length ? errors : null;

Check warning on line 87 in scripts/markdown/check-markdown.js

View check run for this annotation

Codecov / codecov/patch

scripts/markdown/check-markdown.js#L87

Added line #L87 was not covered by tests
}

/**
Expand All @@ -82,19 +94,22 @@ function validateBlogs(frontmatter) {
* @returns {string[]|null} An array of validation error messages, or null if no errors.
*/
function validateDocs(frontmatter) {
const errors = [];

// Check if title exists and is a string
if (!frontmatter.title || typeof frontmatter.title !== 'string') {
errors.push('Title is missing or not a string');
}

// Check if weight exists and is a number
if (frontmatter.weight === undefined || typeof frontmatter.weight !== 'number') {
errors.push('Weight is missing or not a number');
}

return errors.length ? errors : null;
const errors = [];

Check warning on line 97 in scripts/markdown/check-markdown.js

View check run for this annotation

Codecov / codecov/patch

scripts/markdown/check-markdown.js#L97

Added line #L97 was not covered by tests

// Check if title exists and is a string
if (!frontmatter.title || typeof frontmatter.title !== 'string') {
errors.push('Title is missing or not a string');

Check warning on line 101 in scripts/markdown/check-markdown.js

View check run for this annotation

Codecov / codecov/patch

scripts/markdown/check-markdown.js#L100-L101

Added lines #L100 - L101 were not covered by tests
}

// Check if weight exists and is a number
if (

Check warning on line 105 in scripts/markdown/check-markdown.js

View check run for this annotation

Codecov / codecov/patch

scripts/markdown/check-markdown.js#L105

Added line #L105 was not covered by tests
frontmatter.weight === undefined ||
typeof frontmatter.weight !== 'number'
) {
errors.push('Weight is missing or not a number');

Check warning on line 109 in scripts/markdown/check-markdown.js

View check run for this annotation

Codecov / codecov/patch

scripts/markdown/check-markdown.js#L109

Added line #L109 was not covered by tests
}

return errors.length ? errors : null;

Check warning on line 112 in scripts/markdown/check-markdown.js

View check run for this annotation

Codecov / codecov/patch

scripts/markdown/check-markdown.js#L112

Added line #L112 was not covered by tests
}

/**
Expand All @@ -104,101 +119,56 @@ function validateDocs(frontmatter) {
* @param {string} [relativePath=''] - The relative path of the folder for logging purposes.
*/
async function checkMarkdownFiles(
folderPath,
validateFunction,
relativePath = '',
folderPath,
validateFunction,
relativePath = '',
) {
const limit = pLimit(CONCURRENCY_LIMIT);
const allErrors = [];

try {
// Read directory contents synchronously for predictability
const files = fs.readdirSync(folderPath);

// Process files with concurrency control
const filePromises = files.map((file) =>
limit(async () => {
const filePath = path.join(folderPath, file);
const relativeFilePath = path.join(relativePath, file);

// Skip the folder 'docs/reference/specification'
if (relativeFilePath.includes('reference/specification')) {
return [];
}

// Get file stats
const stats = fs.statSync(filePath);

// Recurse if directory, otherwise validate markdown file
if (stats.isDirectory()) {
const subdirErrors = await checkMarkdownFiles(
filePath,
validateFunction,
relativeFilePath,
);
allErrors.push(...subdirErrors);
} else if (path.extname(file) === '.md') {
try {
const fileContent = fs.readFileSync(filePath, 'utf-8');
const { data: frontmatter } = matter(fileContent);

const errors = validateFunction(frontmatter);
if (errors.length > 0) {
console.log(`Errors in file ${relativeFilePath}:`);
allErrors.push(`File ${relativeFilePath}: ${errors.join('; ')}`);
}
} catch (readError) {
allErrors.push(
`Error reading file ${relativeFilePath}: ${readError.message}`,
);
}
}

return [];
}),
);

// Wait for all file promises to complete
await Promise.all(filePromises);

return allErrors;
} catch (error) {
console.error('Error processing files:', error);
return [`Folder processing error: ${error.message}`];
const limit = pLimit(CONCURRENCY_LIMIT);

Check warning on line 126 in scripts/markdown/check-markdown.js

View check run for this annotation

Codecov / codecov/patch

scripts/markdown/check-markdown.js#L126

Added line #L126 was not covered by tests

fs.readdir(folderPath, (err, files) => {
if (err) {
console.error('Error reading directory:', err);
return;

Check warning on line 131 in scripts/markdown/check-markdown.js

View check run for this annotation

Codecov / codecov/patch

scripts/markdown/check-markdown.js#L128-L131

Added lines #L128 - L131 were not covered by tests
}
}

/**
* Main validation function
* @throws {Error} If validation fails
*/
async function validateMarkdownFiles() {
const docsFolderPath = path.resolve(__dirname, '../../markdown/docs');
const blogsFolderPath = path.resolve(__dirname, '../../markdown/blog');

try {
// Validate docs
const docsErrors = await checkMarkdownFiles(docsFolderPath, validateDocs);
if (docsErrors.length > 0) {
console.error('Docs Validation Errors:');
docsErrors.forEach((error) => console.error(error));
throw new Error('Documentation markdown validation failed');
}
files.map((file) =>
limit(() => {
const filePath = path.join(folderPath, file);
const relativeFilePath = path.join(relativePath, file);

Check warning on line 137 in scripts/markdown/check-markdown.js

View check run for this annotation

Codecov / codecov/patch

scripts/markdown/check-markdown.js#L134-L137

Added lines #L134 - L137 were not covered by tests

// Validate blogs
const blogErrors = await checkMarkdownFiles(blogsFolderPath, validateBlogs);
if (blogErrors.length > 0) {
console.error('Blog Validation Errors:');
blogErrors.forEach((error) => console.error(error));
throw new Error('Blog markdown validation failed');
// Skip auto-generated specification documentation
if (relativeFilePath.includes('reference/specification')) {
return;

Check warning on line 141 in scripts/markdown/check-markdown.js

View check run for this annotation

Codecov / codecov/patch

scripts/markdown/check-markdown.js#L140-L141

Added lines #L140 - L141 were not covered by tests
}

console.log('All markdown files validated successfully');
} catch (error) {
console.error('Validation process failed:', error.message);
process.exit(1);
}
fs.stat(filePath, (err, stats) => {
if (err) {
console.error('Error reading file stats:', err);
return;

Check warning on line 147 in scripts/markdown/check-markdown.js

View check run for this annotation

Codecov / codecov/patch

scripts/markdown/check-markdown.js#L144-L147

Added lines #L144 - L147 were not covered by tests
}

// Recurse if directory, otherwise validate markdown file
if (stats.isDirectory()) {
checkMarkdownFiles(filePath, validateFunction, relativeFilePath);
} else if (path.extname(file) === '.md') {
const fileContent = fs.readFileSync(filePath, 'utf-8');
const { data: frontmatter } = matter(fileContent);

Check warning on line 155 in scripts/markdown/check-markdown.js

View check run for this annotation

Codecov / codecov/patch

scripts/markdown/check-markdown.js#L151-L155

Added lines #L151 - L155 were not covered by tests

const errors = validateFunction(frontmatter);
if (errors) {
console.log(`Errors in file ${relativeFilePath}:`);
errors.forEach((error) => console.log(` - ${error}`));
process.exitCode = 1;

Check warning on line 161 in scripts/markdown/check-markdown.js

View check run for this annotation

Codecov / codecov/patch

scripts/markdown/check-markdown.js#L157-L161

Added lines #L157 - L161 were not covered by tests
}
}
});
}),
);
});
}

// Run validation
validateMarkdownFiles();
const docsFolderPath = path.resolve(__dirname, '../../markdown/docs');
const blogsFolderPath = path.resolve(__dirname, '../../markdown/blog');

checkMarkdownFiles(docsFolderPath, validateDocs);
checkMarkdownFiles(blogsFolderPath, validateBlogs);

0 comments on commit 55353d0

Please sign in to comment.