Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

linkinator

Package Overview
Dependencies
Maintainers
0
Versions
114
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

linkinator - npm Package Compare versions

Comparing version 6.0.6 to 6.0.7

13

build/src/cli.js
#!/usr/bin/env node
import process from 'node:process';
import chalk from 'chalk';
import meow from 'meow';
import chalk from 'chalk';
import { getConfig } from './config.js';
import { Format, Logger, LogLevel } from './logger.js';
import { LinkChecker, LinkState, } from './index.js';
import { Format, LogLevel, Logger } from './logger.js';
const cli = meow(`

@@ -131,3 +131,3 @@ Usage

case LinkState.BROKEN: {
state = `[${chalk.red(link.status.toString())}]`;
state = `[${chalk.red(link.status?.toString())}]`;
logger.error(`${state} ${chalk.gray(link.url)}`);

@@ -137,3 +137,3 @@ break;

case LinkState.OK: {
state = `[${chalk.green(link.status.toString())}]`;
state = `[${chalk.green(link.status?.toString())}]`;
logger.warn(`${state} ${chalk.gray(link.url)}`);

@@ -212,3 +212,2 @@ break;

// }
// eslint-disable-next-line unicorn/no-array-reduce
const parents = result.links.reduce((accumulator, current) => {

@@ -245,3 +244,3 @@ const parent = current.parent || '';

case LinkState.BROKEN: {
state = `[${chalk.red(link.status.toString())}]`;
state = `[${chalk.red(link.status?.toString())}]`;
logger.error(` ${state} ${chalk.gray(link.url)}`);

@@ -252,3 +251,3 @@ logger.debug(JSON.stringify(link.failureDetails, null, 2));

case LinkState.OK: {
state = `[${chalk.green(link.status.toString())}]`;
state = `[${chalk.green(link.status?.toString())}]`;
logger.warn(` ${state} ${chalk.gray(link.url)}`);

@@ -255,0 +254,0 @@ break;

import { promises as fs } from 'node:fs';
import path from 'node:path';
import process from 'node:process';
import path from 'node:path';
const validConfigExtensions = ['.js', '.mjs', '.cjs', '.json'];
export async function getConfig(flags) {
// Check to see if a config file path was passed
const configPath = flags.config || 'linkinator.config.json';
let config = {};
let config;
if (flags.config) {
config = await parseConfigFile(configPath);
config = await parseConfigFile(flags.config);
}
else {
config = (await tryGetDefaultConfig()) || {};
}
// `meow` is set up to pass boolean flags as `undefined` if not passed.

@@ -17,3 +20,2 @@ // copy the struct, and delete properties that are `undefined` so the merge

if (value === undefined || (Array.isArray(value) && value.length === 0)) {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete strippedFlags[key];

@@ -27,3 +29,17 @@ }

}
const validConfigExtensions = ['.js', '.mjs', '.cjs', '.json'];
/**
* Attempt to load `linkinator.config.json`, assuming the user hasn't
* passed a specific path to a config.
* @returns The contents of the default config if present, or an empty config.
*/
async function tryGetDefaultConfig() {
const defaultConfigPath = path.join(process.cwd(), 'linkinator.config.json');
try {
const config = await parseConfigFile(defaultConfigPath);
return config;
}
catch (e) {
return {};
}
}
async function parseConfigFile(configPath) {

@@ -57,3 +73,2 @@ const typeOfConfig = getTypeOfConfig(configPath);

// whoever is reading the code.
// eslint-disable-next-line no-new-func
const _import = new Function('p', 'return import(p)');

@@ -60,0 +75,0 @@ const config = (await _import(`file://${path.resolve(process.cwd(), configPath)}`));

@@ -1,7 +0,5 @@

/// <reference types="node" resolution-mode="require"/>
import { EventEmitter } from 'node:events';
import { type GaxiosResponse } from 'gaxios';
import { type CheckOptions } from './options.js';
import { Queue } from './queue.js';
import { type CheckOptions } from './options.js';
export { getConfig } from './config.js';
export declare enum LinkState {

@@ -8,0 +6,0 @@ OK = "OK",

@@ -5,7 +5,6 @@ import { EventEmitter } from 'node:events';

import { request } from 'gaxios';
import { getLinks } from './links.js';
import { processOptions, } from './options.js';
import { Queue } from './queue.js';
import { getLinks } from './links.js';
import { startWebServer } from './server.js';
import { processOptions, } from './options.js';
export { getConfig } from './config.js';
export var LinkState;

@@ -25,2 +24,3 @@ (function (LinkState) {

export class LinkChecker extends EventEmitter {
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
on(event, listener) {

@@ -45,3 +45,3 @@ return super.on(event, listener);

server = await startWebServer({
root: options.serverRoot,
root: options.serverRoot ?? '',
port,

@@ -166,2 +166,5 @@ markdown: options.markdown,

const timeout = options.delayCache.get(options.url.host);
if (timeout === undefined) {
throw new Error('timeout not found');
}
if (timeout > Date.now()) {

@@ -216,3 +219,4 @@ options.queue.add(async () => {

try {
// Some sites don't respond to a stream response type correctly, especially with a HEAD. Try a GET with a text response type
// Some sites don't respond well to HEAD requests, even if they don't return a 405.
// This is a last gasp effort to see if the link is valid.
if ((response === undefined ||

@@ -252,3 +256,3 @@ response.status < 200 ||

}
else {
else if (response !== undefined) {
failures.push(response);

@@ -302,5 +306,8 @@ }

options.queue.add(async () => {
if (result.url === undefined) {
throw new Error('url is undefined');
}
await this.crawl({
url: result.url,
crawl,
crawl: crawl ?? false,
cache: options.cache,

@@ -350,5 +357,5 @@ delayCache: options.delayCache,

// Check to see if there is already a request to wait for this host
if (options.delayCache.has(options.url.host)) {
const currentTimeout = options.delayCache.get(options.url.host);
if (currentTimeout !== undefined) {
// Use whichever time is higher in the cache
const currentTimeout = options.delayCache.get(options.url.host);
if (retryAfter > currentTimeout) {

@@ -390,5 +397,6 @@ options.delayCache.set(options.url.host, retryAfter);

let currentRetries = 1;
if (options.retryErrorsCache.has(options.url.href)) {
const cachedRetries = options.retryErrorsCache.get(options.url.href);
if (cachedRetries !== undefined) {
// Use whichever time is higher in the cache
currentRetries = options.retryErrorsCache.get(options.url.href);
currentRetries = cachedRetries;
if (currentRetries > maxRetries)

@@ -395,0 +403,0 @@ return false;

@@ -1,3 +0,2 @@

/// <reference types="node" resolution-mode="require"/>
import { type Readable } from 'node:stream';
import type { Readable } from 'node:stream';
export type ParsedUrl = {

@@ -4,0 +3,0 @@ link: string;

@@ -49,3 +49,2 @@ import { WritableStream } from 'htmlparser2/lib/WritableStream';

// ignore href properties for link tags where rel is likely to fail
// eslint-disable-next-line unicorn/prevent-abbreviations
const relValuesToIgnore = ['dns-prefetch', 'preconnect'];

@@ -59,3 +58,2 @@ if (tag === 'link' && relValuesToIgnore.includes(attributes.rel)) {

try {
// eslint-disable-next-line no-new
new URL(attributes.content);

@@ -62,0 +60,0 @@ }

@@ -52,3 +52,2 @@ import { promises as fs } from 'node:fs';

fullPath = fullPath.split(path.sep).join('/');
// eslint-disable-next-line no-await-in-loop
const expandedPaths = await glob(fullPath);

@@ -99,3 +98,3 @@ if (expandedPaths.length === 0) {

const pathParts = options.path[0].split(path.sep);
options.path = [path.join('.', pathParts.at(-1))];
options.path = [path.join('.', pathParts.at(-1) ?? '')];
options.serverRoot = pathParts.slice(0, -1).join(path.sep) || '.';

@@ -102,0 +101,0 @@ }

@@ -1,2 +0,1 @@

/// <reference types="node" resolution-mode="require"/>
import { EventEmitter } from 'node:events';

@@ -3,0 +2,0 @@ export type QueueOptions = {

@@ -18,2 +18,3 @@ import { EventEmitter } from 'node:events';

}
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
on(event, listener) {

@@ -46,3 +47,2 @@ return super.on(event, listener);

}
// eslint-disable-next-line @typescript-eslint/prefer-for-of
for (let i = 0; i < this.q.length; i++) {

@@ -55,2 +55,5 @@ // Check if we have too many concurrent functions executing

const item = this.q.shift();
if (item === undefined) {
throw new Error('unexpected undefined item in queue');
}
// Make sure this element is ready to execute - if not, to the back of the stack

@@ -60,3 +63,2 @@ if (item.timeToRun <= Date.now()) {

this.activeFunctions++;
// eslint-disable-next-line @typescript-eslint/no-floating-promises
item.fn().finally(() => {

@@ -63,0 +65,0 @@ this.activeFunctions--;

@@ -1,2 +0,1 @@

/// <reference types="node" resolution-mode="require"/>
import http from 'node:http';

@@ -3,0 +2,0 @@ export type WebServerOptions = {

@@ -0,8 +1,8 @@

import { Buffer } from 'node:buffer';
import { promises as fs } from 'node:fs';
import http from 'node:http';
import path from 'node:path';
import { Buffer } from 'node:buffer';
import { promises as fs } from 'node:fs';
import escapeHtml from 'escape-html';
import { marked } from 'marked';
import mime from 'mime';
import escape from 'escape-html';
import enableDestroy from 'server-destroy';

@@ -35,3 +35,2 @@ /**

.filter(Boolean)
// eslint-disable-next-line unicorn/no-array-callback-reference
.map(decodeURIComponent);

@@ -58,3 +57,3 @@ const originalPath = path.join(root, ...pathParts);

response.setHeader('Content-Length', Buffer.byteLength(document));
response.setHeader('Location', request.url + '/');
response.setHeader('Location', `${request.url}/`);
response.end(document);

@@ -86,3 +85,3 @@ return;

}
response.setHeader('Content-Type', mimeType);
response.setHeader('Content-Type', mimeType || '');
response.setHeader('Content-Length', Buffer.byteLength(data));

@@ -97,3 +96,3 @@ response.writeHead(200);

const fileList = files
.filter((f) => escape(f))
.filter((f) => escapeHtml(f))
.map((f) => `<li><a href="${f}">${f}</a></li>`)

@@ -106,3 +105,2 @@ .join('\r\n');

catch (error_) {
// eslint-disable-next-line @typescript-eslint/naming-convention
const error__ = error_;

@@ -109,0 +107,0 @@ return404(response, error__);

{
"name": "linkinator",
"description": "Find broken links, missing images, etc in your HTML. Scurry around your site and find all those broken links.",
"version": "6.0.6",
"license": "MIT",
"repository": "JustinBeckwith/linkinator",
"author": "Justin Beckwith",
"exports": "./build/src/index.js",
"types": "./build/src/index.d.ts",
"type": "module",
"bin": {
"linkinator": "./build/src/cli.js"
},
"scripts": {
"pretest": "npm run compile",
"prepare": "npm run compile",
"coverage": "c8 report --reporter=json",
"compile": "tsc -p .",
"test": "c8 mocha build/test",
"fix": "xo --prettier --fix",
"lint": "xo --prettier",
"build-binaries": "pkg . --out-path build/binaries",
"docs-test": "node build/src/cli.js ./README.md"
},
"dependencies": {
"chalk": "^5.0.0",
"escape-html": "^1.0.3",
"gaxios": "^6.0.0",
"glob": "^10.3.10",
"htmlparser2": "^9.0.0",
"marked": "^13.0.0",
"meow": "^13.0.0",
"mime": "^4.0.0",
"server-destroy": "^1.0.1",
"srcset": "^5.0.0"
},
"devDependencies": {
"@types/escape-html": "^1.0.1",
"@types/mocha": "^10.0.0",
"@types/node": "^20.0.0",
"@types/server-destroy": "^1.0.1",
"@types/sinon": "^17.0.0",
"c8": "^10.0.0",
"execa": "^9.0.0",
"mocha": "^10.0.0",
"nock": "^13.2.1",
"pkg": "^5.4.1",
"semantic-release": "^24.0.0",
"sinon": "^18.0.0",
"strip-ansi": "^7.0.1",
"typescript": "^5.0.0",
"xo": "^0.58.0"
},
"engines": {
"node": ">=18"
},
"files": [
"build/src"
],
"keywords": [
"404",
"html",
"hyperlink",
"links",
"seo",
"url",
"broken link checker",
"broken",
"link",
"checker"
],
"xo": {
"rules": {
"unicorn/prefer-event-target": "off",
"complexity": "off",
"@typescript-eslint/prefer-nullish-coalescing": "off"
},
"ignores": [
"test/fixtures"
]
}
"name": "linkinator",
"description": "Find broken links, missing images, etc in your HTML. Scurry around your site and find all those broken links.",
"version": "6.0.7",
"license": "MIT",
"repository": "JustinBeckwith/linkinator",
"author": "Justin Beckwith",
"exports": "./build/src/index.js",
"types": "./build/src/index.d.ts",
"type": "module",
"bin": {
"linkinator": "./build/src/cli.js"
},
"scripts": {
"pretest": "npm run build",
"prepare": "npm run build",
"coverage": "c8 report --reporter=json",
"build": "tsc -p .",
"test": "c8 mocha build/test",
"fix": "biome check --write .",
"lint": "biome check .",
"build-binaries": "pkg . --out-path build/binaries",
"docs-test": "node build/src/cli.js ./README.md"
},
"dependencies": {
"chalk": "^5.0.0",
"escape-html": "^1.0.3",
"gaxios": "^6.0.0",
"glob": "^10.3.10",
"htmlparser2": "^9.0.0",
"marked": "^13.0.0",
"meow": "^13.0.0",
"mime": "^4.0.0",
"server-destroy": "^1.0.1",
"srcset": "^5.0.0"
},
"devDependencies": {
"@biomejs/biome": "1.8.3",
"@types/escape-html": "^1.0.1",
"@types/mocha": "^10.0.0",
"@types/node": "^20.0.0",
"@types/server-destroy": "^1.0.1",
"@types/sinon": "^17.0.0",
"c8": "^10.0.0",
"execa": "^9.0.0",
"mocha": "^10.0.0",
"nock": "^13.2.1",
"pkg": "^5.4.1",
"semantic-release": "^24.0.0",
"sinon": "^18.0.0",
"strip-ansi": "^7.0.1",
"typescript": "^5.5.2"
},
"engines": {
"node": ">=18"
},
"files": [
"build/src"
],
"keywords": [
"404",
"html",
"hyperlink",
"links",
"seo",
"url",
"broken link checker",
"broken",
"link",
"checker"
]
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc