nuxt-link-checker
Advanced tools
Comparing version 3.0.2 to 3.1.0
@@ -1,1 +0,1 @@ | ||
{"id":"7bcc0ccc-7abc-4c7a-a38f-10c82bc8da01","timestamp":1721143475429} | ||
{"id":"3af16b43-4f9f-439e-a21d-22a114ed006f","timestamp":1721923131671} |
import * as _nuxt_schema from '@nuxt/schema'; | ||
import { DefaultInspections } from '../dist/runtime/pure/inspect.js'; | ||
import { CreateStorageOptions } from 'unstorage'; | ||
@@ -12,3 +12,3 @@ interface ModuleOptions { | ||
*/ | ||
skipInspections: (Partial<keyof typeof DefaultInspections>)[]; | ||
skipInspections: string[]; | ||
/** | ||
@@ -36,2 +36,14 @@ * The timeout for fetching a URL. | ||
markdown?: boolean; | ||
/** | ||
* Whether to output a JSON report. | ||
*/ | ||
json?: boolean; | ||
/** | ||
* Where to store the files. | ||
* | ||
* Either provide a path relative to the nuxt root or an object with options for `unstorage`. | ||
* | ||
* By default, they'll be in your .output directory. | ||
*/ | ||
storage?: string | CreateStorageOptions; | ||
}; | ||
@@ -38,0 +50,0 @@ /** |
@@ -8,3 +8,3 @@ { | ||
"configKey": "linkChecker", | ||
"version": "3.0.1", | ||
"version": "3.0.2", | ||
"builder": { | ||
@@ -11,0 +11,0 @@ "@nuxt/module-builder": "0.8.1", |
export declare function serverQueryContent(): { | ||
findOne(): Promise<boolean>; | ||
find(): Promise<never[]>; | ||
}; |
@@ -5,4 +5,7 @@ export function serverQueryContent() { | ||
return false; | ||
}, | ||
async find() { | ||
return []; | ||
} | ||
}; | ||
} |
@@ -1,2 +0,2 @@ | ||
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<Partial<import("../../../types").LinkInspectionResult>[]>>; | ||
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>; | ||
export default _default; |
@@ -5,12 +5,13 @@ import { defineEventHandler, getHeader, readBody } from "h3"; | ||
import { resolve } from "pathe"; | ||
import { inspect } from "../../../pure/inspect.js"; | ||
import { generateFileLinkDiff, generateFileLinkPreviews, lruFsCache } from "../../../pure/diff.js"; | ||
import { getLinkResponse } from "../../../pure/crawl.js"; | ||
import { isNonFetchableLink } from "../../../pure/inspections/util.js"; | ||
import { isInternalRoute } from "../../util.js"; | ||
import { useNitroApp, useNitroOrigin, useRuntimeConfig, useSiteConfig } from "#imports"; | ||
import Fuse from "fuse.js"; | ||
import { generateFileLinkDiff, generateFileLinkPreviews, getLinkResponse, inspect, isNonFetchableLink, lruFsCache } from "#link-checker/pure"; | ||
import { useNitroOrigin, useRuntimeConfig, useSiteConfig } from "#imports"; | ||
import { serverQueryContent } from "#content/server"; | ||
function isInternalRoute(path) { | ||
const lastSegment = path.split("/").pop() || path; | ||
return lastSegment.includes(".") || path.startsWith("/__") || path.startsWith("@"); | ||
} | ||
export default defineEventHandler(async (e) => { | ||
const { tasks, ids } = await readBody(e); | ||
const runtimeConfig = useRuntimeConfig().public["nuxt-link-checker"]; | ||
const runtimeConfig = useRuntimeConfig().public["nuxt-link-checker"] || {}; | ||
const partialCtx = { | ||
@@ -28,3 +29,3 @@ ids, | ||
lruFsCache.clear(); | ||
const pageSearch = useNitroApp()._linkCheckerPageSearch; | ||
const links = await $fetch("/__link-checker__/links"); | ||
return Promise.all( | ||
@@ -47,3 +48,6 @@ tasks.map(async ({ link, paths, textContent }) => { | ||
textContent, | ||
pageSearch, | ||
pageSearch: new Fuse(links, { | ||
keys: ["path", "title"], | ||
threshold: 0.5 | ||
}), | ||
response, | ||
@@ -50,0 +54,0 @@ skipInspections: runtimeConfig.skipInspections |
@@ -1,2 +0,2 @@ | ||
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any[]>>; | ||
declare const _default: any; | ||
export default _default; |
@@ -1,6 +0,20 @@ | ||
import { defineEventHandler } from "h3"; | ||
import { useRuntimeConfig } from "#imports"; | ||
import { createDefu } from "defu"; | ||
import { defineCachedEventHandler, useRuntimeConfig } from "#imports"; | ||
import pagePaths from "#nuxt-link-checker-sitemap/pages.mjs"; | ||
export default defineEventHandler(async () => { | ||
const runtimeConfig = useRuntimeConfig().public["nuxt-link-checker"]; | ||
import { serverQueryContent } from "#content/server"; | ||
const merger = createDefu((obj, key, value) => { | ||
if (Array.isArray(obj[key]) && Array.isArray(value)) | ||
obj[key] = Array.from(/* @__PURE__ */ new Set([...obj[key], ...value])); | ||
return obj[key]; | ||
}); | ||
function mergeOnKey(arr, key) { | ||
const res = {}; | ||
arr.forEach((item) => { | ||
const k = item[key]; | ||
res[k] = merger(item, res[k] || {}); | ||
}); | ||
return Object.values(res); | ||
} | ||
export default defineCachedEventHandler(async (e) => { | ||
const runtimeConfig = useRuntimeConfig().public["nuxt-link-checker"] || {}; | ||
const linkDb = [ | ||
@@ -12,5 +26,17 @@ ...pagePaths | ||
const entries = sitemapDebug.globalSources.map((source) => source.urls).flat(); | ||
linkDb.push(...entries.map((s) => s.loc)); | ||
linkDb.push(...entries.map((s) => ({ path: s.loc, title: "" }))); | ||
} | ||
return [.../* @__PURE__ */ new Set([...linkDb])]; | ||
if (serverQueryContent) { | ||
const contentDocuments = await serverQueryContent(e).find(); | ||
if (contentDocuments) { | ||
linkDb.push(...contentDocuments.map((doc) => ({ | ||
path: doc._path, | ||
title: doc.title | ||
}))); | ||
} | ||
} | ||
return mergeOnKey(linkDb, "link"); | ||
}, { | ||
maxAge: 10 | ||
// avoid thrashing | ||
}); |
@@ -1,2 +0,2 @@ | ||
import { defineNuxtPlugin } from "#imports"; | ||
import { defineNuxtPlugin, useRoute } from "#imports"; | ||
export default defineNuxtPlugin((nuxt) => { | ||
@@ -16,4 +16,6 @@ if (typeof document === "undefined" || typeof window === "undefined") | ||
} | ||
const route = useRoute(); | ||
import("./view/client.js").then(({ setupLinkCheckerClient }) => { | ||
setupLinkCheckerClient({ | ||
route, | ||
nuxt | ||
@@ -20,0 +22,0 @@ }); |
import type { NuxtApp } from 'nuxt/app'; | ||
export declare function setupLinkCheckerClient({ nuxt }: { | ||
import type { useRoute } from '#imports'; | ||
export declare function setupLinkCheckerClient({ nuxt, route }: { | ||
nuxt: NuxtApp; | ||
route: ReturnType<typeof useRoute>; | ||
}): Promise<void>; |
@@ -6,5 +6,8 @@ import { computed, createApp, h, ref, shallowReactive, unref } from "vue"; | ||
import { linkDb } from "./state.js"; | ||
import { useRoute, useRuntimeConfig } from "#imports"; | ||
import { useRuntimeConfig } from "#imports"; | ||
function resolveDevtoolsIframe() { | ||
return document.querySelector("#nuxt-devtools-iframe")?.contentWindow?.__NUXT_DEVTOOLS__; | ||
const iframe = document.querySelector("#nuxt-devtools-iframe"); | ||
if (!iframe) | ||
return; | ||
return iframe?.contentWindow?.__NUXT_DEVTOOLS__; | ||
} | ||
@@ -24,3 +27,3 @@ function resolvePathsForEl(el) { | ||
} | ||
export async function setupLinkCheckerClient({ nuxt }) { | ||
export async function setupLinkCheckerClient({ nuxt, route }) { | ||
let queue = []; | ||
@@ -35,7 +38,6 @@ let queueWorkerTimer; | ||
let isOpeningDevtools = false; | ||
const route = useRoute(); | ||
let startQueueIdleId; | ||
let startQueueTimeoutId; | ||
const showInspections = useLocalStorage("nuxt-link-checker:show-inspections", true); | ||
const runtimeConfig = useRuntimeConfig().public["nuxt-link-checker"]; | ||
const runtimeConfig = useRuntimeConfig().public["nuxt-link-checker"] || {}; | ||
const filter = createFilter({ | ||
@@ -156,6 +158,3 @@ exclude: runtimeConfig.excludeLinks | ||
} else { | ||
if (typeof devtoolsClient.host.open === "function") | ||
devtoolsClient.host.open(); | ||
else | ||
devtoolsClient.host.devtools.open(); | ||
devtoolsClient.host.devtools.open(); | ||
const srcPath = new URL(devtoolsClient.host.getIframe().src).pathname; | ||
@@ -182,3 +181,3 @@ if (!srcPath.startsWith("/__nuxt_devtools__/client/modules/custom-nuxt-link-checker")) { | ||
if (!startQueueTimeoutId) { | ||
startQueueTimeoutId = setTimeout(() => { | ||
startQueueTimeoutId = window.setTimeout(() => { | ||
client.scanLinks(); | ||
@@ -185,0 +184,0 @@ client.startQueueWorker(); |
import type { LinkInspectionResult, Rule, RuleTestContext } from '../types.js'; | ||
export declare const DefaultInspections: { | ||
readonly 'missing-hash': Rule; | ||
readonly 'no-error-response': Rule; | ||
readonly 'no-baseless': Rule; | ||
readonly 'no-javascript': Rule; | ||
readonly 'trailing-slash': Rule; | ||
readonly 'absolute-site-urls': Rule; | ||
readonly redirects: Rule; | ||
readonly 'link-text': Rule; | ||
}; | ||
export declare function inspect(ctx: RuleTestContext, rules?: { | ||
readonly 'missing-hash': Rule; | ||
readonly 'no-error-response': Rule; | ||
readonly 'no-baseless': Rule; | ||
readonly 'no-javascript': Rule; | ||
readonly 'trailing-slash': Rule; | ||
readonly 'absolute-site-urls': Rule; | ||
readonly redirects: Rule; | ||
readonly 'link-text': Rule; | ||
}): Partial<LinkInspectionResult>; | ||
export declare const AllInspections: Rule[]; | ||
export declare function inspect(ctx: Pick<Required<RuleTestContext>, 'link'> & Omit<Partial<RuleTestContext>, 'link'>, rules?: Rule[]): Partial<LinkInspectionResult>; |
@@ -11,13 +11,14 @@ import { parseURL } from "ufo"; | ||
import { isNonFetchableLink } from "./inspections/util.js"; | ||
export const DefaultInspections = { | ||
"missing-hash": RuleMissingHash(), | ||
"no-error-response": RuleNoErrorResponse(), | ||
"no-baseless": RuleNoBaseLess(), | ||
"no-javascript": RuleNoJavascript(), | ||
"trailing-slash": RuleTrailingSlash(), | ||
"absolute-site-urls": RuleAbsoluteSiteUrls(), | ||
"redirects": RuleRedirects(), | ||
"link-text": RuleDescriptiveLinkText() | ||
}; | ||
export function inspect(ctx, rules = DefaultInspections) { | ||
export const AllInspections = [ | ||
RuleMissingHash(), | ||
RuleNoErrorResponse(), | ||
RuleNoBaseLess(), | ||
RuleNoJavascript(), | ||
RuleTrailingSlash(), | ||
RuleAbsoluteSiteUrls(), | ||
RuleRedirects(), | ||
RuleDescriptiveLinkText() | ||
]; | ||
export function inspect(ctx, rules) { | ||
rules = rules || AllInspections; | ||
const res = { error: [], warning: [], fix: ctx.link, link: ctx.link }; | ||
@@ -24,0 +25,0 @@ let link = ctx.link; |
@@ -1,9 +0,8 @@ | ||
import { parseURL } from "ufo"; | ||
import { defineRule } from "./util.js"; | ||
export default function RuleAbsoluteSiteUrls() { | ||
return defineRule({ | ||
test({ report, link, siteConfig }) { | ||
id: "absolute-site-urls", | ||
test({ report, link, url, siteConfig }) { | ||
if (!link.startsWith(siteConfig.url)) | ||
return; | ||
const $url = parseURL(link); | ||
report({ | ||
@@ -14,3 +13,3 @@ name: "absolute-site-urls", | ||
tip: "Using internal links that start with / is recommended to avoid issues when deploying your site to different domain names", | ||
fix: $url.pathname, | ||
fix: url.pathname, | ||
fixDescription: `Remove ${siteConfig.url}.` | ||
@@ -17,0 +16,0 @@ }); |
import { defineRule } from "./util.js"; | ||
export default function RuleDescriptiveLinkText() { | ||
return defineRule({ | ||
id: "link-text", | ||
test({ textContent, report }) { | ||
@@ -5,0 +6,0 @@ if (typeof textContent === "undefined") |
@@ -6,2 +6,3 @@ import Fuse from "fuse.js"; | ||
return defineRule({ | ||
id: "missing-hash", | ||
test({ link, report, ids, fromPath }) { | ||
@@ -8,0 +9,0 @@ const [path, hash] = link.split("#"); |
@@ -5,2 +5,3 @@ import { joinURL } from "ufo"; | ||
return defineRule({ | ||
id: "no-baseless", | ||
test({ link, fromPath, report }) { | ||
@@ -7,0 +8,0 @@ if (link.startsWith("/") || link.startsWith("http") || isNonFetchableLink(link)) |
import { defineRule, isNonFetchableLink } from "./util.js"; | ||
export default function RuleNoErrorResponse() { | ||
return defineRule({ | ||
id: "no-error-response", | ||
test({ link, response, report, pageSearch }) { | ||
@@ -13,6 +14,6 @@ if (!response.status || response.status.toString().startsWith("2") || response.status.toString().startsWith("3") || isNonFetchableLink(link)) | ||
if (link.startsWith("/") && pageSearch) { | ||
const fix = pageSearch.search(link)?.[0]?.item; | ||
if (fix && fix !== link) { | ||
payload.fix = fix; | ||
payload.fixDescription = `Did you mean ${fix}?`; | ||
const related = pageSearch.search(link)?.[0]?.item; | ||
if (related?.path && related.path !== link) { | ||
payload.fix = related.path; | ||
payload.fixDescription = `Did you mean ${related.path}?`; | ||
} | ||
@@ -19,0 +20,0 @@ } else { |
import { defineRule } from "./util.js"; | ||
export default function RuleNoJavascript() { | ||
return defineRule({ | ||
id: "no-javascript", | ||
test({ link, report }) { | ||
@@ -5,0 +6,0 @@ if (link.startsWith("javascript:")) { |
@@ -6,2 +6,3 @@ import { fixSlashes } from "site-config-stack/urls"; | ||
return defineRule({ | ||
id: "trailing-slash", | ||
test({ report, link, siteConfig }) { | ||
@@ -8,0 +9,0 @@ if (!link.startsWith("/") && !link.startsWith(siteConfig.url)) |
import { defineRule } from "./inspections/util.js"; | ||
export default function RuleRedirects() { | ||
return defineRule({ | ||
id: "redirects", | ||
test({ report, response }) { | ||
@@ -5,0 +6,0 @@ if (response.status !== 301 && response.status !== 302) |
@@ -1,2 +0,1 @@ | ||
import type { FetchResponse } from 'ofetch'; | ||
import type { SiteConfigResolved } from 'site-config-stack'; | ||
@@ -7,2 +6,3 @@ import type Fuse from 'fuse.js'; | ||
export interface Rule { | ||
id: string; | ||
test: (ctx: RuleTestContext) => void; | ||
@@ -16,5 +16,8 @@ } | ||
fromPath: string; | ||
response: FetchResponse<any>; | ||
response: any; | ||
siteConfig: SiteConfigResolved; | ||
pageSearch?: Fuse<string>; | ||
pageSearch?: Fuse<{ | ||
path: string; | ||
title: string; | ||
}>; | ||
report: (report: RuleReport) => void; | ||
@@ -21,0 +24,0 @@ skipInspections?: string[]; |
{ | ||
"name": "nuxt-link-checker", | ||
"type": "module", | ||
"version": "3.0.2", | ||
"version": "3.1.0", | ||
"description": "Find and magically fix links that may be negatively effecting your Nuxt sites SEO.", | ||
@@ -31,3 +31,5 @@ "author": { | ||
"ofetch", | ||
"std-env" | ||
"std-env", | ||
"unstorage", | ||
"unstorage/drivers/fs" | ||
] | ||
@@ -42,4 +44,4 @@ }, | ||
"@nuxt/devtools-kit": "^1.3.9", | ||
"@nuxt/kit": "^3.12.3", | ||
"@vueuse/core": "^11.0.0-beta.1", | ||
"@nuxt/kit": "^3.12.4", | ||
"@vueuse/core": "^11.0.0-beta.2", | ||
"chalk": "^5.3.0", | ||
@@ -57,13 +59,13 @@ "cheerio": "1.0.0-rc.12", | ||
"site-config-stack": "^2.2.15", | ||
"ufo": "^1.5.3" | ||
"ufo": "^1.5.4" | ||
}, | ||
"devDependencies": { | ||
"@antfu/eslint-config": "2.22.4", | ||
"@nuxt/content": "^2.13.1", | ||
"@antfu/eslint-config": "^2.23.2", | ||
"@nuxt/content": "^2.13.2", | ||
"@nuxt/devtools": "^1.3.9", | ||
"@nuxt/module-builder": "0.8.1", | ||
"@nuxt/test-utils": "^3.13.1", | ||
"@nuxt/ui": "^2.17.0", | ||
"@nuxt/ui": "^2.18.1", | ||
"@nuxtjs/eslint-config-typescript": "^12.1.0", | ||
"@nuxtjs/sitemap": "^5.3.4", | ||
"@nuxtjs/sitemap": "^5.3.5", | ||
"@types/diff": "^5.2.1", | ||
@@ -73,11 +75,14 @@ "bumpp": "^9.4.1", | ||
"execa": "^9.3.0", | ||
"nuxt": "^3.12.3", | ||
"vitest": "^2.0.3" | ||
"nuxt": "^3.12.4", | ||
"vitest": "^2.0.4" | ||
}, | ||
"resolutions": { | ||
"nuxt-link-checker": "workspace:*" | ||
}, | ||
"scripts": { | ||
"build": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxt-module-build build && npm run client:build", | ||
"client:build": "nuxi generate client", | ||
"client:dev": "nuxi dev client --port 3300", | ||
"client:dev": "nuxi dev client --port 3030", | ||
"dev": "npm run play:dev", | ||
"dev:prepare": "nuxt-module-build build --stub && nuxi prepare client", | ||
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare client && nuxi prepare playground", | ||
"play:dev": "nuxi dev playground", | ||
@@ -88,4 +93,5 @@ "play:prod": "npm run build && nuxi dev playground", | ||
"test": "vitest run", | ||
"test:types": "npx nuxi typecheck", | ||
"test:watch": "vitest watch" | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
8747934
49462
326
Updated@nuxt/kit@^3.12.4
Updated@vueuse/core@^11.0.0-beta.2
Updatedufo@^1.5.4