nuxt-link-checker 5.0.9 → 5.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.
Files changed (41) hide show
  1. package/dist/devtools/components/link-checker/CodeDiff.vue +55 -0
  2. package/dist/devtools/components/link-checker/CodeHighlight.vue +35 -0
  3. package/dist/devtools/components/link-checker/FixActionDialog.vue +58 -0
  4. package/dist/devtools/components/link-checker/LinkInspection.vue +108 -0
  5. package/dist/devtools/components/link-checker/LinkPassing.vue +23 -0
  6. package/dist/devtools/lib/link-checker/dialog.ts +1 -0
  7. package/dist/devtools/lib/link-checker/rpc-types.ts +1 -0
  8. package/dist/devtools/lib/link-checker/rpc.ts +45 -0
  9. package/dist/devtools/lib/link-checker/state.ts +53 -0
  10. package/dist/devtools/lib/link-checker/types.ts +1 -0
  11. package/dist/devtools/nuxt.config.ts +7 -0
  12. package/dist/devtools/pages/link-checker/debug.vue +25 -0
  13. package/dist/devtools/pages/link-checker/docs.vue +3 -0
  14. package/dist/devtools/pages/link-checker/index.vue +73 -0
  15. package/dist/devtools/pages/link-checker/links.vue +16 -0
  16. package/dist/devtools/pages/link-checker.vue +86 -0
  17. package/dist/eslint.mjs +10 -2
  18. package/dist/module.json +1 -1
  19. package/dist/module.mjs +6 -6
  20. package/dist/runtime/app/plugins/view/client.d.ts +1 -1
  21. package/dist/runtime/app/plugins/view/client.js +3 -2
  22. package/dist/runtime/server/providers/content-v3.js +1 -1
  23. package/dist/runtime/server/routes/__link-checker__/debug.js +1 -1
  24. package/dist/runtime/server/routes/__link-checker__/inspect.js +1 -1
  25. package/dist/runtime/server/routes/__link-checker__/links.js +1 -1
  26. package/package.json +31 -30
  27. package/dist/devtools/200.html +0 -1
  28. package/dist/devtools/404.html +0 -1
  29. package/dist/devtools/_nuxt/B8PEiB0p.js +0 -1
  30. package/dist/devtools/_nuxt/BGPtaqnr.js +0 -1
  31. package/dist/devtools/_nuxt/CVO1_9PV.js +0 -1
  32. package/dist/devtools/_nuxt/Cp-IABpG.js +0 -1
  33. package/dist/devtools/_nuxt/D0r3Knsf.js +0 -1
  34. package/dist/devtools/_nuxt/builds/latest.json +0 -1
  35. package/dist/devtools/_nuxt/builds/meta/eecfe483-dacc-4e85-91af-c4cc23bfceaa.json +0 -1
  36. package/dist/devtools/_nuxt/entry.BHWSa69F.css +0 -1
  37. package/dist/devtools/_nuxt/fira-code.Bc8wnsZt.woff2 +0 -0
  38. package/dist/devtools/_nuxt/hubot-sans.DLGyhQVu.woff2 +0 -0
  39. package/dist/devtools/_nuxt/wDzz0qaB.js +0 -1
  40. package/dist/devtools/_nuxt/y7unhDtB.js +0 -183
  41. package/dist/devtools/index.html +0 -1
@@ -0,0 +1,55 @@
1
+ <script setup lang="ts">
2
+ import type { BundledLanguage } from 'shiki'
3
+
4
+ const props = defineProps<{
5
+ code: string
6
+ lang?: BundledLanguage
7
+ diff: { added: number[], removed: number[], result: string }
8
+ }>()
9
+
10
+ const start = ref(props.diff.added[0] - 2)
11
+
12
+ const shikiClassRe = /class="shiki/
13
+ const lineClassRe = /class="line"/g
14
+ const codeContentRe = /<code>([\s\S]*)<\/code>/
15
+
16
+ function transformRendered(code: string) {
17
+ let count = 0
18
+ const linesToInclude = new Set<number>()
19
+ const diffed = code
20
+ .replace(shikiClassRe, 'class="shiki diff')
21
+ .replace(lineClassRe, (_) => {
22
+ count++
23
+ const hasAdded = props.diff.added.includes(count - 1)
24
+ const hasRemoved = props.diff.removed.includes(count - 1)
25
+ if (hasAdded || hasRemoved) {
26
+ for (let i = count - 3; i < count + 3; i++) {
27
+ if (i >= 0)
28
+ linesToInclude.add(i)
29
+ }
30
+ }
31
+ if (hasAdded)
32
+ return 'class="line line-added"'
33
+ if (hasRemoved)
34
+ return 'class="line line-removed"'
35
+ return _
36
+ })
37
+ return diffed.replace(codeContentRe, (_, p1) => {
38
+ const lines = p1.split('\n')
39
+ const filtered = lines.filter((_: any, i: number) => linesToInclude.has(i + 1))
40
+ return `<code>${filtered.join('\n')}</code>`
41
+ })
42
+ }
43
+
44
+ const elRef = useTemplateRef<HTMLDivElement>('elRef')
45
+ </script>
46
+
47
+ <template>
48
+ <OCodeBlock
49
+ ref="elRef"
50
+ :code="diff.result"
51
+ :lang="lang"
52
+ :transform-rendered="transformRendered"
53
+ :style="`--start: ${start};`"
54
+ />
55
+ </template>
@@ -0,0 +1,35 @@
1
+ <script setup lang="ts">
2
+ import type { BundledLanguage } from 'shiki'
3
+
4
+ const props = defineProps<{
5
+ code: string
6
+ lang: BundledLanguage
7
+ link: string
8
+ }>()
9
+
10
+ function transformRendered(code: string) {
11
+ return code.replaceAll(props.link, `<span class="highlight">${props.link}</span>`)
12
+ }
13
+
14
+ const elRef = useTemplateRef<HTMLDivElement>('elRef')
15
+ </script>
16
+
17
+ <template>
18
+ <OCodeBlock
19
+ ref="elRef"
20
+ :code="code"
21
+ :lang="lang"
22
+ :transform-rendered="transformRendered"
23
+ v-bind="$attrs"
24
+ />
25
+ </template>
26
+
27
+ <style>
28
+ .highlight {
29
+ background-color: #ffd5d5;
30
+ }
31
+
32
+ .dark .highlight {
33
+ background-color: #5c0000;
34
+ }
35
+ </style>
@@ -0,0 +1,58 @@
1
+ <script setup lang="ts">
2
+ import { FixDialog } from '../../lib/link-checker/dialog'
3
+ import { host } from '../../lib/link-checker/rpc'
4
+
5
+ function openFilePath(filepath: string) {
6
+ host.value?.openInEditor(filepath)
7
+ }
8
+
9
+ function handleClose(_a: any, resolve: (value: boolean) => void) {
10
+ resolve(false)
11
+ }
12
+ </script>
13
+
14
+ <template>
15
+ <FixDialog v-slot="{ resolve, args }">
16
+ <UModal :open="true" @close="handleClose('close', resolve)">
17
+ <div class="flex flex-col gap-2 w-full p-6">
18
+ <h2 class="text-xl font-semibold text-[var(--color-primary)]">
19
+ Confirm Code Diff
20
+ </h2>
21
+
22
+ <div v-for="(source, i) in args[0].diff" :key="i">
23
+ <div class="flex items-center gap-2 mb-1">
24
+ <div class="text-sm gap-2 flex flex-row mb-1 justify-end">
25
+ <span class="text-green-500">+{{ source.diff.added.length }}</span>
26
+ <span class="text-red-500">-{{ source.diff.removed.length }}</span>
27
+ </div>
28
+ <button
29
+ type="button"
30
+ class="opacity-50 text-xs font-mono cursor-pointer hover:opacity-80"
31
+ @click="openFilePath(source.filepath)"
32
+ >
33
+ {{ source.filepath }}
34
+ </button>
35
+ </div>
36
+
37
+ <div class="rounded-lg border border-[var(--color-border)] overflow-hidden max-h-50 overflow-auto">
38
+ <div class="flex flex-col gap-1 items-start px-4 py-2">
39
+ <CodeDiff v-bind="source" lang="vue-html" class="overflow-auto" />
40
+ </div>
41
+ </div>
42
+ </div>
43
+
44
+ <div class="flex gap-3 mt-2 justify-end items-center">
45
+ <DevtoolsAlert variant="info" class="flex-auto text-sm">
46
+ Experimental.
47
+ </DevtoolsAlert>
48
+ <UButton variant="ghost" @click="resolve(false)">
49
+ Cancel
50
+ </UButton>
51
+ <UButton color="primary" @click="resolve(true)">
52
+ Apply Fix
53
+ </UButton>
54
+ </div>
55
+ </div>
56
+ </UModal>
57
+ </FixDialog>
58
+ </template>
@@ -0,0 +1,108 @@
1
+ <script setup lang="ts">
2
+ import { FixDialog } from '../../lib/link-checker/dialog'
3
+ import { host, linkCheckerRpc } from '../../lib/link-checker/rpc'
4
+
5
+ const { item } = defineProps<{
6
+ item: any
7
+ }>()
8
+
9
+ function openFilePath(filepath: string) {
10
+ host.value?.openInEditor(filepath)
11
+ }
12
+
13
+ async function fixDialog() {
14
+ if (!await FixDialog.start(item))
15
+ return
16
+ await linkCheckerRpc.value!.applyLinkFixes(item.diff, item.link, item.fix)
17
+ }
18
+
19
+ async function scrollToLink() {
20
+ await linkCheckerRpc.value!.scrollToLink(item.link)
21
+ }
22
+ </script>
23
+
24
+ <template>
25
+ <div v-if="item" class="text-sm lg:flex w-full gap-5">
26
+ <div class="flex-shrink-0">
27
+ <UButton variant="ghost" size="xs" icon="carbon:cursor-2" @click="scrollToLink" />
28
+ </div>
29
+ <div class="min-w-0 flex-1">
30
+ <div class="flex space-x-2">
31
+ <UTooltip :text="item.textContent">
32
+ <div class="opacity-90 truncate" style="max-width: 150px;">
33
+ {{ item.textContent }}
34
+ </div>
35
+ </UTooltip>
36
+ <a :href="item.link" target="_blank" class="font-mono mb-3 truncate text-[var(--color-primary)] hover:underline">
37
+ {{ item.link }}
38
+ </a>
39
+ </div>
40
+ <div>
41
+ <div v-for="(inspection, i) in [...item.error, ...item.warning]" :key="i" class="flex flex-row gap-2 items-center mb-2">
42
+ <div class="flex gap-2 w-full">
43
+ <div style="min-width: 150px; margin-top: 1px;">
44
+ <template v-if="inspection.scope === 'error'">
45
+ <div class="text-red-800 text-xs flex items-center font-medium dark:text-red-300">
46
+ <UIcon name="carbon:error" class="mr-1 text-xs" />
47
+ Error
48
+ </div>
49
+ </template>
50
+ <template v-else>
51
+ <div class="text-yellow-800 flex items-center text-xs font-medium dark:text-yellow-300">
52
+ <UIcon name="carbon:warning" class="mr-1 text-xs" />
53
+ Warning
54
+ </div>
55
+ </template>
56
+ <span class="text-[var(--color-text-muted)]">{{ inspection.name }}</span>
57
+ </div>
58
+ <div class="flex-grow">
59
+ <div>{{ inspection.message }}</div>
60
+ <div v-if="inspection.tip">
61
+ <span class="opacity-60">{{ inspection.tip }}</span>
62
+ </div>
63
+ <UButton
64
+ v-if="inspection.fix"
65
+ variant="outline"
66
+ color="green"
67
+ size="xs"
68
+ icon="carbon:magic-wand"
69
+ :label="inspection.fixDescription"
70
+ class="mt-2"
71
+ @click="fixDialog"
72
+ />
73
+ </div>
74
+ </div>
75
+ </div>
76
+ </div>
77
+ </div>
78
+ <template v-if="item.passes">
79
+ <div class="flex gap-2 items-center">
80
+ <div style="flex-basis: 4rem;">
81
+ <span class="text-green-800 text-xs flex items-center font-medium dark:text-green-300">
82
+ <UIcon name="carbon:checkmark-outline" class="mr-1 text-xs" />
83
+ Pass
84
+ </span>
85
+ </div>
86
+ </div>
87
+ </template>
88
+ <div v-else class="flex flex-col w-full">
89
+ <div v-if="!item.sources?.length" class="text-[var(--color-text-muted)]">
90
+ No source code found.
91
+ </div>
92
+ <div v-for="(source, i) in item.sources" :key="i" class="mb-4">
93
+ <div class="flex mb-1 items-center">
94
+ <button
95
+ type="button"
96
+ class="opacity-50 text-xs font-mono cursor-pointer hover:opacity-80"
97
+ @click="openFilePath(source.filepath)"
98
+ >
99
+ {{ source.filepath }}
100
+ </button>
101
+ </div>
102
+ <div v-for="(preview, pk) in source.previews" :key="pk" class="rounded-lg border border-[var(--color-border)] overflow-hidden">
103
+ <CodeHighlight :link="item.link" :code="preview.code" lang="vue-html" class="overflow-auto" :style="{ '--start': Math.max(preview.lineNumber - 2, 1) }" />
104
+ </div>
105
+ </div>
106
+ </div>
107
+ </div>
108
+ </template>
@@ -0,0 +1,23 @@
1
+ <script setup lang="ts">
2
+ defineProps<{
3
+ item: any
4
+ }>()
5
+ </script>
6
+
7
+ <template>
8
+ <div class="text-sm flex w-full gap-5">
9
+ <div class="min-w-0 flex-1">
10
+ <a :href="item.link" target="_blank" class="font-mono mb-3 text-[var(--color-primary)] hover:underline">
11
+ {{ item.link }}
12
+ </a>
13
+ </div>
14
+ <div class="flex gap-2 items-center">
15
+ <div style="flex-basis: 4rem;">
16
+ <span class="text-green-800 text-xs flex items-center font-medium dark:text-green-300">
17
+ <UIcon name="carbon:checkmark-outline" class="mr-1 text-xs" />
18
+ Pass
19
+ </span>
20
+ </div>
21
+ </div>
22
+ </div>
23
+ </template>
@@ -0,0 +1 @@
1
+ export const FixDialog = createTemplatePromise<boolean, [any]>()
@@ -0,0 +1 @@
1
+ export type { ClientFunctions, ServerFunctions } from '../../../src/rpc-types'
@@ -0,0 +1,45 @@
1
+ import type { BirpcReturn } from 'birpc'
2
+ import type { ClientFunctions, ServerFunctions } from './rpc-types'
3
+ import type { NuxtLinkCheckerClient } from './types'
4
+ import { useDevtoolsConnection } from 'nuxtseo-layer-devtools/composables/rpc'
5
+ import { getQuery } from 'ufo'
6
+ import { ref, unref } from 'vue'
7
+ import { linkDb, linkFilter, queueLength, visibleLinks } from './state'
8
+
9
+ const RPC_NAMESPACE = 'nuxt-link-checker-rpc'
10
+
11
+ export const host = ref<any>()
12
+ export const linkCheckerRpc = ref<BirpcReturn<ServerFunctions>>()
13
+
14
+ useDevtoolsConnection({
15
+ onConnected(connectedHost) {
16
+ host.value = connectedHost
17
+
18
+ const linkCheckerClient = connectedHost.inject<NuxtLinkCheckerClient>('linkChecker')
19
+ // The injected link-checker client holds the live link data; without it there
20
+ // is nothing to mirror, so finish with the layer's data refresh (already fired).
21
+ if (!linkCheckerClient)
22
+ return
23
+
24
+ linkDb.value = linkCheckerClient.linkDb.value
25
+ visibleLinks.value = [...linkCheckerClient.visibleLinks]
26
+ linkFilter.value = getQuery(window.location.href).link as string
27
+
28
+ const rpc = connectedHost.rpc<ServerFunctions, ClientFunctions>(RPC_NAMESPACE, {
29
+ queueWorking(payload) {
30
+ queueLength.value = payload.queueLength
31
+ linkDb.value = unref(linkCheckerClient.linkDb)
32
+ visibleLinks.value = [...linkCheckerClient.visibleLinks]
33
+ },
34
+ updated() {
35
+ linkDb.value = unref(linkCheckerClient.linkDb)
36
+ visibleLinks.value = [...linkCheckerClient.visibleLinks]
37
+ },
38
+ filter(payload) {
39
+ linkFilter.value = payload.link
40
+ },
41
+ })
42
+ linkCheckerRpc.value = rpc
43
+ rpc?.connected()
44
+ },
45
+ })
@@ -0,0 +1,53 @@
1
+ import type { LinkInspectionResult } from './types'
2
+ import { useLocalStorage } from '@vueuse/core'
3
+ import { appFetch } from 'nuxtseo-layer-devtools/composables/rpc'
4
+ import { refreshTime } from 'nuxtseo-layer-devtools/composables/state'
5
+ import { computed, ref } from 'vue'
6
+ import { useAsyncData } from '#imports'
7
+
8
+ export const linkDb = ref<LinkInspectionResult[]>([])
9
+ export const showLiveInspections = useLocalStorage<boolean>('nuxt-link-checker:show-live-inspections', true)
10
+ export const visibleLinks = ref<string[]>([])
11
+ export const queueLength = ref(0)
12
+
13
+ export const linkFilter = ref<string | false>('')
14
+
15
+ // Derived link views shared across the Inspections and Links tabs
16
+ export const nodes = computed(() => {
17
+ const validLinks = visibleLinks.value
18
+ let n = [...linkDb.value]
19
+ if (linkFilter.value)
20
+ n = n.filter(node => node.link === linkFilter.value)
21
+ else
22
+ n = n.filter(node => validLinks.includes(node.link))
23
+ return n.sort((a, b) => (a.fix && !b.fix ? 1 : -1))
24
+ })
25
+
26
+ export const failingNodes = computed(() => {
27
+ const seen = new Set<string>()
28
+ return nodes.value.filter((n) => {
29
+ if (!n.error.length && !n.warning.length)
30
+ return false
31
+ if (seen.has(n.link))
32
+ return false
33
+ seen.add(n.link)
34
+ return true
35
+ })
36
+ })
37
+
38
+ export const internalLinks = computed(() => nodes.value.filter(n => n.passes && n.link.startsWith('/')))
39
+ export const externalLinks = computed(() => nodes.value.filter(n => n.passes && !n.link.startsWith('/')))
40
+ export const errorCount = computed(() => nodes.value.reduce((count, n) => count + n.error.length, 0))
41
+ export const warningCount = computed(() => nodes.value.reduce((count, n) => count + n.warning.length, 0))
42
+ export const visibleLinkCount = computed(() => visibleLinks.value.length)
43
+
44
+ export function useDebugData(): any {
45
+ return useAsyncData<{ runtimeConfig: any } | null>('link-checker-debug', () => {
46
+ if (!appFetch.value)
47
+ return null
48
+ return appFetch.value('/__link-checker__/debug.json')
49
+ }, {
50
+ watch: [appFetch, refreshTime],
51
+ default: () => null,
52
+ })
53
+ }
@@ -0,0 +1 @@
1
+ export type { LinkInspectionResult, NuxtLinkCheckerClient } from '../../../src/runtime/types'
@@ -0,0 +1,7 @@
1
+ import { resolve } from 'pathe'
2
+
3
+ // Nuxt SEO devtools panel, shipped as a layer (Model C). Components flat-registered
4
+ // so intra-panel references resolve by name.
5
+ export default defineNuxtConfig({
6
+ components: [{ path: resolve(__dirname, './components'), pathPrefix: false }],
7
+ })
@@ -0,0 +1,25 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import { useDebugData } from '../../lib/link-checker/state'
4
+
5
+ const { data } = useDebugData()
6
+
7
+ const runtimeConfigItems = computed(() => {
8
+ const config = data.value?.runtimeConfig || {}
9
+ return Object.entries(config)
10
+ .filter(([key]) => key !== 'version')
11
+ .map(([key, value]) => ({
12
+ key,
13
+ value: typeof value === 'object' ? JSON.stringify(value, null, 2) : String(value ?? ''),
14
+ mono: true,
15
+ copyable: true,
16
+ code: typeof value === 'object' ? 'json' as const : undefined,
17
+ }))
18
+ })
19
+ </script>
20
+
21
+ <template>
22
+ <DevtoolsSection icon="carbon:settings" text="Runtime Config">
23
+ <DevtoolsKeyValue :items="runtimeConfigItems" striped />
24
+ </DevtoolsSection>
25
+ </template>
@@ -0,0 +1,3 @@
1
+ <template>
2
+ <DevtoolsDocs url="https://nuxtseo.com/link-checker" />
3
+ </template>
@@ -0,0 +1,73 @@
1
+ <script setup lang="ts">
2
+ import FixActionDialog from '../../components/link-checker/FixActionDialog.vue'
3
+ import {
4
+ errorCount,
5
+ failingNodes,
6
+ linkFilter,
7
+ nodes,
8
+ queueLength,
9
+ visibleLinkCount,
10
+ warningCount,
11
+ } from '../../lib/link-checker/state'
12
+ </script>
13
+
14
+ <template>
15
+ <div class="space-y-2">
16
+ <DevtoolsToolbar>
17
+ <DevtoolsMetric
18
+ v-if="queueLength"
19
+ icon="carbon:progress-bar-round"
20
+ :value="`${visibleLinkCount ? Math.round((Math.abs(queueLength - visibleLinkCount) / visibleLinkCount) * 100) : 0}%`"
21
+ />
22
+ <DevtoolsMetric
23
+ v-if="errorCount"
24
+ icon="carbon:error"
25
+ :value="errorCount"
26
+ label="Errors"
27
+ variant="danger"
28
+ />
29
+ <DevtoolsMetric
30
+ v-if="warningCount"
31
+ icon="carbon:warning"
32
+ :value="warningCount"
33
+ label="Warnings"
34
+ variant="warning"
35
+ />
36
+ <DevtoolsMetric
37
+ v-if="!warningCount && !errorCount"
38
+ icon="carbon:checkmark-outline"
39
+ value="All links passing"
40
+ variant="success"
41
+ />
42
+ </DevtoolsToolbar>
43
+
44
+ <p v-if="linkFilter" class="text-sm flex items-center gap-1 mb-4">
45
+ <UIcon name="carbon:filter" />
46
+ Filtering Results.
47
+ <button type="button" class="underline" @click="linkFilter = false">
48
+ Show All
49
+ </button>
50
+ </p>
51
+
52
+ <div v-if="!linkFilter">
53
+ <template v-if="failingNodes.length">
54
+ <LinkInspection
55
+ v-for="(item, index) of failingNodes"
56
+ :key="index"
57
+ :item="item"
58
+ class="odd:bg-[var(--color-bg-elevated)] p-2"
59
+ />
60
+ </template>
61
+ <DevtoolsEmptyState
62
+ v-else-if="!queueLength"
63
+ icon="carbon:checkmark-outline"
64
+ title="No issues found"
65
+ description="All visible links are passing validation."
66
+ />
67
+ </div>
68
+ <div v-else>
69
+ <LinkInspection v-for="(item, index) of nodes" :key="index" :item="item" />
70
+ </div>
71
+ <FixActionDialog />
72
+ </div>
73
+ </template>
@@ -0,0 +1,16 @@
1
+ <script setup lang="ts">
2
+ import { externalLinks, internalLinks } from '../../lib/link-checker/state'
3
+ </script>
4
+
5
+ <template>
6
+ <div class="space-y-4">
7
+ <DevtoolsSection icon="carbon:chart-network" text="Internal Links">
8
+ <LinkPassing v-for="(item, index) of internalLinks" :key="index" :item="item" />
9
+ <DevtoolsEmptyState v-if="!internalLinks.length" icon="carbon:link" title="No internal links" />
10
+ </DevtoolsSection>
11
+ <DevtoolsSection icon="carbon:launch" text="External Links">
12
+ <LinkPassing v-for="(item, index) of externalLinks" :key="index" :item="item" />
13
+ <DevtoolsEmptyState v-if="!externalLinks.length" icon="carbon:link" title="No external links" />
14
+ </DevtoolsSection>
15
+ </div>
16
+ </template>
@@ -0,0 +1,86 @@
1
+ <script setup lang="ts">
2
+ import { loadShiki } from 'nuxtseo-layer-devtools/composables/shiki'
3
+ import { isProductionMode, refreshSources } from 'nuxtseo-layer-devtools/composables/state'
4
+ import { computed, ref, watch } from 'vue'
5
+ import { navigateTo, useRoute } from '#imports'
6
+ import { linkCheckerRpc } from '../lib/link-checker/rpc'
7
+ import { linkDb, queueLength, showLiveInspections, useDebugData } from '../lib/link-checker/state'
8
+
9
+ await loadShiki({
10
+ extraLangs: [
11
+ import('@shikijs/langs/vue-html'),
12
+ ],
13
+ })
14
+
15
+ const { data } = await useDebugData()
16
+
17
+ const route = useRoute()
18
+ const currentTab = computed(() => {
19
+ const p = route.path
20
+ if (p.startsWith('/link-checker/links'))
21
+ return 'links'
22
+ if (p.startsWith('/link-checker/debug'))
23
+ return 'debug'
24
+ if (p.startsWith('/link-checker/docs'))
25
+ return 'docs'
26
+ return 'inspections'
27
+ })
28
+
29
+ const navItems = [
30
+ { value: 'inspections', to: '/link-checker', icon: 'carbon:warning-diamond', label: 'Inspections', devOnly: false },
31
+ { value: 'links', to: '/link-checker/links', icon: 'carbon:checkmark-outline', label: 'Links', devOnly: false },
32
+ { value: 'debug', to: '/link-checker/debug', icon: 'carbon:debug', label: 'Debug', devOnly: true },
33
+ { value: 'docs', to: '/link-checker/docs', icon: 'carbon:book', label: 'Docs', devOnly: false },
34
+ ]
35
+
36
+ const loading = ref(false)
37
+
38
+ async function retryAll() {
39
+ linkDb.value = []
40
+ queueLength.value = 0
41
+ await linkCheckerRpc.value!.reset()
42
+ }
43
+ async function toggleLiveInspections() {
44
+ showLiveInspections.value = !showLiveInspections.value
45
+ await linkCheckerRpc.value!.toggleLiveInspections(showLiveInspections.value)
46
+ }
47
+ async function refresh() {
48
+ loading.value = true
49
+ await retryAll()
50
+ refreshSources()
51
+ setTimeout(() => {
52
+ loading.value = false
53
+ }, 300)
54
+ }
55
+
56
+ // Debug data is dev-only; leave the debug tab when the header switches to Production
57
+ watch(isProductionMode, (isProd) => {
58
+ if (isProd && currentTab.value === 'debug')
59
+ return navigateTo('/link-checker')
60
+ })
61
+ </script>
62
+
63
+ <template>
64
+ <DevtoolsLayout
65
+ v-model:active-tab="currentTab"
66
+ module-name="nuxt-link-checker"
67
+ title="Link Checker"
68
+ icon="carbon:cloud-satellite-link"
69
+ :version="data?.runtimeConfig?.version"
70
+ :nav-items="navItems"
71
+ github-url="https://github.com/harlan-zw/nuxt-link-checker"
72
+ :loading="loading"
73
+ @refresh="refresh"
74
+ >
75
+ <template #actions>
76
+ <UButton
77
+ :icon="showLiveInspections ? 'carbon:view-off' : 'carbon:view'"
78
+ variant="ghost"
79
+ size="xs"
80
+ :label="showLiveInspections ? 'Hide Inspections' : 'Show Inspections'"
81
+ @click="toggleLiveInspections"
82
+ />
83
+ </template>
84
+ <NuxtPage />
85
+ </DevtoolsLayout>
86
+ </template>
package/dist/eslint.mjs CHANGED
@@ -28,6 +28,13 @@ function extractMarkdownLinks(text) {
28
28
  return links;
29
29
  }
