From e738ac499339e858ab518e3766fbec31d500c9fe Mon Sep 17 00:00:00 2001 From: dsinghvi Date: Mon, 3 Mar 2025 12:43:58 +0530 Subject: [PATCH 1/2] feat(cli): release broken link checker --- .../src/commands/validate/logViolations.ts | 19 +++++++++++-------- packages/cli/cli/versions.yml | 12 ++++++++++++ .../check-if-pathname-exists.ts | 17 ++++++++++++++++- .../valid-markdown-link.ts | 10 +--------- 4 files changed, 40 insertions(+), 18 deletions(-) diff --git a/packages/cli/cli/src/commands/validate/logViolations.ts b/packages/cli/cli/src/commands/validate/logViolations.ts index 587c6cc6219..50f14aacb6b 100644 --- a/packages/cli/cli/src/commands/validate/logViolations.ts +++ b/packages/cli/cli/src/commands/validate/logViolations.ts @@ -1,4 +1,5 @@ import chalk from "chalk"; +import { eq } from "lodash-es"; import { formatLog } from "@fern-api/cli-logger"; import { assertNever } from "@fern-api/core-utils"; @@ -28,20 +29,21 @@ export function logViolations({ }): LogViolationsResponse { // dedupe violations before processing const deduplicatedViolations: ValidationViolation[] = []; - const map = new Map(); + const record: Record = {}; for (const violation of violations) { - const existingViolations = map.get(violation.nodePath) ?? []; + const key = JSON.stringify(violation.nodePath); + const existingViolations = record[key] ?? []; const isDuplicate = existingViolations.some( (existingViolation) => existingViolation.message === violation.message && existingViolation.nodePath.length === violation.nodePath.length && - existingViolation.nodePath.every((item, index) => item === violation.nodePath[index]) && + existingViolation.nodePath.every((item, index) => eq(item, violation.nodePath[index])) && existingViolation.relativeFilepath === violation.relativeFilepath && existingViolation.severity === violation.severity ); if (!isDuplicate) { deduplicatedViolations.push(violation); - map.set(violation.nodePath, [...existingViolations, violation]); + record[key] = [...existingViolations, violation]; } } violations = deduplicatedViolations; @@ -70,12 +72,13 @@ export function logViolations({ } function groupViolationsByNodePath(violations: ValidationViolation[]): Map { - const map = new Map(); + const record: Record = {}; for (const violation of violations) { - const existingViolations = map.get(violation.nodePath) ?? []; - map.set(violation.nodePath, [...existingViolations, violation]); + const key = JSON.stringify(violation.nodePath); + const existingViolations = record[key] ?? []; + record[key] = [...existingViolations, violation]; } - return map; + return new Map(Object.entries(record).map(([key, violations]) => [JSON.parse(key), violations])); } function logViolationsGroup({ diff --git a/packages/cli/cli/versions.yml b/packages/cli/cli/versions.yml index fb006d725c9..92e2bc3c43a 100644 --- a/packages/cli/cli/versions.yml +++ b/packages/cli/cli/versions.yml @@ -1,3 +1,15 @@ +- changelogEntry: + - summary: | + Fixed several issues with broken link detection in docs: + - Fixed handling of redirects to ensure broken links aren't reported when valid redirects exist + - Added proper handling of relative paths from different slugs + - Improved URL validation and error messages + + Running `fern docs broken-links` will now scan your docs site and log any broken internal links. + type: fix + irVersion: 56 + version: 0.56.0 + - changelogEntry: - summary: | Fixed duplicate validation messages in docs validation by deduplicating violations diff --git a/packages/cli/yaml/docs-validator/src/rules/valid-markdown-link/check-if-pathname-exists.ts b/packages/cli/yaml/docs-validator/src/rules/valid-markdown-link/check-if-pathname-exists.ts index 4023b74fa1f..754bfc50020 100644 --- a/packages/cli/yaml/docs-validator/src/rules/valid-markdown-link/check-if-pathname-exists.ts +++ b/packages/cli/yaml/docs-validator/src/rules/valid-markdown-link/check-if-pathname-exists.ts @@ -51,7 +51,14 @@ export async function checkIfPathnameExists({ // if the pathname starts with `/`, it must either be a slug or a file in the current workspace if (pathname.startsWith("/")) { // only check slugs if the file is expected to be a markdown file - const redirectedPath = withRedirects(pathname, baseUrl, redirects); + let redirectedPath = withoutAnchors(withRedirects(pathname, baseUrl, redirects)); + for (let redirectCount = 0; redirectCount < 5; ++redirectCount) { + const nextRedirectPath = withoutAnchors(withRedirects(redirectedPath, baseUrl, redirects)); + if (redirectedPath === nextRedirectPath) { + break; + } + redirectedPath = nextRedirectPath; + } if (markdown && pageSlugs.has(removeLeadingSlash(redirectedPath))) { return true; @@ -114,3 +121,11 @@ function withRedirects( } return result.redirect.destination; } + +function withoutAnchors(slug: string): string { + const hashIndex = slug.indexOf("#"); + if (hashIndex === -1) { + return slug; + } + return slug.substring(0, hashIndex); +} diff --git a/packages/cli/yaml/docs-validator/src/rules/valid-markdown-link/valid-markdown-link.ts b/packages/cli/yaml/docs-validator/src/rules/valid-markdown-link/valid-markdown-link.ts index cef34594ca5..2656f76c549 100644 --- a/packages/cli/yaml/docs-validator/src/rules/valid-markdown-link/valid-markdown-link.ts +++ b/packages/cli/yaml/docs-validator/src/rules/valid-markdown-link/valid-markdown-link.ts @@ -83,14 +83,6 @@ export const ValidMarkdownLinks: Rule = { return []; } - let violationSeverity: "fatal" | "error" | "warning" = "error"; - - // If this file cannot be indexed (noindex=true, or hidden=true), then we report violations as less serious warnings - // since the broken links will not hurt SEO. - if (slugs.every((slug) => !collector.indexablePageSlugs.includes(slug))) { - violationSeverity = "warning"; - } - // Find all matches in the Markdown text const { pathnamesToCheck, violations } = collectPathnamesToCheck(content, { absoluteFilepath, @@ -122,7 +114,7 @@ export const ValidMarkdownLinks: Rule = { }); return { name: ValidMarkdownLinks.name, - severity: violationSeverity, + severity: "error" as const, message, relativeFilepath: relFilePath }; From d9fe062a88435a4ea26278ff41234be598bbfafe Mon Sep 17 00:00:00 2001 From: dsinghvi Date: Mon, 3 Mar 2025 12:54:55 +0530 Subject: [PATCH 2/2] chore(cli): rerelease 0.56.1 --- packages/cli/cli/versions.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/cli/cli/versions.yml b/packages/cli/cli/versions.yml index 92e2bc3c43a..8517a16c9a9 100644 --- a/packages/cli/cli/versions.yml +++ b/packages/cli/cli/versions.yml @@ -1,3 +1,10 @@ +- changelogEntry: + - summary: | + Re-releasing the Fern CLI to fix an issue with the published package. + type: fix + irVersion: 56 + version: 0.56.1 + - changelogEntry: - summary: | Fixed several issues with broken link detection in docs: