diff --git a/.github/styles/vocab.txt b/.github/vale/Vocab/Backstage/accept.txt similarity index 100% rename from .github/styles/vocab.txt rename to .github/vale/Vocab/Backstage/accept.txt diff --git a/.github/vale/config.ini b/.github/vale/config.ini new file mode 100644 index 0000000000..36acb2c058 --- /dev/null +++ b/.github/vale/config.ini @@ -0,0 +1,6 @@ +StylesPath = . +Vocab = Backstage + +[*.md] +BasedOnStyles = Vale +Vale.Terms = NO diff --git a/.github/workflows/verify_docs-quality.yml b/.github/workflows/verify_docs-quality.yml index 55c1c23587..997890d5ad 100644 --- a/.github/workflows/verify_docs-quality.yml +++ b/.github/workflows/verify_docs-quality.yml @@ -12,10 +12,17 @@ jobs: steps: - uses: actions/checkout@v2 + + # Vale does not support file excludes, so we use the script to generate a list of files instead + - name: list files to check + id: list + run: echo "::set-output name=files::$(node scripts/check-docs-quality.js --list)" + - name: documentation quality check - uses: errata-ai/vale-action@v1.4.0 + uses: errata-ai/vale-action@v1.5.0 # Whitelist excluding ADOPTERS, CHANGELOG and OWNERS (no exclude flag exists) with: - files: '[".changeset", ".github", "contrib", "docs", "microsite", "packages", "plugins", "CONTRIBUTING.md", "CODE_OF_CONDUCT.md", "GOVERNANCE.md", "README.md"]' + config: .github/vale/config.ini + files: '${{ steps.list.files }}' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/scripts/check-docs-quality.js b/scripts/check-docs-quality.js index 3352596890..a4adfba929 100755 --- a/scripts/check-docs-quality.js +++ b/scripts/check-docs-quality.js @@ -13,81 +13,100 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -const { execSync, spawnSync } = require('child_process'); -// eslint-disable-next-line import/no-extraneous-dependencies +const { spawnSync } = require('child_process'); +const { resolve: resolvePath, join: joinPath } = require('path'); const commandExists = require('command-exists'); +const fs = require('fs').promises; -const inheritStdIo = { - stdio: 'inherit', -}; +const IGNORED = [ + /^ADOPTERS\.md$/, + /^OWNERS\.md$/, + /^docs[/\\]releases[/\\]*-changelog\.md$/, +]; -const LINT_SKIPPED_MESSAGE = - 'Skipping documentation quality check (vale not found). Install vale linter (https://docs.errata.ai/vale/install) to enable.\n'; -const LINT_ERROR_MESSAGE = `Language linter (vale) generated errors. Please check the errors and review any markdown files that you changed. - Possibly update .github/styles/vocab.txt to add new valid words.\n`; -const VALE_NOT_FOUND_MESSAGE = `Language linter (vale) was not found. Please install vale linter (https://docs.errata.ai/vale/install).\n`; +const rootDir = resolvePath(__dirname, '..'); -// Note: Make sure the script is run as `node check-docs-quality.js [FILES]` instead of `./check-docs-quality.js [FILES]` -// If the script receives arguments (file paths), the script is run exclusively on them. (e.g. when run via pre-commit hook) -const getFilesToLint = () => { - // Files have been provided as arguments - if (process.argv.length > 2) { - return process.argv.slice(2); - } +// Manual listing to avoid dependency install for listing files in CI +async function listFiles(dir = '') { + const files = await fs.readdir(dir || rootDir); + const paths = await Promise.all( + files + .filter(file => file !== 'node_modules') + .map(async file => { + const path = joinPath(dir, file); - let command = `git ls-files | ./node_modules/.bin/shx grep ".md"`; - if (process.platform === 'win32') { - command = `git ls-files | .\\node_modules\\.bin\\shx grep ".md"`; - } + if (IGNORED.some(pattern => pattern.test(path))) { + return []; + } + if ((await fs.stat(path)).isDirectory()) { + return listFiles(path); + } + if (!path.endsWith('.md')) { + return []; + } + return path; + }), + ); + return paths.flat(); +} - // Note this ignore list only applies locally, CI runs `.github/workflows/docs-quality-checker.yml` - const ignored = ['', 'ADOPTERS.md', 'OWNERS.md']; - - return execSync(command, { - stdio: ['ignore', 'pipe', 'inherit'], - }) - .toString() - .split('\n') - .filter(el => !ignored.includes(el)); -}; - -// Proceed with the script only if Vale linter is installed. Limit the friction and surprises caused by the script. -// On CI, we want to ensure vale linter is run. -commandExists('vale') - .catch(() => { +// Proceed with the script only if Vale linter is installed. Limit the friction and surprises +// caused by the script. In CI, we want to ensure vale linter is run. +async function exitIfMissingVale() { + try { + await commandExists('vale'); + } catch (e) { if (process.env.CI) { - console.log(VALE_NOT_FOUND_MESSAGE); + console.log( + `Language linter (vale) was not found. Please install vale linter (https://docs.errata.ai/vale/install).\n`, + ); process.exit(1); } - console.log(LINT_SKIPPED_MESSAGE); + console.log(`Language linter (vale) generated errors. Please check the errors and review any markdown files that you changed. + Possibly update .github/vale/Vocab/Backstage/accept.txt to add new valid words.\n`); process.exit(0); - }) - .then(() => { - const filesToLint = getFilesToLint(); + } +} - if (process.platform === 'win32') { - // Windows - try { - const output = spawnSync('vale', filesToLint, inheritStdIo); +async function runVale(files) { + const result = spawnSync( + 'vale', + ['--config', resolvePath(rootDir, '.github/vale/config.ini'), ...files], + { + stdio: 'inherit', + }, + ); - // If the command does not succeed - if (output.status !== 0) { - // If it contains system level error. In this case vale does not exist. - if (output.error) { - console.log(LINT_ERROR_MESSAGE); - } - process.exit(1); - } - } catch (e) { - console.log(e.message); - process.exit(1); - } - } else { - // Unix - const output = spawnSync('vale', filesToLint, inheritStdIo); - if (output.status !== 0) { - console.log(LINT_ERROR_MESSAGE); - process.exit(1); - } + if (result.status !== 0) { + // TODO(Rugvip): This logic was here before but seems a bit odd, could use some verification on windows. + // If it contains system level error. In this case vale does not exist. + if (process.platform !== 'win32' || result.error) { + console.log(`Language linter (vale) generated errors. Please check the errors and review any markdown files that you changed. + Possibly update .github/vale/Vocab/Backstage/accept.txt to add new valid words.\n`); } - }); + return false; + } + + return true; +} + +async function main() { + const files = await listFiles(); + + if (process.argv.includes('--list')) { + process.stdout.write(JSON.stringify(files)); + return; + } + + await exitIfMissingVale(); + + const success = await runVale(files); + if (!success) { + process.exit(2); + } +} + +main().catch(error => { + console.error(error); + process.exit(1); +});