30
30
  const lineMapStack = [];
31
+ const OWN_RULES = /* @__PURE__ */ new Set(["valid-route", "valid-sitemap-link"]);
32
+ function isOwnRule(ruleId) {
33
+ if (!ruleId)
34
+ return false;
35
+ const name = ruleId.includes("/") ? ruleId.slice(ruleId.lastIndexOf("/") + 1) : ruleId;
36
+ return OWN_RULES.has(name);
37
+ }
31
38
  const markdownProcessor = {
32
39
  meta: {
33
40
  name: "link-checker/markdown",
@@ -54,9 +61,10 @@ const markdownProcessor = {
54
61
  },
55
62
  postprocess(messages) {
56
63
  const lineMap = lineMapStack.pop();
64
+ const filtered = messages.flat().filter((msg) => isOwnRule(msg.ruleId));
57
65
  if (!lineMap || !lineMap.size)
58
- return messages.flat();
59
- return messages.flat().map((msg) => {
66
+ return filtered;
67
+ return filtered.map((msg) => {
60
68
  const link = lineMap.get(msg.line);
61
69
  if (link) {
62
70
  return {
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "nuxt": ">=3.9.0"
5
5
  },
6
6
  "configKey": "linkChecker",
7
- "version": "5.0.9",
7
+ "version": "5.1.0",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"