|
| 1 | +diff --git a/index.ts b/index.ts |
| 2 | +index f8277dc78db878b6a54da7ef7a92596c91b77528..53c6b514f8c8f48a0d563f186041da5bb201ded0 100644 |
| 3 | +--- a/index.ts |
| 4 | ++++ b/index.ts |
| 5 | +@@ -1,12 +1,13 @@ |
| 6 | + import type { StarlightPlugin } from '@astrojs/starlight/types' |
| 7 | + import type { IntegrationResolvedRoute } from 'astro' |
| 8 | + import { AstroError } from 'astro/errors' |
| 9 | ++import { green } from 'kleur/colors' |
| 10 | + |
| 11 | + import { clearContentLayerCache } from './libs/astro' |
| 12 | + import { StarlightLinksValidatorOptionsSchema, type StarlightLinksValidatorUserOptions } from './libs/config' |
| 13 | + import { pathnameToSlug, stripTrailingSlash } from './libs/path' |
| 14 | + import { remarkStarlightLinksValidator, type RemarkStarlightLinksValidatorConfig } from './libs/remark' |
| 15 | +-import { logErrors, validateLinks } from './libs/validation' |
| 16 | ++import { logErrors, logRelativeLinkWarnings, splitRelativeLinkErrors, validateLinks } from './libs/validation' |
| 17 | + |
| 18 | + export type { StarlightLinksValidatorOptions } from './libs/config' |
| 19 | + |
| 20 | +@@ -69,9 +70,11 @@ export default function starlightLinksValidatorPlugin( |
| 21 | + |
| 22 | + const errors = validateLinks(pages, customPages, dir, astroConfig, starlightConfig, options.data) |
| 23 | + |
| 24 | +- const hasInvalidLinkToCustomPage = logErrors(logger, errors, site) |
| 25 | ++ const { fatalErrors, relativeErrors } = splitRelativeLinkErrors(errors) |
| 26 | ++ logRelativeLinkWarnings(logger, relativeErrors, site) |
| 27 | + |
| 28 | +- if (errors.size > 0) { |
| 29 | ++ if (fatalErrors.size > 0) { |
| 30 | ++ const hasInvalidLinkToCustomPage = logErrors(logger, fatalErrors, site) |
| 31 | + throwPluginError( |
| 32 | + 'Links validation failed.', |
| 33 | + hasInvalidLinkToCustomPage |
| 34 | +@@ -79,6 +82,16 @@ export default function starlightLinksValidatorPlugin( |
| 35 | + : undefined, |
| 36 | + ) |
| 37 | + } |
| 38 | ++ |
| 39 | ++ if (relativeErrors.size > 0) { |
| 40 | ++ logger.info( |
| 41 | ++ green( |
| 42 | ++ '✓ Link validation passed (relative-link issues above are warnings only).\n', |
| 43 | ++ ), |
| 44 | ++ ) |
| 45 | ++ } else { |
| 46 | ++ logErrors(logger, fatalErrors, site) |
| 47 | ++ } |
| 48 | + }, |
| 49 | + }, |
| 50 | + }) |
| 51 | +diff --git a/libs/validation.ts b/libs/validation.ts |
| 52 | +index d5436cdab642e018f97d81834045c115b1e4a974..7e3b263884b0b59e31370228abb66efa265590f2 100644 |
| 53 | +--- a/libs/validation.ts |
| 54 | ++++ b/libs/validation.ts |
| 55 | +@@ -4,7 +4,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url' |
| 56 | + |
| 57 | + import type { StarlightUserConfig as StarlightUserConfigWithPlugins } from '@astrojs/starlight/types' |
| 58 | + import type { AstroConfig, AstroIntegrationLogger } from 'astro' |
| 59 | +-import { bgGreen, black, blue, dim, green, red } from 'kleur/colors' |
| 60 | ++import { bgGreen, black, blue, dim, green, red, yellow } from 'kleur/colors' |
| 61 | + import picomatch from 'picomatch' |
| 62 | + import terminalLink from 'terminal-link' |
| 63 | + |
| 64 | +@@ -327,3 +327,66 @@ interface ValidationContext { |
| 65 | + } |
| 66 | + |
| 67 | + export type StarlightUserConfig = Omit<StarlightUserConfigWithPlugins, 'plugins'> |
| 68 | ++ |
| 69 | ++/** |
| 70 | ++ * Splits validation results so "relative link" policy violations can be logged as warnings |
| 71 | ++ * without failing the production build (aptos-docs). |
| 72 | ++ */ |
| 73 | ++export function splitRelativeLinkErrors(errors: ValidationErrors): { |
| 74 | ++ fatalErrors: ValidationErrors |
| 75 | ++ relativeErrors: ValidationErrors |
| 76 | ++} { |
| 77 | ++ const fatalErrors: ValidationErrors = new Map() |
| 78 | ++ const relativeErrors: ValidationErrors = new Map() |
| 79 | ++ |
| 80 | ++ for (const [id, { errors: validationErrors, file }] of errors) { |
| 81 | ++ const relativeOnly = validationErrors.filter((e) => e.type === ValidationErrorType.RelativeLink) |
| 82 | ++ const fatalOnly = validationErrors.filter((e) => e.type !== ValidationErrorType.RelativeLink) |
| 83 | ++ |
| 84 | ++ if (fatalOnly.length > 0) { |
| 85 | ++ fatalErrors.set(id, { errors: fatalOnly, file }) |
| 86 | ++ } |
| 87 | ++ if (relativeOnly.length > 0) { |
| 88 | ++ relativeErrors.set(id, { errors: relativeOnly, file }) |
| 89 | ++ } |
| 90 | ++ } |
| 91 | ++ |
| 92 | ++ return { fatalErrors, relativeErrors } |
| 93 | ++} |
| 94 | ++ |
| 95 | ++export function logRelativeLinkWarnings( |
| 96 | ++ pluginLogger: AstroIntegrationLogger, |
| 97 | ++ errors: ValidationErrors, |
| 98 | ++ site: AstroConfig['site'], |
| 99 | ++): void { |
| 100 | ++ if (errors.size === 0) { |
| 101 | ++ return |
| 102 | ++ } |
| 103 | ++ |
| 104 | ++ const logger = pluginLogger.fork('') |
| 105 | ++ |
| 106 | ++ const warnCount = [...errors.values()].reduce( |
| 107 | ++ (acc, { errors: validationErrors }) => acc + validationErrors.length, |
| 108 | ++ 0, |
| 109 | ++ ) |
| 110 | ++ |
| 111 | ++ logger.warn( |
| 112 | ++ yellow( |
| 113 | ++ `⚠ Found ${warnCount} ${pluralize(warnCount, 'relative link issue')} in ${errors.size} ${pluralize(errors.size, 'file')} (warning only; build continues).`, |
| 114 | ++ ), |
| 115 | ++ ) |
| 116 | ++ |
| 117 | ++ for (const [id, { errors: validationErrors, file }] of errors) { |
| 118 | ++ logger.warn(`${yellow('▶')} ${blue(terminalLink(id, pathToFileURL(file).toString(), { fallback: false }))}`) |
| 119 | ++ |
| 120 | ++ for (const [index, validationError] of validationErrors.entries()) { |
| 121 | ++ logger.warn( |
| 122 | ++ ` ${blue(`${index < validationErrors.length - 1 ? '├' : '└'}─`)} ${validationError.link}${dim( |
| 123 | ++ ` - ${formatValidationError(validationError, site)}`, |
| 124 | ++ )}`, |
| 125 | ++ ) |
| 126 | ++ } |
| 127 | ++ } |
| 128 | ++ |
| 129 | ++ process.stdout.write('\n') |
| 130 | ++} |
0 commit comments