nuxt-link-checker 3.1.2 → 3.2.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.
Files changed (191) hide show
  1. package/LICENSE.md +9 -0
  2. package/README.md +26 -17
  3. package/dist/client/200.html +90 -85
  4. package/dist/client/404.html +90 -85
  5. package/dist/client/_nuxt/{CE0v8NN9.js → 0qbUsQ6_.js} +1 -1
  6. package/dist/client/_nuxt/2aygx1xg.js +1 -0
  7. package/dist/client/_nuxt/{DBxrVw_D.js → 5Vw9JATd.js} +1 -1
  8. package/dist/client/_nuxt/6RUVFG9U.js +1 -0
  9. package/dist/client/_nuxt/B1Z2YEe0.js +1 -0
  10. package/dist/client/_nuxt/{OiuHoWY5.js → B1rsb5QC.js} +1 -1
  11. package/dist/client/_nuxt/B2uSYIFn.js +1 -0
  12. package/dist/client/_nuxt/BA1ndEPy.js +1 -0
  13. package/dist/client/_nuxt/BCUAspaU.js +41 -0
  14. package/dist/client/_nuxt/{p-youoOz.js → BCawSwWi.js} +1 -1
  15. package/dist/client/_nuxt/{rAPcFIiw.js → BYqZ7etr.js} +1 -1
  16. package/dist/client/_nuxt/{BjE2mIY4.js → BbalPOsb.js} +1 -1
  17. package/dist/client/_nuxt/{DwFwdhDE.js → Bcw97oti.js} +1 -1
  18. package/dist/client/_nuxt/{aPBkmDv4.js → BfjtVDDH.js} +1 -1
  19. package/dist/client/_nuxt/{CiXlrwC7.js → BhC5s0V7.js} +1 -1
  20. package/dist/client/_nuxt/{BVLG6Y-w.js → Bmb0A568.js} +1 -1
  21. package/dist/client/_nuxt/{DpVQgTom.js → BoZcIFWd.js} +1 -1
  22. package/dist/client/_nuxt/BqtQrzRp.js +1 -0
  23. package/dist/client/_nuxt/BrbOHqWv.js +1 -0
  24. package/dist/client/_nuxt/{BWgb8Pxw.js → Bv_sPlCv.js} +1 -1
  25. package/dist/client/_nuxt/BwpQXVS2.js +1 -0
  26. package/dist/client/_nuxt/BzaEo2Du.js +1 -0
  27. package/dist/client/_nuxt/C-_shW-Y.js +1 -0
  28. package/dist/client/_nuxt/{nkNqggyT.js → CBzZD_Xh.js} +1 -1
  29. package/dist/client/_nuxt/CD_QflpE.js +1 -0
  30. package/dist/client/_nuxt/{Bpo8LMpD.js → CI15r38m.js} +1 -1
  31. package/dist/client/_nuxt/{JbneklYT.js → CK7S-5jS.js} +1 -1
  32. package/dist/client/_nuxt/{B9hcNGex.js → CKfqYE_r.js} +1 -1
  33. package/dist/client/_nuxt/CLXkKSjS.js +1 -0
  34. package/dist/client/_nuxt/CM4fc1WH.js +1 -0
  35. package/dist/client/_nuxt/CVESyXxo.js +1 -0
  36. package/dist/client/_nuxt/{vFtNXPBc.js → CWJejSyX.js} +1 -1
  37. package/dist/client/_nuxt/{Bt50SWsh.js → CY2R9RuK.js} +1 -1
  38. package/dist/client/_nuxt/{Ckz-YMxz.js → CeZwWGti.js} +1 -1
  39. package/dist/client/_nuxt/CjDtw9vr.js +1 -0
  40. package/dist/client/_nuxt/{BhLNiG9c.js → ClV7KpCc.js} +1 -1
  41. package/dist/client/_nuxt/{D7z58M2W.js → Cm25um3E.js} +1 -1
  42. package/dist/client/_nuxt/CmCqftbK.js +1 -0
  43. package/dist/client/_nuxt/Cmhojp3q.js +1 -0
  44. package/dist/client/_nuxt/{BwojPDJW.js → CnsmiicP.js} +1 -1
  45. package/dist/client/_nuxt/{DCRAZVCM.js → CodIOJDM.js} +1 -1
  46. package/dist/client/_nuxt/{0e7srHXh.js → Csvi84BS.js} +1 -1
  47. package/dist/client/_nuxt/{CHZeBJpW.js → CuIYtGag.js} +1 -1
  48. package/dist/client/_nuxt/{cVkK16_y.js → Cuk6v7N8.js} +1 -1
  49. package/dist/client/_nuxt/{I1uqSxRv.js → Cv3cJnDV.js} +1 -1
  50. package/dist/client/_nuxt/Cv7ZOjGq.js +1 -0
  51. package/dist/client/_nuxt/{Da_8ueAw.js → CyEc264g.js} +1 -1
  52. package/dist/client/_nuxt/{DTlJ9n0s.js → CyIlscnE.js} +1 -1
  53. package/dist/client/_nuxt/{Bx0W9Nc2.js → D0xWnrgR.js} +1 -1
  54. package/dist/client/_nuxt/{CRR3mLlZ.js → D2O4HAMr.js} +1 -1
  55. package/dist/client/_nuxt/{B7-uoU3n.js → D7oLnXFd.js} +1 -1
  56. package/dist/client/_nuxt/{Bexp5DGn.js → DFQ4Xr-4.js} +1 -1
  57. package/dist/client/_nuxt/{DLSxEyHP.js → DH5Ifo-i.js} +1 -1
  58. package/dist/client/_nuxt/DMFdBl0X.js +1 -0
  59. package/dist/client/_nuxt/DNgb01dh.js +1 -0
  60. package/dist/client/_nuxt/DRW-0cLl.js +1 -0
  61. package/dist/client/_nuxt/{Xse1gjxk.js → DRhUEtVu.js} +1 -1
  62. package/dist/client/_nuxt/{BGdveAQA.js → DUdzsfxe.js} +1 -1
  63. package/dist/client/_nuxt/{CYyBA3kJ.js → Db-JQHi3.js} +1 -1
  64. package/dist/client/_nuxt/DepYB8Ml.js +1 -0
  65. package/dist/client/_nuxt/{ChEme8PX.js → DhG9BCpW.js} +1 -1
  66. package/dist/client/_nuxt/{BxJadH_W.js → DhqJCFIX.js} +1 -1
  67. package/dist/client/_nuxt/DqpEK-75.js +1 -0
  68. package/dist/client/_nuxt/Ds-gbosJ.js +1 -0
  69. package/dist/client/_nuxt/{D4O64OTH.js → DvA-6Bhw.js} +1 -1
  70. package/dist/client/_nuxt/{CV1i-Amj.js → E3gJ1_iC.js} +1 -1
  71. package/dist/client/_nuxt/{B66eQrDP.js → FMvz1E70.js} +1 -1
  72. package/dist/client/_nuxt/GBQ2dnAY.js +1 -0
  73. package/dist/client/_nuxt/{CJrM-UL-.js → JJNjxVar.js} +1 -1
  74. package/dist/client/_nuxt/{D_QQB6Yv.js → KUYRHWK4.js} +1 -1
  75. package/dist/client/_nuxt/LGGdnPYs.js +1 -0
  76. package/dist/client/_nuxt/Lp17StYA.js +1 -0
  77. package/dist/client/_nuxt/RXHOmnpW.js +1 -0
  78. package/dist/client/_nuxt/{4NJYcRSJ.js → W2lx4xae.js} +1 -1
  79. package/dist/client/_nuxt/YurBl9Qv.js +1 -0
  80. package/dist/client/_nuxt/{ZBuUynGX.js → aDoct50J.js} +1 -1
  81. package/dist/client/_nuxt/builds/latest.json +1 -1
  82. package/dist/client/_nuxt/builds/meta/2259ca4d-d009-41be-acfd-fea8f1d19525.json +1 -0
  83. package/dist/client/_nuxt/{D0ybgVpa.js → dwbxrGt_.js} +1 -1
  84. package/dist/client/_nuxt/{D3CL8VSI.js → eACALr6E.js} +1 -1
  85. package/dist/client/_nuxt/entry.CzbW4W9L.css +1 -0
  86. package/dist/client/_nuxt/error-404.fx_5-qn-.css +1 -0
  87. package/dist/client/_nuxt/error-500.dWWWhQqD.css +1 -0
  88. package/dist/client/_nuxt/{B8E_5cfo.js → gOa3Yz9U.js} +1 -1
  89. package/dist/client/_nuxt/{BbzkbZ9e.js → i22b9gbM.js} +1 -1
  90. package/dist/client/_nuxt/{CE38B3OF.js → mDBYJdpO.js} +1 -1
  91. package/dist/client/_nuxt/{g_GSZ1hr.js → u4bm_LMz.js} +1 -1
  92. package/dist/client/_nuxt/{A13Lx_ye.js → vocl-2a6.js} +1 -1
  93. package/dist/client/_nuxt/{CN6rGqjK.js → xfgw03QH.js} +1 -1
  94. package/dist/client/_nuxt/zchoCBdH.js +1 -0
  95. package/dist/client/index.html +90 -85
  96. package/dist/module.json +1 -1
  97. package/dist/module.mjs +163 -159
  98. package/dist/runtime/{nuxt/plugin → app/plugins}/view/Main.vue +1 -1
  99. package/dist/runtime/{nuxt/plugin → app/plugins}/view/client.js +5 -4
  100. package/dist/runtime/{nitro → server}/routes/__link-checker__/inspect.js +4 -4
  101. package/dist/runtime/{pure → shared}/index.d.ts +3 -3
  102. package/dist/runtime/{pure → shared}/index.js +5 -5
  103. package/dist/runtime/{pure → shared}/inspect.js +28 -17
  104. package/dist/runtime/{pure/inspections/descriptive-link-text.js → shared/inspections/link-text.js} +2 -2
  105. package/dist/runtime/shared/inspections/no-baseless.d.ts +1 -0
  106. package/dist/runtime/{pure → shared}/inspections/no-baseless.js +3 -2
  107. package/dist/runtime/shared/inspections/no-double-slashes.d.ts +1 -0
  108. package/dist/runtime/shared/inspections/no-double-slashes.js +25 -0
  109. package/dist/runtime/shared/inspections/no-duplicate-query-params.d.ts +1 -0
  110. package/dist/runtime/shared/inspections/no-duplicate-query-params.js +28 -0
  111. package/dist/runtime/shared/inspections/no-missing-href.d.ts +1 -0
  112. package/dist/runtime/shared/inspections/no-missing-href.js +17 -0
  113. package/dist/runtime/shared/inspections/no-non-ascii-chars.d.ts +1 -0
  114. package/dist/runtime/shared/inspections/no-non-ascii-chars.js +18 -0
  115. package/dist/runtime/shared/inspections/no-underscores.d.ts +1 -0
  116. package/dist/runtime/shared/inspections/no-underscores.js +17 -0
  117. package/dist/runtime/shared/inspections/no-uppercase-chars.d.ts +1 -0
  118. package/dist/runtime/shared/inspections/no-uppercase-chars.js +17 -0
  119. package/dist/runtime/shared/inspections/no-whitespace.d.ts +1 -0
  120. package/dist/runtime/shared/inspections/no-whitespace.js +25 -0
  121. package/dist/runtime/types.d.ts +4 -3
  122. package/package.json +24 -24
  123. package/dist/client/_nuxt/35JD59R_.js +0 -1
  124. package/dist/client/_nuxt/9xzcv9xf.js +0 -1
  125. package/dist/client/_nuxt/B-GI89LT.js +0 -1
  126. package/dist/client/_nuxt/BGXXrhfN.js +0 -40
  127. package/dist/client/_nuxt/BStEqDGx.js +0 -1
  128. package/dist/client/_nuxt/BT8b6B_-.js +0 -1
  129. package/dist/client/_nuxt/BTo9ix6S.js +0 -1
  130. package/dist/client/_nuxt/BeSffZBD.js +0 -1
  131. package/dist/client/_nuxt/Begb3drz.js +0 -1
  132. package/dist/client/_nuxt/C3t61SNw.js +0 -1
  133. package/dist/client/_nuxt/C8UtRc_0.js +0 -1
  134. package/dist/client/_nuxt/COtMjVi7.js +0 -1
  135. package/dist/client/_nuxt/C_OO3DVV.js +0 -1
  136. package/dist/client/_nuxt/D5gt1ch3.js +0 -1
  137. package/dist/client/_nuxt/D6HQHXJE.js +0 -1
  138. package/dist/client/_nuxt/DIvA8ll9.js +0 -1
  139. package/dist/client/_nuxt/DQJjjPvk.js +0 -1
  140. package/dist/client/_nuxt/DWt6wkI-.js +0 -1
  141. package/dist/client/_nuxt/DanlQboj.js +0 -1
  142. package/dist/client/_nuxt/DateCQAz.js +0 -1
  143. package/dist/client/_nuxt/Dt1_zKdO.js +0 -1
  144. package/dist/client/_nuxt/Jwd9j1ay.js +0 -1
  145. package/dist/client/_nuxt/NfFGm2lL.js +0 -1
  146. package/dist/client/_nuxt/S_yhxdK1.js +0 -1
  147. package/dist/client/_nuxt/builds/meta/cd536571-8901-45d4-b5c2-149b2eb2ef0c.json +0 -1
  148. package/dist/client/_nuxt/eCoydtrh.js +0 -1
  149. package/dist/client/_nuxt/entry.BPVwhKGg.css +0 -1
  150. package/dist/client/_nuxt/error-404.CcgDkxt2.css +0 -1
  151. package/dist/client/_nuxt/error-500.CcBgumSp.css +0 -1
  152. package/dist/client/_nuxt/keK8bHwS.js +0 -1
  153. package/dist/runtime/pure/inspections/no-baseless.d.ts +0 -1
  154. package/dist/runtime/{nuxt/plugin → app/plugins}/ui.client.d.ts +0 -0
  155. package/dist/runtime/{nuxt/plugin → app/plugins}/ui.client.js +0 -0
  156. package/dist/runtime/{nuxt/plugin → app/plugins}/view/Squiggle.vue +0 -0
  157. package/dist/runtime/{nuxt/plugin → app/plugins}/view/client.d.ts +1 -1
  158. package/dist/runtime/{nuxt/plugin → app/plugins}/view/state.d.ts +0 -0
  159. package/dist/runtime/{nuxt/plugin → app/plugins}/view/state.js +0 -0
  160. package/dist/runtime/{nuxt/plugin → app/plugins}/view/utils.d.ts +0 -0
  161. package/dist/runtime/{nuxt/plugin → app/plugins}/view/utils.js +0 -0
  162. package/dist/runtime/{nitro → server}/composables/content-mock.d.ts +0 -0
  163. package/dist/runtime/{nitro → server}/composables/content-mock.js +0 -0
  164. package/dist/runtime/{nitro → server}/routes/__link-checker__/debug.d.ts +0 -0
  165. package/dist/runtime/{nitro → server}/routes/__link-checker__/debug.js +1 -1
  166. package/dist/runtime/{nitro → server}/routes/__link-checker__/inspect.d.ts +0 -0
  167. package/dist/runtime/{nitro → server}/routes/__link-checker__/links.d.ts +0 -0
  168. package/dist/runtime/{nitro → server}/routes/__link-checker__/links.js +1 -1
  169. /package/dist/runtime/{nitro → server}/tsconfig.json +0 -0
  170. /package/dist/runtime/{pure → shared}/crawl.d.ts +0 -0
  171. /package/dist/runtime/{pure → shared}/crawl.js +0 -0
  172. /package/dist/runtime/{pure → shared}/diff.d.ts +0 -0
  173. /package/dist/runtime/{pure → shared}/diff.js +0 -0
  174. /package/dist/runtime/{pure → shared}/inspect.d.ts +0 -0
  175. /package/dist/runtime/{pure → shared}/inspections/absolute-site-urls.d.ts +0 -0
  176. /package/dist/runtime/{pure → shared}/inspections/absolute-site-urls.js +0 -0
  177. /package/dist/runtime/{pure/inspections/descriptive-link-text.d.ts → shared/inspections/link-text.d.ts} +0 -0
  178. /package/dist/runtime/{pure → shared}/inspections/missing-hash.d.ts +0 -0
  179. /package/dist/runtime/{pure → shared}/inspections/missing-hash.js +0 -0
  180. /package/dist/runtime/{pure/inspections/no-error-response-status.d.ts → shared/inspections/no-error-response.d.ts} +0 -0
  181. /package/dist/runtime/{pure/inspections/no-error-response-status.js → shared/inspections/no-error-response.js} +0 -0
  182. /package/dist/runtime/{pure → shared}/inspections/no-javascript.d.ts +0 -0
  183. /package/dist/runtime/{pure → shared}/inspections/no-javascript.js +0 -0
  184. /package/dist/runtime/{pure → shared}/inspections/trailing-slash.d.ts +0 -0
  185. /package/dist/runtime/{pure → shared}/inspections/trailing-slash.js +0 -0
  186. /package/dist/runtime/{pure → shared}/inspections/util.d.ts +0 -0
  187. /package/dist/runtime/{pure → shared}/inspections/util.js +0 -0
  188. /package/dist/runtime/{pure → shared}/redirects.d.ts +0 -0
  189. /package/dist/runtime/{pure → shared}/redirects.js +0 -0
  190. /package/dist/runtime/{pure → shared}/sharedUtils.d.ts +0 -0
  191. /package/dist/runtime/{pure → shared}/sharedUtils.js +0 -0
