nuxt-link-checker 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,121 @@
1
+ <h1 align='center'>nuxt-link-checker</h1>
2
+
3
+ <p align="center">
4
+ <a href='https://github.com/harlan-zw/nuxt-link-checker/actions/workflows/test.yml'>
5
+ </a>
6
+ <a href="https://www.npmjs.com/package/nuxt-link-checker" target="__blank"><img src="https://img.shields.io/npm/v/nuxt-link-checker?style=flat&colorA=002438&colorB=28CF8D" alt="NPM version"></a>
7
+ <a href="https://www.npmjs.com/package/nuxt-link-checker" target="__blank"><img alt="NPM Downloads" src="https://img.shields.io/npm/dm/nuxt-link-checker?flat&colorA=002438&colorB=28CF8D"></a>
8
+ <a href="https://github.com/harlan-zw/nuxt-link-checker" target="__blank"><img alt="GitHub stars" src="https://img.shields.io/github/stars/harlan-zw/nuxt-link-checker?flat&colorA=002438&colorB=28CF8D"></a>
9
+ </p>
10
+
11
+
12
+ <p align="center">
13
+ Improve your sites SEO by identifying and fixing link issues in your Nuxt v3 app.
14
+ </p>
15
+
16
+ <p align="center">
17
+ <table>
18
+ <tbody>
19
+ <td align="center">
20
+ <img width="800" height="0" /><br>
21
+ <i>Status:</i> Early Access</b> <br>
22
+ <sup> Please report any issues 🐛</sup><br>
23
+ <sub>Made possible by my <a href="https://github.com/sponsors/harlan-zw">Sponsor Program 💖</a><br> Follow me <a href="https://twitter.com/harlan_zw">@harlan_zw</a> 🐦 • Join <a href="https://discord.gg/275MBUBvgP">Discord</a> for help</sub><br>
24
+ <img width="800" height="0" />
25
+ </td>
26
+ </tbody>
27
+ </table>
28
+ </p>
29
+
30
+ ## Features
31
+
32
+ - ⛰️ Discover broken links - 404s and internal redirects
33
+ - ❌ Fail on build if broken links are found (optional)
34
+
35
+ ## Install
36
+
37
+ ⚠️ The module is in early access and only works when pre-rendering.
38
+
39
+ ```bash
40
+ npm install --save-dev nuxt-link-checker
41
+
42
+ # Using yarn
43
+ yarn add --dev nuxt-link-checker
44
+ ```
45
+
46
+ ## Setup
47
+
48
+ _nuxt.config.ts_
49
+
50
+ ```ts
51
+ export default defineNuxtConfig({
52
+ modules: [
53
+ 'nuxt-link-checker',
54
+ ],
55
+ })
56
+ ```
57
+
58
+ To have routes scanned for broken links automatically, they need to be pre-rendered by Nitro.
59
+
60
+ ```ts
61
+ export default defineNuxtConfig({
62
+ nitro: {
63
+ prerender: {
64
+ crawlLinks: true,
65
+ routes: [
66
+ '/',
67
+ // any URLs that can't be discovered by crawler
68
+ '/my-hidden-url'
69
+ ]
70
+ }
71
+ }
72
+ })
73
+ ```
74
+
75
+
76
+ ### Set host (optional)
77
+
78
+ You'll need to provide the host of your site so that the crawler can resolve absolute URLs that may be internal.
79
+
80
+ ```ts
81
+ export default defineNuxtConfig({
82
+ // Recommended
83
+ runtimeConfig: {
84
+ siteUrl: 'https://example.com',
85
+ },
86
+ // OR
87
+ sitemap: {
88
+ host: 'https://example.com',
89
+ },
90
+ })
91
+ ```
92
+
93
+ ## Module Config
94
+
95
+ ### `host`
96
+
97
+ - Type: `string`
98
+ - Default: `runtimeConfig.public.siteUrl`
99
+ - Required: `false`
100
+
101
+ The host of your site. This is required to validate absolute URLs which may be internal.
102
+
103
+ ### `trailingSlash`
104
+
105
+ - Type: `boolean`
106
+ - Default: `false`
107
+
108
+ Whether internal links should have a trailing slash or not.
109
+
110
+ ## Sponsors
111
+
112
+ <p align="center">
113
+ <a href="https://raw.githubusercontent.com/harlan-zw/static/main/sponsors.svg">
114
+ <img src='https://raw.githubusercontent.com/harlan-zw/static/main/sponsors.svg'/>
115
+ </a>
116
+ </p>
117
+
118
+
119
+ ## License
120
+
121
+ MIT License © 2022-PRESENT [Harlan Wilton](https://github.com/harlan-zw)
@@ -0,0 +1,5 @@
1
+ module.exports = function(...args) {
2
+ return import('./module.mjs').then(m => m.default.call(this, ...args))
3
+ }
4
+ const _meta = module.exports.meta = require('./module.json')
5
+ module.exports.getMeta = () => Promise.resolve(_meta)
@@ -0,0 +1,23 @@
1
+ import * as _nuxt_schema from '@nuxt/schema';
2
+
3
+ interface ModuleOptions {
4
+ /**
5
+ * Should the URLs be inserted with a trailing slash.
6
+ *
7
+ * @default false
8
+ */
9
+ trailingSlash: boolean;
10
+ /**
11
+ * Your site hostname. Used to determine if absolute links are internal.
12
+ */
13
+ host?: string;
14
+ /**
15
+ * Whether the build should fail when a 404 is encountered.
16
+ */
17
+ failOn404: boolean;
18
+ }
19
+ interface ModuleHooks {
20
+ }
21
+ declare const _default: _nuxt_schema.NuxtModule<ModuleOptions>;
22
+
23
+ export { ModuleHooks, ModuleOptions, _default as default };
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "nuxt-link-checker",
3
+ "compatibility": {
4
+ "nuxt": "^3.0.0",
5
+ "bridge": false
6
+ },
7
+ "configKey": "linkChecker",
8
+ "version": "0.1.0"
9
+ }
@@ -0,0 +1,117 @@
1
+ import { defineNuxtModule } from '@nuxt/kit';
2
+ import chalk from 'chalk';
3
+ import { isRelative, parseURL } from 'ufo';
4
+ import { load } from 'cheerio';
5
+
6
+ const linkMap = {};
7
+ const EXT_REGEX = /\.[\da-z]+$/;
8
+ const allowedExtensions = /* @__PURE__ */ new Set(["", ".json"]);
9
+ function getExtension(path) {
10
+ return (path.match(EXT_REGEX) || [])[0] || "";
11
+ }
12
+ function extractLinks(html, from, { host, trailingSlash }) {
13
+ const links = [];
14
+ const _links = [];
15
+ const $ = load(html);
16
+ $("[href]").each((i, el) => {
17
+ const href = $(el).attr("href");
18
+ if (!href)
19
+ return;
20
+ if (host && !isRelative(href) && href.startsWith(host))
21
+ return;
22
+ if (!allowedExtensions.has(getExtension(href)))
23
+ return;
24
+ _links.push({
25
+ href,
26
+ badTrailingSlash: href !== "/" && (trailingSlash && !href.endsWith("/") || !trailingSlash && href.endsWith("/")),
27
+ element: $.html(el) || ""
28
+ });
29
+ });
30
+ for (const link of _links.filter(Boolean)) {
31
+ const parsed = parseURL(link.href);
32
+ if (parsed.protocol)
33
+ continue;
34
+ let { pathname } = parsed;
35
+ if (!pathname.startsWith("/")) {
36
+ const fromURL = new URL(from, "http://localhost");
37
+ pathname = new URL(pathname, fromURL).pathname;
38
+ }
39
+ links.push({
40
+ ...link,
41
+ href: pathname
42
+ });
43
+ }
44
+ return links;
45
+ }
46
+
47
+ const invalidStatusCodes = [404, 302, 301, 307, 303];
48
+ const module = defineNuxtModule({
49
+ meta: {
50
+ name: "nuxt-link-checker",
51
+ compatibility: {
52
+ nuxt: "^3.0.0",
53
+ bridge: false
54
+ },
55
+ configKey: "linkChecker"
56
+ },
57
+ defaults(nuxt) {
58
+ return {
59
+ host: nuxt.options.runtimeConfig.public?.siteUrl,
60
+ trailingSlash: nuxt.options.runtimeConfig.public?.trailingSlash || false,
61
+ failOn404: true
62
+ };
63
+ },
64
+ setup(config, nuxt) {
65
+ nuxt.hooks.hook("nitro:init", async (nitro) => {
66
+ const invalidRoutes = {};
67
+ nitro.hooks.hook("prerender:generate", async (ctx) => {
68
+ if (ctx.contents && ctx.fileName?.endsWith(".html"))
69
+ linkMap[ctx.route] = extractLinks(ctx.contents, ctx.route, config);
70
+ if (ctx.error?.statusCode && invalidStatusCodes.includes(Number(ctx.error?.statusCode)))
71
+ invalidRoutes[ctx.route] = ctx.error.statusCode;
72
+ });
73
+ nitro.hooks.hook("close", async () => {
74
+ nitro.logger.info("Scanning for broken links...");
75
+ const links = Object.entries(linkMap);
76
+ const hasBrokenLinks = Object.keys(invalidRoutes).length;
77
+ let routeCount = 0;
78
+ let badLinkCount = 0;
79
+ links.forEach(([route, routes]) => {
80
+ const brokenLinks = routes.map((r) => {
81
+ return {
82
+ ...r,
83
+ statusCode: invalidRoutes[r.href] || 200
84
+ };
85
+ }).filter((r) => r.statusCode !== 200 || r.badTrailingSlash);
86
+ if (brokenLinks.length) {
87
+ nitro.logger.log(chalk.gray(
88
+ ` ${Number(++routeCount) === links.length - 1 ? "\u2514\u2500" : "\u251C\u2500"} ${route}`
89
+ ));
90
+ brokenLinks.forEach((link) => {
91
+ badLinkCount++;
92
+ if (link.statusCode !== 200) {
93
+ nitro.logger.log(chalk.red(
94
+ ` ${link.href} ${link.statusCode}`
95
+ ));
96
+ } else if (link.badTrailingSlash) {
97
+ nitro.logger.log(chalk.yellow(
98
+ ` ${link.href} Wrong trailing slash`
99
+ ));
100
+ }
101
+ nitro.logger.log(` ${chalk.gray(link.element)}`);
102
+ });
103
+ }
104
+ });
105
+ if (hasBrokenLinks) {
106
+ nitro.logger[config.failOn404 ? "error" : "warn"](`Found ${badLinkCount} broken links.`);
107
+ if (config.failOn404) {
108
+ nitro.logger.log(chalk.gray('You can disable this by setting "linkChecker: { failOn404: false }" in your nuxt.config.ts.'));
109
+ process.exit(1);
110
+ }
111
+ }
112
+ });
113
+ });
114
+ }
115
+ });
116
+
117
+ export { module as default };
@@ -0,0 +1,11 @@
1
+
2
+ import { ModuleOptions, ModuleHooks } from './module'
3
+
4
+ declare module '@nuxt/schema' {
5
+ interface NuxtConfig { ['linkChecker']?: Partial<ModuleOptions> }
6
+ interface NuxtOptions { ['linkChecker']?: ModuleOptions }
7
+ interface NuxtHooks extends ModuleHooks {}
8
+ }
9
+
10
+
11
+ export { ModuleHooks, ModuleOptions, default } from './module'
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "nuxt-link-checker",
3
+ "type": "module",
4
+ "version": "0.1.0",
5
+ "packageManager": "pnpm@7.18.0",
6
+ "license": "MIT",
7
+ "funding": "https://github.com/sponsors/harlan-zw",
8
+ "homepage": "https://github.com/harlan-zw/nuxt-link-checker#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/harlan-zw/nuxt-link-checker.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/harlan-zw/nuxt-link-checker/issues"
15
+ },
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/types.d.ts",
19
+ "require": "./dist/module.cjs",
20
+ "import": "./dist/module.mjs"
21
+ }
22
+ },
23
+ "main": "./dist/module.cjs",
24
+ "types": "./dist/types.d.ts",
25
+ "files": [
26
+ "dist"
27
+ ],
28
+ "dependencies": {
29
+ "@nuxt/kit": "3.0.0",
30
+ "chalk": "^5.2.0",
31
+ "cheerio": "1.0.0-rc.12",
32
+ "defu": "^6.1.1",
33
+ "radix3": "^1.0.0",
34
+ "sitemap": "^7.1.1",
35
+ "ufo": "^1.0.1"
36
+ },
37
+ "devDependencies": {
38
+ "@antfu/eslint-config": "^0.34.0",
39
+ "@nuxt/kit": "3.0.0",
40
+ "@nuxt/module-builder": "^0.2.1",
41
+ "@nuxt/test-utils": "3.0.0",
42
+ "@nuxtjs/eslint-config-typescript": "^12.0.0",
43
+ "bumpp": "^8.2.1",
44
+ "eslint": "8.30.0",
45
+ "execa": "^6.1.0",
46
+ "nuxt": "^3.0.0",
47
+ "pathe": "^1.0.0",
48
+ "vitest": "^0.25.8"
49
+ },
50
+ "scripts": {
51
+ "lint": "eslint \"**/*.{ts,vue,json,yml}\" --fix",
52
+ "build": "nuxi prepare .playground && nuxt-module-build",
53
+ "dev": "nuxi dev .playground",
54
+ "dev:build": "nuxi build .playground",
55
+ "dev:prepare": "nuxt-module-build --stub && nuxi prepare .playground",
56
+ "release": "bumpp package.json --commit --push --tag",
57
+ "test": "pnpm lint"
58
+ }
59
+ }