diff --git a/Makefile b/Makefile index 5eed314c2c7964..1d298cb857deae 100644 --- a/Makefile +++ b/Makefile @@ -629,6 +629,7 @@ test-doc: doc-only lint ## Builds, lints, and verifies the docs. else \ $(PYTHON) tools/test.py $(PARALLEL_ARGS) doctool; \ fi + $(NODE) tools/doc/checkLinks.js . test-known-issues: all $(PYTHON) tools/test.py $(PARALLEL_ARGS) known_issues diff --git a/doc/guides/collaborator-guide.md b/doc/guides/collaborator-guide.md index 956eae7fb23726..f5b4b3c10ef153 100644 --- a/doc/guides/collaborator-guide.md +++ b/doc/guides/collaborator-guide.md @@ -37,8 +37,8 @@ This document explains how Collaborators manage the Node.js project. Collaborators should understand the -[guidelines for new contributors](CONTRIBUTING.md) and the -[project governance model](GOVERNANCE.md). +[guidelines for new contributors](../../CONTRIBUTING.md) and the +[project governance model](../../GOVERNANCE.md). ## Issues and Pull Requests @@ -50,7 +50,7 @@ request. See [Who to CC in the issue tracker](#who-to-cc-in-the-issue-tracker). Always show courtesy to individuals submitting issues and pull requests. Be welcoming to first-time contributors, identified by the GitHub -![First-time contributor](./doc/first_timer_badge.png) badge. +![First-time contributor](../first_timer_badge.png) badge. For first-time contributors, check if the commit author is the same as the pull request author. This way, once their pull request lands, GitHub will show them @@ -474,7 +474,7 @@ $ git checkout master ``` Update the tree (assumes your repo is set up as detailed in -[CONTRIBUTING.md](./doc/guides/contributing/pull-requests.md#step-1-fork)): +[CONTRIBUTING.md](./contributing/pull-requests.md#step-1-fork)): ```text $ git fetch upstream diff --git a/doc/guides/cpp-style-guide.md b/doc/guides/cpp-style-guide.md index f3dcd4e647b04e..f937b60edc20d4 100644 --- a/doc/guides/cpp-style-guide.md +++ b/doc/guides/cpp-style-guide.md @@ -1,7 +1,7 @@ # C++ Style Guide -See also the [C++ codebase README](src/README.md) for C++ idioms in the Node.js -codebase not related to stylistic issues. +See also the [C++ codebase README](../../src/README.md) for C++ idioms in the +Node.js codebase not related to stylistic issues. ## Table of Contents diff --git a/doc/guides/releases.md b/doc/guides/releases.md index 37018e1b3fd002..2068a46dc111e4 100644 --- a/doc/guides/releases.md +++ b/doc/guides/releases.md @@ -322,8 +322,8 @@ accordingly by removing the bold styling from the previous release. If this release includes new APIs then it is necessary to document that they were first added in this version. The relevant commits should already include `REPLACEME` tags as per the example in the -[docs README](../tools/doc/README.md). Check for these tags with `grep REPLACEME -doc/api/*.md`, and substitute this node version with `sed -i +[docs README](../../tools/doc/README.md). Check for these tags with `grep +REPLACEME doc/api/*.md`, and substitute this node version with `sed -i "s/REPLACEME/$VERSION/g" doc/api/*.md` or `perl -pi -e "s/REPLACEME/$VERSION/g" doc/api/*.md`. diff --git a/onboarding.md b/onboarding.md index b1cc1ab031d59c..d6cb92af868df7 100644 --- a/onboarding.md +++ b/onboarding.md @@ -83,7 +83,7 @@ onboarding session. * Be nice about closing issues! Let people know why, and that issues and PRs can be reopened if necessary -* [**See "Labels"**](./onboarding-extras.md#labels) +* [**See "Labels"**](./doc/guides/onboarding-extras.md#labels) * There is [a bot](https://github.com/nodejs-github-bot/github-bot) that applies subsystem labels (for example, `doc`, `test`, `assert`, or `buffer`) so that we know what parts of the code base the pull request modifies. It is diff --git a/tools/doc/checkLinks.js b/tools/doc/checkLinks.js new file mode 100644 index 00000000000000..dc5e6e1573abee --- /dev/null +++ b/tools/doc/checkLinks.js @@ -0,0 +1,74 @@ +'use strict'; + +const fs = require('fs'); +const { Worker, isMainThread, workerData: path } = require('worker_threads'); + +function* getLinksRecursively(node) { + if ( + (node.type === 'link' && !node.url.startsWith('#')) || + node.type === 'image' + ) { + yield node; + } + for (const child of node.children || []) { + yield* getLinksRecursively(child); + } +} + +if (isMainThread) { + const { extname, join, resolve } = require('path'); + const DIR = resolve(process.argv[2]); + + console.log('Running Markdown link checker...'); + + async function* findMarkdownFilesRecursively(dirPath) { + const fileNames = await fs.promises.readdir(dirPath); + + for (const fileName of fileNames) { + const path = join(dirPath, fileName); + + const stats = await fs.promises.stat(path); + if ( + stats.isDirectory() && + fileName !== 'api' && + fileName !== 'deps' && + fileName !== 'node_modules' + ) { + yield* findMarkdownFilesRecursively(path); + } else if (extname(fileName) === '.md') { + yield path; + } + } + } + + function errorHandler(error) { + console.error(error); + process.exitCode = 1; + } + + setImmediate(async () => { + for await (const path of findMarkdownFilesRecursively(DIR)) { + new Worker(__filename, { workerData: path }).on('error', errorHandler); + } + }); +} else { + const unified = require('unified'); + const { pathToFileURL } = require('url'); + + const tree = unified() + .use(require('remark-parse')) + .parse(fs.readFileSync(path)); + + const base = pathToFileURL(path); + for (const node of getLinksRecursively(tree)) { + const targetURL = new URL(node.url, base); + if (targetURL.protocol === 'file:' && !fs.existsSync(targetURL)) { + const error = new Error('Broken link in a Markdown document.'); + const { start } = node.position; + error.stack = + error.stack.substring(0, error.stack.indexOf('\n') + 5) + + `at ${node.type} (${path}:${start.line}:${start.column})`; + throw error; + } + } +}