package/dist/module.mjs CHANGED
@@ -2,32 +2,174 @@ import { useNuxt, extendPages, defineNuxtModule, createResolver, useLogger, addP
2
2
  import { useSiteConfig, installNuxtSiteConfig } from 'nuxt-site-config-kit';
3
3
  import { readPackageJSON } from 'pkg-types';
4
4
  import { existsSync } from 'node:fs';
5
- import chalk from 'chalk';
6
- import Fuse from 'fuse.js';
5
+ import { readFile, writeFile } from 'node:fs/promises';
6
+ import { onDevToolsInitialized, extendServerRpc } from '@nuxt/devtools-kit';
7
+ import { diffArrays } from 'diff';
7
8
  import { resolve, relative } from 'pathe';
9
+ import { generateLinkDiff } from '../dist/runtime/shared/diff.js';
10
+ import { joinURL, withoutLeadingSlash } from 'ufo';
11
+ import chalk from 'chalk';
8
12
  import { load } from 'cheerio';
9
- import { withoutLeadingSlash, joinURL } from 'ufo';
13
+ import Fuse from 'fuse.js';
10
14
  import { createStorage } from 'unstorage';
11
15
  import fsDriver from 'unstorage/drivers/fs';
12
- import { inspect } from '../dist/runtime/pure/inspect.js';
13
- import { createFilter } from '../dist/runtime/pure/sharedUtils.js';
14
- import { setLinkResponse, getLinkResponse, crawlFetch } from '../dist/runtime/pure/crawl.js';
15
- import { readFile, writeFile } from 'node:fs/promises';
16
- import { onDevToolsInitialized, extendServerRpc } from '@nuxt/devtools-kit';
17
- import { diffArrays } from 'diff';
18
- import { generateLinkDiff } from '../dist/runtime/pure/diff.js';
16
+ import { setLinkResponse, getLinkResponse, crawlFetch } from '../dist/runtime/shared/crawl.js';
17
+ import { inspect } from '../dist/runtime/shared/inspect.js';
18
+ import { createFilter } from '../dist/runtime/shared/sharedUtils.js';
19
+
20
+ function convertNuxtPagesToPaths(pages) {
21
+ return pages.map((page) => {
22
+ return page.children?.length ? page.children.map((child) => {
23
+ return {
24
+ path: joinURL(page.path, child.path),
25
+ page: child
26
+ };
27
+ }) : { page, path: page.path };
28
+ }).flat().filter((p) => !p.path.includes(":")).map((p) => ({
29
+ title: p.page?.meta?.title || "",
30
+ link: p.path
31
+ }));
32
+ }
33
+ function useViteWebSocket(nuxt = useNuxt()) {
34
+ return new Promise((_resolve) => {
35
+ nuxt.hooks.hook("vite:serverCreated", (viteServer) => {
36
+ _resolve(viteServer.ws);
37
+ });
38
+ });
39
+ }
40
+
41
+ const DEVTOOLS_UI_ROUTE = "/__nuxt-link-checker";
42
+ const DEVTOOLS_UI_LOCAL_PORT = 3030;
43
+ const RPC_NAMESPACE = "nuxt-link-checker-rpc";
44
+ function setupDevToolsUI(options, moduleResolve, nuxt = useNuxt()) {
45
+ const clientPath = moduleResolve("./client");
46
+ const isProductionBuild = existsSync(clientPath);
47
+ if (isProductionBuild) {
48
+ nuxt.hook("vite:serverCreated", async (server) => {
49
+ const sirv = await import('sirv').then((r) => r.default || r);
50
+ server.middlewares.use(
51
+ DEVTOOLS_UI_ROUTE,
52
+ sirv(clientPath, { dev: true, single: true })
53
+ );
54
+ });
55
+ } else {
56
+ nuxt.hook("vite:extendConfig", (config) => {
57
+ config.server = config.server || {};
58
+ config.server.proxy = config.server.proxy || {};
59
+ config.server.proxy[DEVTOOLS_UI_ROUTE] = {
60
+ target: `http://localhost:${DEVTOOLS_UI_LOCAL_PORT}${DEVTOOLS_UI_ROUTE}`,
61
+ changeOrigin: true,
62
+ followRedirects: true,
63
+ rewrite: (path) => path.replace(DEVTOOLS_UI_ROUTE, "")
64
+ };
65
+ });
66
+ }
67
+ nuxt.hook("devtools:customTabs", (tabs) => {
68
+ tabs.push({
69
+ // unique identifier
70
+ name: "nuxt-link-checker",
71
+ // title to display in the tab
72
+ title: "Link Checker",
73
+ // any icon from Iconify, or a URL to an image
74
+ icon: "carbon:cloud-satellite-link",
75
+ // iframe view
76
+ view: {
77
+ type: "iframe",
78
+ src: DEVTOOLS_UI_ROUTE
79
+ }
80
+ });
81
+ });
82
+ let isConnected = false;
83
+ const viteServerWs = useViteWebSocket();
84
+ const rpc = new Promise((promiseResolve) => {
85
+ onDevToolsInitialized(async () => {
86
+ const rpc2 = extendServerRpc(RPC_NAMESPACE, {
87
+ getOptions() {
88
+ return options;
89
+ },
90
+ async reset() {
91
+ const ws = await viteServerWs;
92
+ ws.send("nuxt-link-checker:reset");
93
+ },
94
+ async scrollToLink(link) {
95
+ const ws = await viteServerWs;
96
+ ws.send("nuxt-link-checker:scroll-to-link", link);
97
+ },
98
+ async applyLinkFixes(diff, original, replacement) {
99
+ for (const { filepath } of diff) {
100
+ const rootDirFolderName = nuxt.options.rootDir.split("/").pop();
101
+ const filepathWithoutRoot = filepath.replace(new RegExp(`^${nuxt.options.rootDir}/`), "").replace(new RegExp(`^${rootDirFolderName}/`), "");
102
+ const fp = resolve(nuxt.options.rootDir, filepathWithoutRoot);
103
+ const contents = await readFile(fp, "utf8");
104
+ const diff2 = generateLinkDiff(contents, original, replacement);
105
+ await writeFile(fp, diff2.code, "utf8");
106
+ }
107
+ const ws = await viteServerWs;
108
+ ws.send("nuxt-link-checker:reset");
109
+ },
110
+ async toggleLiveInspections(enabled) {
111
+ const ws = await viteServerWs;
112
+ ws.send("nuxt-link-checker:live-inspections", { enabled });
113
+ },
114
+ async connected() {
115
+ const ws = await viteServerWs;
116
+ ws.send("nuxt-link-checker:connected");
117
+ isConnected = true;
118
+ }
119
+ });
120
+ promiseResolve(rpc2);
121
+ });
122
+ });
123
+ viteServerWs.then((ws) => {
124
+ ws.on("nuxt-link-checker:queueWorking", async (payload) => {
125
+ if (isConnected) {
126
+ const _rpc = await rpc;
127
+ _rpc.broadcast.queueWorking(payload).catch(() => {
128
+ });
129
+ }
130
+ });
131
+ ws.on("nuxt-link-checker:updated", async () => {
132
+ if (isConnected) {
133
+ const _rpc = await rpc;
134
+ _rpc.broadcast.updated().catch(() => {
135
+ });
136
+ }
137
+ });
138
+ ws.on("nuxt-link-checker:filter", async (payload) => {
139
+ if (isConnected) {
140
+ const _rpc = await rpc;
141
+ _rpc.broadcast.filter(payload).catch(() => {
142
+ });
143
+ }
144
+ });
145
+ let lastRoutes = [];
146
+ extendPages(async (pages) => {
147
+ const routes = convertNuxtPagesToPaths(pages);
148
+ if (lastRoutes.length) {
149
+ const routeDiff = diffArrays(lastRoutes, routes);
150
+ if (routeDiff.some((diff) => diff.added || diff.removed))
151
+ ws.send("nuxt-link-checker:reset");
152
+ }
153
+ lastRoutes = routes;
154
+ });
155
+ });
156
+ }
19
157
 
20
158
  const linkMap = {};
21
- function extractPayload(html) {
159
+ function extractPayload(html, rootNodeId = "#__nuxt") {
160
+ if (String(rootNodeId).length) {
161
+ rootNodeId = rootNodeId[0] === "#" ? rootNodeId : `#${rootNodeId}`;
162
+ }
22
163
  const $ = load(html);
23
- const ids = $("#__nuxt [id]").map((i, el) => $(el).attr("id")).get();
164
+ const ids = $(`${rootNodeId} [id]`.trim()).map((i, el) => $(el).attr("id")).get();
24
165
  const title = $("title").text();
25
- const links = $("#__nuxt a[href]").map((i, el) => {
166
+ const links = $(`${rootNodeId} a`.trim()).map((i, el) => {
26
167
  return {
168
+ role: $(el).attr("role") || "",
27
169
  link: $(el).attr("href") || "",
28
170
  textContent: ($(el).attr("aria-label") || $(el).attr("title") || $(el).text()).trim() || ""
29
171
  };
30
- }).get().filter((l) => !!l.link);
172
+ }).get();
31
173
  return { title, ids, links };
32
174
  }
33
175
  function isNuxtGenerate(nuxt = useNuxt()) {
@@ -42,7 +184,7 @@ function prerender(config, nuxt = useNuxt()) {
42
184
  nitro.hooks.hook("prerender:generate", async (ctx) => {
43
185
  const route = decodeURI(ctx.route);
44
186
  if (ctx.contents && !ctx.error && ctx.fileName?.endsWith(".html") && !route.endsWith(".html") && urlFilter(route))
45
- linkMap[route] = extractPayload(ctx.contents);
187
+ linkMap[route] = extractPayload(ctx.contents, nuxt.options.app.rootAttrs?.id || "");
46
188
  setLinkResponse(route, Promise.resolve({ status: Number(ctx.error?.statusCode) || 200, statusText: ctx.error?.statusMessage || "", headers: {} }));
47
189
  });
48
190
  nitro.hooks.hook("prerender:done", async () => {
@@ -230,144 +372,6 @@ function prerender(config, nuxt = useNuxt()) {
230
372
  });
231
373
  }
232
374
 
233
- function convertNuxtPagesToPaths(pages) {
234
- return pages.map((page) => {
235
- return page.children?.length ? page.children.map((child) => {
236
- return {
237
- path: joinURL(page.path, child.path),
238
- page: child
239
- };
240
- }) : { page, path: page.path };
241
- }).flat().filter((p) => !p.path.includes(":")).map((p) => ({
242
- title: p.page?.meta?.title || "",
243
- link: p.path
244
- }));
245
- }
246
- function useViteWebSocket(nuxt = useNuxt()) {
247
- return new Promise((_resolve) => {
248
- nuxt.hooks.hook("vite:serverCreated", (viteServer) => {
249
- _resolve(viteServer.ws);
250
- });
251
- });
252
- }
253
-
254
- const DEVTOOLS_UI_ROUTE = "/__nuxt-link-checker";
255
- const DEVTOOLS_UI_LOCAL_PORT = 3030;
256
- const RPC_NAMESPACE = "nuxt-link-checker-rpc";
257
- function setupDevToolsUI(options, moduleResolve, nuxt = useNuxt()) {
258
- const clientPath = moduleResolve("./client");
259
- const isProductionBuild = existsSync(clientPath);
260
- if (isProductionBuild) {
261
- nuxt.hook("vite:serverCreated", async (server) => {
262
- const sirv = await import('sirv').then((r) => r.default || r);
263
- server.middlewares.use(
264
- DEVTOOLS_UI_ROUTE,
265
- sirv(clientPath, { dev: true, single: true })
266
- );
267
- });
268
- } else {
269
- nuxt.hook("vite:extendConfig", (config) => {
270
- config.server = config.server || {};
271
- config.server.proxy = config.server.proxy || {};
272
- config.server.proxy[DEVTOOLS_UI_ROUTE] = {
273
- target: `http://localhost:${DEVTOOLS_UI_LOCAL_PORT}${DEVTOOLS_UI_ROUTE}`,
274
- changeOrigin: true,
275
- followRedirects: true,
276
- rewrite: (path) => path.replace(DEVTOOLS_UI_ROUTE, "")
277
- };
278
- });
279
- }
280
- nuxt.hook("devtools:customTabs", (tabs) => {
281
- tabs.push({
282
- // unique identifier
283
- name: "nuxt-link-checker",
284
- // title to display in the tab
285
- title: "Link Checker",
286
- // any icon from Iconify, or a URL to an image
287
- icon: "carbon:cloud-satellite-link",
288
- // iframe view
289
- view: {
290
- type: "iframe",
291
- src: DEVTOOLS_UI_ROUTE
292
- }
293
- });
294
- });
295
- let isConnected = false;
296
- const viteServerWs = useViteWebSocket();
297
- const rpc = new Promise((promiseResolve) => {
298
- onDevToolsInitialized(async () => {
299
- const rpc2 = extendServerRpc(RPC_NAMESPACE, {
300
- getOptions() {
301
- return options;
302
- },
303
- async reset() {
304
- const ws = await viteServerWs;
305
- ws.send("nuxt-link-checker:reset");
306
- },
307
- async scrollToLink(link) {
308
- const ws = await viteServerWs;
309
- ws.send("nuxt-link-checker:scroll-to-link", link);
310
- },
311
- async applyLinkFixes(diff, original, replacement) {
312
- for (const { filepath } of diff) {
313
- const rootDirFolderName = nuxt.options.rootDir.split("/").pop();
314
- const filepathWithoutRoot = filepath.replace(new RegExp(`^${nuxt.options.rootDir}/`), "").replace(new RegExp(`^${rootDirFolderName}/`), "");
315
- const fp = resolve(nuxt.options.rootDir, filepathWithoutRoot);
316
- const contents = await readFile(fp, "utf8");
317
- const diff2 = generateLinkDiff(contents, original, replacement);
318
- await writeFile(fp, diff2.code, "utf8");
319
- }
320
- const ws = await viteServerWs;
321
- ws.send("nuxt-link-checker:reset");
322
- },
323
- async toggleLiveInspections(enabled) {
324
- const ws = await viteServerWs;
325
- ws.send("nuxt-link-checker:live-inspections", { enabled });
326
- },
327
- async connected() {
328
- const ws = await viteServerWs;
329
- ws.send("nuxt-link-checker:connected");
330
- isConnected = true;
331
- }
332
- });
333
- promiseResolve(rpc2);
334
- });
335
- });
336
- viteServerWs.then((ws) => {
337
- ws.on("nuxt-link-checker:queueWorking", async (payload) => {
338
- if (isConnected) {
339
- const _rpc = await rpc;
340
- _rpc.broadcast.queueWorking(payload).catch(() => {
341
- });
342
- }
343
- });
344
- ws.on("nuxt-link-checker:updated", async () => {
345
- if (isConnected) {
346
- const _rpc = await rpc;
347
- _rpc.broadcast.updated().catch(() => {
348
- });
349
- }
350
- });
351
- ws.on("nuxt-link-checker:filter", async (payload) => {
352
- if (isConnected) {
353
- const _rpc = await rpc;
354
- _rpc.broadcast.filter(payload).catch(() => {
355
- });
356
- }
357
- });
358
- let lastRoutes = [];
359
- extendPages(async (pages) => {
360
- const routes = convertNuxtPagesToPaths(pages);
361
- if (lastRoutes.length) {
362
- const routeDiff = diffArrays(lastRoutes, routes);
363
- if (routeDiff.some((diff) => diff.added || diff.removed))
364
- ws.send("nuxt-link-checker:reset");
365
- }
366
- lastRoutes = routes;
367
- });
368
- });
369
- }
370
-
371
375
  const module = defineNuxtModule({
372
376
  meta: {
373
377
  name: "nuxt-link-checker",
@@ -408,20 +412,20 @@ const module = defineNuxtModule({
408
412
  const isDevToolsEnabled = typeof nuxt.options.devtools === "boolean" ? nuxt.options.devtools : nuxt.options.devtools.enabled;
409
413
  if (nuxt.options.dev && isDevToolsEnabled) {
410
414
  addPlugin({
411
- src: resolve("./runtime/nuxt/plugin/ui.client"),
415
+ src: resolve("./runtime/app/plugins/ui.client"),
412
416
  mode: "client"
413
417
  });
414
418
  addServerHandler({
415
419
  route: "/__link-checker__/inspect",
416
- handler: resolve("./runtime/nitro/routes/__link-checker__/inspect")
420
+ handler: resolve("./runtime/server/routes/__link-checker__/inspect")
417
421
  });
418
422
  addServerHandler({
419
423
  route: "/__link-checker__/links",
420
- handler: resolve("./runtime/nitro/routes/__link-checker__/links")
424
+ handler: resolve("./runtime/server/routes/__link-checker__/links")
421
425
  });
422
426
  addServerHandler({
423
427
  route: "/__link-checker__/debug.json",
424
- handler: resolve("./runtime/nitro/routes/__link-checker__/debug")
428
+ handler: resolve("./runtime/server/routes/__link-checker__/debug")
425
429
  });
426
430
  const pagePromise = new Promise((_resolve) => {
427
431
  extendPages((pages) => {
@@ -437,9 +441,9 @@ const module = defineNuxtModule({
437
441
  const hasSitemapModule = (hasNuxtModule("@nuxtjs/sitemap") || hasNuxtModule("nuxt-simple-sitemap") && await hasNuxtModuleCompatibility("nuxt-simple-sitemap", ">=4")) && nuxt.options.sitemap?.enabled !== false;
438
442
  nuxt.options.nitro.alias = nuxt.options.nitro.alias || {};
439
443
  if (!hasNuxtModule("@nuxt/content")) {
440
- nuxt.options.nitro.alias["#content/server"] = resolve("./runtime/nitro/composables/content-mock");
444
+ nuxt.options.nitro.alias["#content/server"] = resolve("./runtime/server/composables/content-mock");
441
445
  }
442
- nuxt.options.nitro.alias["#link-checker/pure"] = resolve("./runtime/pure");
446
+ nuxt.options.nitro.alias["#link-checker/shared"] = resolve("./runtime/shared");
443
447
  nuxt.options.runtimeConfig.public["nuxt-link-checker"] = {
444
448
  version,
445
449
  hasSitemapModule,
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
- import { type Ref, computed, ref } from 'vue'
3
2
  import type { NuxtLinkCheckerClient } from '../../../types'
3
+ import { computed, type Ref, ref } from 'vue'
4
4
  import Squiggle from './Squiggle.vue'
5
5
  import { useEventListener } from './utils'
6
6
 
@@ -1,9 +1,9 @@
1
- import { computed, createApp, h, ref, shallowReactive, unref } from "vue";
1
+ import { useRuntimeConfig } from "#imports";
2
2
  import { useLocalStorage } from "@vueuse/core";
3
- import { createFilter } from "../../../pure/sharedUtils.js";
3
+ import { computed, createApp, h, ref, shallowReactive, unref } from "vue";
4
+ import { createFilter } from "../../../shared/sharedUtils.js";
4
5
  import Main from "./Main.vue";
5
6
  import { linkDb } from "./state.js";
6
- import { useRuntimeConfig } from "#imports";
7
7
  function resolveDevtoolsIframe() {
8
8
  const iframe = document.querySelector("#nuxt-devtools-iframe");
9
9
  if (!iframe)
@@ -47,7 +47,7 @@ export async function setupLinkCheckerClient({ nuxt, route }) {
47
47
  elMap = {};
48
48
  visibleLinks.clear();
49
49
  lastIds = [...new Set([...document.querySelectorAll("#__nuxt [id]")].map((el) => el.id))];
50
- [...document.querySelectorAll("#__nuxt a[href]")].map((el) => ({ el, link: el.getAttribute("href") })).forEach(({ el, link }) => {
50
+ [...document.querySelectorAll("#__nuxt a")].map((el) => ({ el, link: el.getAttribute("href") })).forEach(({ el, link }) => {
51
51
  if (!link)
52
52
  return;
53
53
  if (!filter(link))
@@ -65,6 +65,7 @@ export async function setupLinkCheckerClient({ nuxt, route }) {
65
65
  }
66
66
  queue.push({
67
67
  link,
68
+ role: el.getAttribute("role") || "",
68
69
  textContent: (el.textContent || el.getAttribute("aria-label") || el.getAttribute("title") || "").trim(),
69
70
  paths
70
71
  });
@@ -1,10 +1,10 @@
1
+ import { useNitroOrigin, useRuntimeConfig, useSiteConfig } from "#imports";
2
+ import Fuse from "fuse.js";
1
3
  import { defineEventHandler, getHeader, readBody } from "h3";
4
+ import { resolve } from "pathe";
2
5
  import { fixSlashes } from "site-config-stack/urls";
3
6
  import { parseURL } from "ufo";
4
- import { resolve } from "pathe";
5
- import Fuse from "fuse.js";
6
- import { generateFileLinkDiff, generateFileLinkPreviews, getLinkResponse, inspect, isNonFetchableLink, lruFsCache } from "#link-checker/pure";
7
- import { useNitroOrigin, useRuntimeConfig, useSiteConfig } from "#imports";
7
+ import { generateFileLinkDiff, generateFileLinkPreviews, getLinkResponse, inspect, isNonFetchableLink, lruFsCache } from "#link-checker/shared";
8
8
  import { serverQueryContent } from "#content/server";
9
9
  function isInternalRoute(path) {
10
10
  const lastSegment = path.split("/").pop() || path;
@@ -1,5 +1,5 @@
1
- import { inspect } from './inspect.js';
2
- import { generateFileLinkDiff, generateFileLinkPreviews, lruFsCache } from './diff.js';
3
1
  import { getLinkResponse } from './crawl.js';
2
+ import { generateFileLinkDiff, generateFileLinkPreviews, lruFsCache } from './diff.js';
3
+ import { inspect } from './inspect.js';
4
4
  import { isNonFetchableLink } from './inspections/util.js';
5
- export { inspect, generateFileLinkDiff, generateFileLinkPreviews, lruFsCache, getLinkResponse, isNonFetchableLink, };
5
+ export { generateFileLinkDiff, generateFileLinkPreviews, getLinkResponse, inspect, isNonFetchableLink, lruFsCache, };
@@ -1,12 +1,12 @@
1
- import { inspect } from "./inspect.js";
2
- import { generateFileLinkDiff, generateFileLinkPreviews, lruFsCache } from "./diff.js";
3
1
  import { getLinkResponse } from "./crawl.js";
2
+ import { generateFileLinkDiff, generateFileLinkPreviews, lruFsCache } from "./diff.js";
3
+ import { inspect } from "./inspect.js";
4
4
  import { isNonFetchableLink } from "./inspections/util.js";
5
5
  export {
6
- inspect,
7
6
  generateFileLinkDiff,
8
7
  generateFileLinkPreviews,
9
- lruFsCache,
10
8
  getLinkResponse,
11
- isNonFetchableLink
9
+ inspect,
10
+ isNonFetchableLink,
11
+ lruFsCache
12
12
  };
@@ -1,19 +1,32 @@
1
1
  import { parseURL } from "ufo";
2
- import RuleTrailingSlash from "./inspections/trailing-slash.js";
2
+ import RuleAbsoluteSiteUrls from "./inspections/absolute-site-urls.js";
3
+ import RuleDescriptiveLinkText from "./inspections/link-text.js";
3
4
  import RuleMissingHash from "./inspections/missing-hash.js";
4
- import RuleNoBaseLess from "./inspections/no-baseless.js";
5
+ import RuleNoDocumentRelative from "./inspections/no-baseless.js";
6
+ import RuleNoDoubleSlashes from "./inspections/no-double-slashes.js";
7
+ import RuleNoDuplicateQueryParams from "./inspections/no-duplicate-query-params.js";
8
+ import RuleNoErrorResponse from "./inspections/no-error-response.js";
5
9
  import RuleNoJavascript from "./inspections/no-javascript.js";
6
- import RuleAbsoluteSiteUrls from "./inspections/absolute-site-urls.js";
10
+ import RuleNoMissingHref from "./inspections/no-missing-href.js";
11
+ import RuleNoNonAsciiChars from "./inspections/no-non-ascii-chars.js";
12
+ import RuleNoUnderscores from "./inspections/no-underscores.js";
13
+ import RuleNoUppercaseChars from "./inspections/no-uppercase-chars.js";
14
+ import RuleNoWhitespace from "./inspections/no-whitespace.js";
15
+ import RuleTrailingSlash from "./inspections/trailing-slash.js";
7
16
  import RuleRedirects from "./redirects.js";
8
- import RuleNoErrorResponse from "./inspections/no-error-response-status.js";
9
- import RuleDescriptiveLinkText from "./inspections/descriptive-link-text.js";
10
- import { isNonFetchableLink } from "./inspections/util.js";
11
17
  export const AllInspections = [
18
+ RuleNoMissingHref(),
19
+ RuleNoDuplicateQueryParams(),
20
+ RuleNoNonAsciiChars(),
12
21
  RuleMissingHash(),
22
+ RuleNoUnderscores(),
23
+ RuleNoWhitespace(),
24
+ RuleNoDoubleSlashes(),
13
25
  RuleNoErrorResponse(),
14
- RuleNoBaseLess(),
26
+ RuleNoDocumentRelative(),
15
27
  RuleNoJavascript(),
16
28
  RuleTrailingSlash(),
29
+ RuleNoUppercaseChars(),
17
30
  RuleAbsoluteSiteUrls(),
18
31
  RuleRedirects(),
19
32
  RuleDescriptiveLinkText()
@@ -23,26 +36,24 @@ export function inspect(ctx, rules) {
23
36
  const res = { error: [], warning: [], fix: ctx.link, link: ctx.link };
24
37
  let link = ctx.link;
25
38
  const url = parseURL(link);
26
- if (!url.pathname && !url.protocol && !url.host && !isNonFetchableLink(link)) {
27
- res.error.push({
28
- name: "invalid-url",
29
- scope: "error",
30
- message: `Invalid URL: ${link}`
31
- });
32
- return res;
33
- }
34
- const validInspections = Object.entries(rules).filter(([name]) => !ctx.skipInspections || !ctx.skipInspections.includes(name)).map(([, rule]) => rule);
39
+ const validInspections = rules.filter(({ id }) => !(ctx.skipInspections || []).includes(id));
40
+ let processing = true;
35
41
  for (const rule of validInspections) {
36
42
  rule.test({
37
43
  ...ctx,
38
44
  link,
39
45
  url,
40
- report(obj) {
46
+ report(obj, stop) {
47
+ if (stop) {
48
+ processing = false;
49
+ }
41
50
  res[obj.scope].push(obj);
42
51
  if (obj.fix)
43
52
  link = obj.fix;
44
53
  }
45
54
  });
55
+ if (!processing)
56
+ break;
46
57
  }
47
58
  res.passes = !res.error?.length && !res.warning?.length;
48
59
  res.fix = link;
@@ -9,7 +9,7 @@ export default function RuleDescriptiveLinkText() {
9
9
  report({
10
10
  name: "link-text",
11
11
  scope: "warning",
12
- message: "Should have descriptive text.",
12
+ message: "Missing link textContent, title or aria-label.",
13
13
  tip: "Links with descriptive text are easier to understand for screen readers and search engines."
14
14
  });
15
15
  }
@@ -29,7 +29,7 @@ export default function RuleDescriptiveLinkText() {
29
29
  report({
30
30
  name: "link-text",
31
31
  scope: "warning",
32
- message: "Should have descriptive text.",
32
+ message: `Link text "${textContent}" should be more descriptive.`,
33
33
  tip: `The ${textContent} descriptive text does not provide any context to the link.`
34
34
  });
35
35
  }
@@ -0,0 +1 @@
1
+ export default function RuleNoDocumentRelative(): import("../../types").Rule;
@@ -1,15 +1,16 @@
1
1
  import { joinURL } from "ufo";
2
2
  import { defineRule, isNonFetchableLink } from "./util.js";
3
- export default function RuleNoBaseLess() {
3
+ export default function RuleNoDocumentRelative() {
4
4
  return defineRule({
5
5
  id: "no-baseless",
6
+ // TODO rename to no-document-relative
6
7
  test({ link, fromPath, report }) {
7
8
  if (link.startsWith("/") || link.startsWith("http") || isNonFetchableLink(link))
8
9
  return;
9
10
  report({
10
11
  name: "no-baseless",
11
12
  scope: "warning",
12
- message: "Should not have a base.",
13
+ message: "Links should be root relative.",
13
14
  fix: `${joinURL(fromPath, link)}`,
14
15
  fixDescription: `Add base ${fromPath}.`
15
16
  });
@@ -0,0 +1 @@
1
+ export default function RuleNoDoubleSlashes(): import("../../types").Rule;
@@ -0,0 +1,25 @@
1
+ import { defineRule } from "./util.js";
2
+ export default function RuleNoDoubleSlashes() {
3
+ return defineRule({
4
+ id: "no-double-slashes",
5
+ test({ url, link, report }) {
6
+ if (link.startsWith("//") && !link.includes(".")) {
7
+ report({
8
+ name: "no-double-slashes",
9
+ scope: "warning",
10
+ message: "Links should not contain double slashes.",
11
+ fix: link.replaceAll(/(^\/{2,}|\/{2,})/g, "/"),
12
+ fixDescription: "Remove double slashes."
13
+ });
14
+ } else if (url.pathname.match(/(^\/{2,}|\/{2,})/)) {
15
+ report({
16
+ name: "no-double-slashes",
17
+ scope: "warning",
18
+ message: "Links should not contain double slashes.",
19
+ fix: link.replace(url.pathname, url.pathname.replaceAll(/(^\/{2,}|\/{2,})/g, "/")),
20
+ fixDescription: "Remove double slashes."
21
+ });
22
+ }
23
+ }
24
+ });
25
+ }
@@ -0,0 +1 @@
1
+ export default function RuleNoDuplicateQueryParams(): import("../../types").Rule;
@@ -0,0 +1,28 @@
1
+ import { defineRule } from "./util.js";
2
+ export default function RuleNoDuplicateQueryParams() {
3
+ return defineRule({
4
+ id: "no-duplicate-query-params",
5
+ test({ report, link, url }) {
6
+ if (!url.search)
7
+ return;
8
+ const search = url.search.slice(1);
9
+ const searchParams = search.split("&").map((param) => param.split("=")[0]);
10
+ const duplicates = /* @__PURE__ */ new Set();
11
+ for (const param of searchParams) {
12
+ if (duplicates.has(param)) {
13
+ const fix = link.replace(new RegExp(`([?&])${param}=[^&]*&?`), "$1");
14
+ report({
15
+ name: "no-duplicate-query-params",
16
+ scope: "warning",
17
+ message: "Links should not contain duplicated query parameters.",
18
+ fix,
19
+ tip: "Duplicate query parameters can cause canonical URL issues.",
20
+ fixDescription: "Remove duplicate query parameter."
21
+ });
22
+ return;
23
+ }
24
+ duplicates.add(param);
25
+ }
26
+ }
27
+ });
28
+ }
@@ -0,0 +1 @@
1
+ export default function RuleNoMissingHref(): import("../../types").Rule;
@@ -0,0 +1,17 @@
1
+ import { defineRule } from "./util.js";
2
+ export default function RuleNoMissingHref() {
3
+ return defineRule({
4
+ id: "no-missing-href",
5
+ test({ report, link, role }) {
6
+ if (link.trim().length > 0 || role === "button") {
7
+ return;
8
+ }
9
+ report({
10
+ name: "no-missing-href",
11
+ scope: "warning",
12
+ message: "For accessibility and UX anchor tags require a href attribute.",
13
+ tip: 'Use a button element with type="button" instead if the link is not navigational.'
14
+ }, true);
15
+ }
16
+ });
17
+ }
@@ -0,0 +1 @@
1
+ export default function RuleNoNonAsciiChars(): import("../../types").Rule;