From 524f600b0d2f44af42c34df28d11ed00f089a30e Mon Sep 17 00:00:00 2001 From: Justin Beckwith Date: Sat, 28 Nov 2020 19:34:23 -0800 Subject: [PATCH] feat: add basic support for markdown (#188) This adds a `--markdown` flag that allows for basic markdown link scanning. When passing `--markdown` on the CLI or setting the `markdown: true` option, markdown in the local directory will be rendered as HTML and scanned. --- .gitignore | 1 + README.md | 12 ++++++ package.json | 8 ++-- src/cli.ts | 7 +++- src/config.ts | 1 + src/index.ts | 63 ++++++++++++++++++++++++------ test/fixtures/markdown/LICENSE.md | 21 ++++++++++ test/fixtures/markdown/README.md | 6 +++ test/fixtures/markdown/boo.jpg | Bin 0 -> 855685 bytes test/test.ts | 9 +++++ tsconfig.json | 3 +- 11 files changed, 111 insertions(+), 20 deletions(-) create mode 100644 test/fixtures/markdown/LICENSE.md create mode 100644 test/fixtures/markdown/README.md create mode 100644 test/fixtures/markdown/boo.jpg diff --git a/.gitignore b/.gitignore index d13b004..d561b7b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ package-lock.json .nyc_output build/ coverage +.vscode diff --git a/README.md b/README.md index e0fa9b5..b567cb1 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Behold my latest inator! The `linkinator` provides an API and CLI for crawling w - 🔥Scan any element that includes links, not just `` - 🔥Supports redirects, absolute links, relative links, all the things - 🔥Configure specific regex patterns to skip +- 🔥Scan markdown files without transpilation ## Installation @@ -58,6 +59,9 @@ $ linkinator LOCATION [ --arguments ] --timeout Request timeout in ms. Defaults to 0 (no timeout). + + --markdown + Automatically parse and scan markdown if scanning from a location on disk. --help Show this command. @@ -101,6 +105,12 @@ Maybe you're going to pipe the output to another program. Use the `--format` op $ linkinator ./docs --format CSV ``` +Let's make sure the `README.md` in our repo doesn't have any busted links: + +```sh +$ linkinator ./README.md +``` + ### Configuration file You can pass options directly to the `linkinator` CLI, or you can define a config file. By default, `linkinator` will look for a `linkinator.config.json` file in the current working directory. @@ -113,6 +123,7 @@ All options are optional. It should look like this: "silent": true, "concurrency": 100, "timeout": 0, + "markdown": true, "skip": "www.googleapis.com" } ``` @@ -132,6 +143,7 @@ Asynchronous method that runs a site wide scan. Options come in the form of an o - `port` (number) - When the `path` is provided as a local path on disk, the `port` on which to start the temporary web server. Defaults to a random high range order port. - `recurse` (boolean) - By default, all scans are shallow. Only the top level links on the requested page will be scanned. By setting `recurse` to `true`, the crawler will follow all links on the page, and continue scanning links **on the same domain** for as long as it can go. Results are cached, so no worries about loops. - `timeout` (number) - By default, requests made by linkinator do not time out (or follow the settings of the OS). This option (in milliseconds) will fail requests after the configured amount of time. +- `markdown` (boolean) - Automatically parse and scan markdown if scanning from a location on disk. - `linksToSkip` (array | function) - An array of regular expression strings that should be skipped, OR an async function that's called for each link with the link URL as its only argument. Return a Promise that resolves to `true` to skip the link or `false` to check it. #### linkinator.LinkChecker() diff --git a/package.json b/package.json index 1deab20..8974fc4 100644 --- a/package.json +++ b/package.json @@ -21,23 +21,23 @@ "dependencies": { "chalk": "^4.0.0", "cheerio": "^1.0.0-rc.2", - "finalhandler": "^1.1.2", + "express": "^4.17.1", "gaxios": "^4.0.0", "jsonexport": "^3.0.0", + "marked": "^1.2.5", "meow": "^8.0.0", "p-queue": "^6.2.1", - "serve-static": "^1.14.1", "server-destroy": "^1.0.1", "update-notifier": "^5.0.0" }, "devDependencies": { "@types/chai": "^4.2.7", "@types/cheerio": "^0.22.10", - "@types/finalhandler": "^1.1.0", + "@types/express": "^4.17.9", + "@types/marked": "^1.2.0", "@types/meow": "^5.0.0", "@types/mocha": "^8.0.0", "@types/node": "^12.7.12", - "@types/serve-static": "^1.13.3", "@types/server-destroy": "^1.0.0", "@types/sinon": "^9.0.0", "@types/update-notifier": "^5.0.0", diff --git a/src/cli.ts b/src/cli.ts index 50e2fad..ff37ab8 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -22,7 +22,7 @@ const cli = meow( Positional arguments LOCATION - Required. Either the URL or the path on disk to check for broken links. + Required. Either the URLs or the paths on disk to check for broken links. Flags --config @@ -46,6 +46,9 @@ const cli = meow( --timeout Request timeout in ms. Defaults to 0 (no timeout). + --markdown + Automatically parse and scan markdown if scanning from a location on disk. + --help Show this command. @@ -65,6 +68,7 @@ const cli = meow( format: {type: 'string', alias: 'f'}, silent: {type: 'boolean'}, timeout: {type: 'number'}, + markdown: {type: 'boolean'}, }, booleanDefault: undefined, } @@ -115,6 +119,7 @@ async function main() { path: cli.input[0], recurse: flags.recurse, timeout: Number(flags.timeout), + markdown: flags.markdown, concurrency: Number(flags.concurrency), }; if (flags.skip) { diff --git a/src/config.ts b/src/config.ts index ad380d2..65972c6 100644 --- a/src/config.ts +++ b/src/config.ts @@ -11,6 +11,7 @@ export interface Flags { format?: string; silent?: boolean; timeout?: number; + markdown?: boolean; } export async function getConfig(flags: Flags) { diff --git a/src/index.ts b/src/index.ts index 3fafc1a..39f014b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,14 +2,19 @@ import {EventEmitter} from 'events'; import * as gaxios from 'gaxios'; import * as http from 'http'; import enableDestroy = require('server-destroy'); +import * as express from 'express'; +import * as fs from 'fs'; +import * as util from 'util'; +import * as path from 'path'; +import * as marked from 'marked'; import PQueue, {DefaultAddOptions} from 'p-queue'; import {getLinks} from './links'; import {URL} from 'url'; import PriorityQueue from 'p-queue/dist/priority-queue'; -import finalhandler = require('finalhandler'); -import serveStatic = require('serve-static'); +const stat = util.promisify(fs.stat); +const readFile = util.promisify(fs.readFile); export interface CheckOptions { concurrency?: number; @@ -17,6 +22,7 @@ export interface CheckOptions { path: string; recurse?: boolean; timeout?: number; + markdown?: boolean; linksToSkip?: string[] | ((link: string) => Promise); } @@ -59,12 +65,27 @@ export class LinkChecker extends EventEmitter { */ async check(options: CheckOptions) { options.linksToSkip = options.linksToSkip || []; + options.path = path.normalize(options.path); let server: http.Server | undefined; if (!options.path.startsWith('http')) { + let localDirectory = options.path; + let localFile = ''; + const s = await stat(options.path); + if (s.isFile()) { + const pathParts = options.path.split(path.sep); + localFile = path.sep + pathParts[pathParts.length - 1]; + localDirectory = pathParts + .slice(0, pathParts.length - 1) + .join(path.sep); + } const port = options.port || 5000 + Math.round(Math.random() * 1000); - server = await this.startWebServer(options.path, port); + server = await this.startWebServer( + localDirectory, + port, + options.markdown + ); enableDestroy(server); - options.path = `http://localhost:${port}`; + options.path = `http://localhost:${port}${localFile}`; } const queue = new PQueue({ @@ -101,19 +122,35 @@ export class LinkChecker extends EventEmitter { * Spin up a local HTTP server to serve static requests from disk * @param root The local path that should be mounted as a static web server * @param port The port on which to start the local web server + * @param markdown If markdown should be automatically compiled and served * @private * @returns Promise that resolves with the instance of the HTTP server */ - private startWebServer(root: string, port: number): Promise { - return new Promise((resolve, reject) => { - const serve = serveStatic(root); - const server = http - .createServer((req, res) => - serve(req, res, finalhandler(req, res) as () => void) - ) - .listen(port, () => resolve(server)) - .on('error', reject); + private async startWebServer(root: string, port: number, markdown?: boolean) { + const app = express() + .use(async (req, res, next) => { + if (!markdown) { + return next(); + } + const pathParts = req.path.split('/').filter(x => !!x); + if (pathParts.length === 0) { + return next(); + } + const ext = path.extname(pathParts[pathParts.length - 1]); + if (ext.toLowerCase() === '.md') { + const filePath = path.join(path.resolve(root), req.path); + const data = await readFile(filePath, {encoding: 'utf-8'}); + const result = marked(data, {gfm: true}); + res.send(result).end(); + return; + } + return next(); + }) + .use(express.static(path.resolve(root))); + const server = await new Promise(resolve => { + const s = app.listen(port, () => resolve(s)); }); + return server; } /** diff --git a/test/fixtures/markdown/LICENSE.md b/test/fixtures/markdown/LICENSE.md new file mode 100644 index 0000000..3336d55 --- /dev/null +++ b/test/fixtures/markdown/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Justin Beckwith (jbeckwith.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/test/fixtures/markdown/README.md b/test/fixtures/markdown/README.md new file mode 100644 index 0000000..0d0f573 --- /dev/null +++ b/test/fixtures/markdown/README.md @@ -0,0 +1,6 @@ +# Say hello to my README +This has [a link](LICENSE.md) to something. + +Also here is my cat. +![booboobadkitteh](boo.jpg) + diff --git a/test/fixtures/markdown/boo.jpg b/test/fixtures/markdown/boo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..50c194356c0b9ed4314b139a84ef569b4e76cbbf GIT binary patch literal 855685 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

h#jRZ-4Jw*`ObLN=lE4#o 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