linkinator
Advanced tools
Comparing version 2.4.0 to 2.5.0
@@ -79,3 +79,3 @@ #!/usr/bin/env node | ||
async function main() { | ||
if (cli.input.length !== 1) { | ||
if (cli.input.length < 1) { | ||
cli.showHelp(); | ||
@@ -116,3 +116,3 @@ return; | ||
const opts = { | ||
path: cli.input[0], | ||
path: cli.input, | ||
recurse: flags.recurse, | ||
@@ -119,0 +119,0 @@ timeout: Number(flags.timeout), |
@@ -9,3 +9,3 @@ /// <reference types="node" /> | ||
port?: number; | ||
path: string; | ||
path: string | string[]; | ||
recurse?: boolean; | ||
@@ -40,2 +40,3 @@ timeout?: number; | ||
queue: PQueue<PriorityQueue, DefaultAddOptions>; | ||
rootPath: string; | ||
} | ||
@@ -51,3 +52,3 @@ /** | ||
*/ | ||
check(options: CheckOptions): Promise<{ | ||
check(opts: CheckOptions): Promise<{ | ||
links: LinkResult[]; | ||
@@ -60,10 +61,4 @@ passed: boolean; | ||
*/ | ||
private validateOptions; | ||
private processOptions; | ||
/** | ||
* Figure out which directory should be used as the root for the web server, | ||
* and how that impacts the path to the file for the first request. | ||
* @param options CheckOptions passed in from the CLI or API | ||
*/ | ||
private getServerRoot; | ||
/** | ||
* Spin up a local HTTP server to serve static requests from disk | ||
@@ -70,0 +65,0 @@ * @param root The local path that should be mounted as a static web server |
@@ -32,14 +32,24 @@ "use strict"; | ||
*/ | ||
async check(options) { | ||
this.validateOptions(options); | ||
async check(opts) { | ||
const options = await this.processOptions(opts); | ||
if (!Array.isArray(options.path)) { | ||
options.path = [options.path]; | ||
} | ||
options.linksToSkip = options.linksToSkip || []; | ||
options.path = path.normalize(options.path); | ||
let server; | ||
if (!options.path.startsWith('http')) { | ||
const serverOptions = await this.getServerRoot(options); | ||
const hasHttpPaths = options.path.find(x => x.startsWith('http')); | ||
if (!hasHttpPaths) { | ||
const port = options.port || 5000 + Math.round(Math.random() * 1000); | ||
server = await this.startWebServer(serverOptions.serverRoot, port, options.markdown); | ||
server = await this.startWebServer(options.serverRoot, port, options.markdown); | ||
enableDestroy(server); | ||
options.path = `http://localhost:${port}${serverOptions.path}`; | ||
for (let i = 0; i < options.path.length; i++) { | ||
if (options.path[i].startsWith('/')) { | ||
options.path[i] = options.path[i].slice(1); | ||
} | ||
options.path[i] = `http://localhost:${port}/${options.path[i]}`; | ||
} | ||
} | ||
if (process.env.LINKINATOR_DEBUG) { | ||
console.log(options); | ||
} | ||
const queue = new p_queue_1.default({ | ||
@@ -49,15 +59,18 @@ concurrency: options.concurrency || 100, | ||
const results = new Array(); | ||
const url = new url_1.URL(options.path); | ||
const initCache = new Set(); | ||
initCache.add(url.href); | ||
queue.add(async () => { | ||
await this.crawl({ | ||
url: new url_1.URL(options.path), | ||
crawl: true, | ||
checkOptions: options, | ||
results, | ||
cache: initCache, | ||
queue, | ||
for (const path of options.path) { | ||
const url = new url_1.URL(path); | ||
initCache.add(url.href); | ||
queue.add(async () => { | ||
await this.crawl({ | ||
url, | ||
crawl: true, | ||
checkOptions: options, | ||
results, | ||
cache: initCache, | ||
queue, | ||
rootPath: path, | ||
}); | ||
}); | ||
}); | ||
} | ||
await queue.onIdle(); | ||
@@ -77,34 +90,54 @@ const result = { | ||
*/ | ||
validateOptions(options) { | ||
if (options.serverRoot && options.path.startsWith('http')) { | ||
async processOptions(opts) { | ||
const options = Object.assign({}, opts); | ||
// ensure at least one path is provided | ||
if (options.path.length === 0) { | ||
throw new Error('At least one path must be provided'); | ||
} | ||
// normalize options.path to an array of strings | ||
if (!Array.isArray(options.path)) { | ||
options.path = [options.path]; | ||
} | ||
// Ensure we do not mix http:// and file system paths. The paths passed in | ||
// must all be filesystem paths, or HTTP paths. | ||
let isUrlType = undefined; | ||
for (const path of options.path) { | ||
const innerIsUrlType = path.startsWith('http'); | ||
if (isUrlType === undefined) { | ||
isUrlType = innerIsUrlType; | ||
} | ||
else if (innerIsUrlType !== isUrlType) { | ||
throw new Error('Paths cannot be mixed between HTTP and local filesystem paths.'); | ||
} | ||
} | ||
// if there is a server root, make sure there are no HTTP paths | ||
if (options.serverRoot && isUrlType) { | ||
throw new Error("'serverRoot' cannot be defined when the 'path' points to an HTTP endpoint."); | ||
} | ||
} | ||
/** | ||
* Figure out which directory should be used as the root for the web server, | ||
* and how that impacts the path to the file for the first request. | ||
* @param options CheckOptions passed in from the CLI or API | ||
*/ | ||
async getServerRoot(options) { | ||
if (options.serverRoot) { | ||
const filePath = options.path.startsWith('/') | ||
? options.path | ||
: '/' + options.path; | ||
return { | ||
serverRoot: options.serverRoot, | ||
path: filePath, | ||
}; | ||
// Figure out which directory should be used as the root for the web server, | ||
// and how that impacts the path to the file for the first request. | ||
if (!options.serverRoot && !isUrlType) { | ||
// if the serverRoot wasn't defined, and there are multiple paths, just | ||
// use process.cwd(). | ||
if (options.path.length > 1) { | ||
options.serverRoot = process.cwd(); | ||
} | ||
else { | ||
// if there's a single path, try to be smart and figure it out | ||
const s = await stat(options.path[0]); | ||
options.serverRoot = options.path[0]; | ||
if (s.isFile()) { | ||
const pathParts = options.path[0].split(path.sep); | ||
options.path = [path.sep + pathParts[pathParts.length - 1]]; | ||
options.serverRoot = pathParts | ||
.slice(0, pathParts.length - 1) | ||
.join(path.sep); | ||
} | ||
else { | ||
options.serverRoot = options.path[0]; | ||
options.path = '/'; | ||
} | ||
} | ||
} | ||
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); | ||
} | ||
return { | ||
serverRoot: localDirectory, | ||
path: localFile, | ||
}; | ||
return options; | ||
} | ||
@@ -126,5 +159,2 @@ /** | ||
const pathParts = req.path.split('/').filter(x => !!x); | ||
if (pathParts.length === 0) { | ||
return next(); | ||
} | ||
const ext = path.extname(pathParts[pathParts.length - 1]); | ||
@@ -153,2 +183,3 @@ if (ext.toLowerCase() === '.md') { | ||
async crawl(opts) { | ||
var _a; | ||
// explicitly skip non-http[s] links before making the request | ||
@@ -284,12 +315,10 @@ const proto = opts.url.protocol; | ||
} | ||
let crawl = (opts.checkOptions.recurse && | ||
result.url && | ||
result.url.href.startsWith(opts.checkOptions.path)); | ||
let crawl = (opts.checkOptions.recurse && ((_a = result.url) === null || _a === void 0 ? void 0 : _a.href.startsWith(opts.rootPath))); | ||
// only crawl links that start with the same host | ||
if (crawl) { | ||
try { | ||
const pathUrl = new url_1.URL(opts.checkOptions.path); | ||
const pathUrl = new url_1.URL(opts.rootPath); | ||
crawl = result.url.host === pathUrl.host; | ||
} | ||
catch (_a) { | ||
catch (_b) { | ||
// ignore errors | ||
@@ -311,2 +340,3 @@ } | ||
parent: opts.url.href, | ||
rootPath: opts.rootPath, | ||
}); | ||
@@ -313,0 +343,0 @@ }); |
{ | ||
"name": "linkinator", | ||
"description": "Find broken links, missing images, etc in your HTML. Scurry around your site and find all those broken links.", | ||
"version": "2.4.0", | ||
"version": "2.5.0", | ||
"license": "MIT", | ||
@@ -6,0 +6,0 @@ "repository": "JustinBeckwith/linkinator", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
59856
783
4