Skip to content

Commit

Permalink
test: add markdown tests (asyncapi#3301)
Browse files Browse the repository at this point in the history
Co-authored-by: Akshat Nema <[email protected]>
  • Loading branch information
anshgoyalevil and akshatnema authored Oct 23, 2024
1 parent 6387b71 commit 0b09994
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 2 deletions.
38 changes: 36 additions & 2 deletions .github/workflows/if-nodejs-pr-testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@ jobs:
name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "${{ steps.nodeversion.outputs.version }}"

node-version: '${{ steps.nodeversion.outputs.version }}'
- name: Install dependencies
run: npm ci
- if: steps.packagejson.outputs.exists == 'true'
Expand All @@ -76,6 +75,41 @@ jobs:
shell: bash
run: npm run generate:assets --if-present

# Run the test:md script and capture output
- if: steps.packagejson.outputs.exists == 'true'
name: Run markdown checks
id: markdown_check
run: |
ERRORS=$(npm run test:md | sed -n '/Errors in file/,$p')
echo "markdown_output<<EOF" >> $GITHUB_OUTPUT
echo "$ERRORS" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
# Post a comment using sticky-pull-request-comment
- name: Comment on PR with markdown issues
if: ${{ steps.markdown_check.outputs.markdown_output != '' }}
uses: marocchino/sticky-pull-request-comment@3d60a5b2dae89d44e0c6ddc69dd7536aec2071cd
with:
header: markdown-check-error
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
message: |
### Markdown Check Results
We found issues in the following markdown files:
```
${{ steps.markdown_check.outputs.markdown_output }}
```
# Delete the comment if there are no issues
- if: ${{ steps.markdown_check.outputs.markdown_output == '' }}
name: Delete markdown check comment
uses: marocchino/sticky-pull-request-comment@3d60a5b2dae89d44e0c6ddc69dd7536aec2071cd
with:
header: markdown-check-error
delete: true
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}

- if: steps.packagejson.outputs.exists == 'true'
name: Upload Coverage to Codecov
uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673
Expand Down
1 change: 1 addition & 0 deletions markdown/docs/migration/index.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
title: "Overview"
weight: 1
---
Migration to a new major version is always difficult, and AsyncAPI is no exception, but we want to provide as smooth a transition as possible.

Expand Down
1 change: 1 addition & 0 deletions markdown/docs/migration/migrating-to-v3.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
title: "Migrating to v3"
weight: 2
---


Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"generate:videos": "node scripts/build-newsroom-videos.js",
"generate:tools": "node scripts/build-tools.js",
"test:netlify": "deno test --allow-env --trace-ops netlify/**/*.test.ts",
"test:md": "node scripts/markdown/check-markdown.js",
"dev:storybook": "storybook dev -p 6006",
"build:storybook": "storybook build"
},
Expand Down
146 changes: 146 additions & 0 deletions scripts/markdown/check-markdown.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
const fs = require('fs');
const matter = require('gray-matter');
const path = require('path');

/**
* 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;
}
}

/**
* Validates the frontmatter of a blog post.
* @param {object} frontmatter - The frontmatter object to validate.
* @param {string} filePath - The path to the file being validated.
* @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}`);
}

// 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`);
}
});
}
}

return errors.length ? errors : null;
}

/**
* Validates the frontmatter of a documentation file.
* @param {object} frontmatter - The frontmatter object to validate.
* @param {string} filePath - The path to the file being validated.
* @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;
}

/**
* Recursively checks markdown files in a folder and validates their frontmatter.
* @param {string} folderPath - The path to the folder to check.
* @param {Function} validateFunction - The function used to validate the frontmatter.
* @param {string} [relativePath=''] - The relative path of the folder for logging purposes.
*/
function checkMarkdownFiles(folderPath, validateFunction, relativePath = '') {
fs.readdir(folderPath, (err, files) => {
if (err) {
console.error('Error reading directory:', err);
return;
}

files.forEach(file => {
const filePath = path.join(folderPath, file);
const relativeFilePath = path.join(relativePath, file);

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

fs.stat(filePath, (err, stats) => {
if (err) {
console.error('Error reading file stats:', err);
return;
}

// 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);

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

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

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

0 comments on commit 0b09994

Please sign in to comment.