Skip to content

Commit 806366c

Browse files
committed
feat!: adds errors for local links, e.g. URLs with a hostname of localhost or 127.0.0.1
1 parent 3182cfa commit 806366c

File tree

6 files changed

+63
-7
lines changed

6 files changed

+63
-7
lines changed

docs/src/content/docs/configuration.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,28 @@ export default defineConfig({
125125
})
126126
```
127127

128+
### `errorOnLocalLinks`
129+
130+
**Type:** `boolean`
131+
**Default:** `true`
132+
133+
By default, the Starlight Links Validator plugin will error on local links, e.g. URLs with a hostname of `localhost` or `127.0.0.1`, as they are usually used for development purposes and should not be present in production.
134+
If you want to allow such links, you can set this option to `false`.
135+
136+
```js {6}
137+
export default defineConfig({
138+
integrations: [
139+
starlight({
140+
plugins: [
141+
starlightLinksValidator({
142+
errorOnLocalLinks: false,
143+
}),
144+
],
145+
}),
146+
],
147+
})
148+
```
149+
128150
### `exclude`
129151

130152
**Type:** `string[]`

packages/starlight-links-validator/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ const starlightLinksValidatorOptionsSchema = z
4141
* @default true
4242
*/
4343
errorOnInvalidHashes: z.boolean().default(true),
44+
/**
45+
* Defines whether the plugin should error on local links, e.g. URLs with a hostname of `localhost` or `127.0.0.1`.
46+
*
47+
* @default true
48+
*/
49+
errorOnLocalLinks: z.boolean().default(true),
4450
/**
4551
* Defines a list of links or glob patterns that should be excluded from validation.
4652
*

packages/starlight-links-validator/libs/remark.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export const remarkStarlightLinksValidator: Plugin<[{ base: string; srcDir: URL
6161
break
6262
}
6363
case 'link': {
64-
if (isInternalLink(node.url)) {
64+
if (shouldValidateLink(node.url)) {
6565
fileLinks.push(node.url)
6666
}
6767

@@ -70,7 +70,7 @@ export const remarkStarlightLinksValidator: Plugin<[{ base: string; srcDir: URL
7070
case 'linkReference': {
7171
const definition = fileDefinitions.get(node.identifier)
7272

73-
if (definition && isInternalLink(definition)) {
73+
if (definition && shouldValidateLink(definition)) {
7474
fileLinks.push(definition)
7575
}
7676

@@ -96,7 +96,7 @@ export const remarkStarlightLinksValidator: Plugin<[{ base: string; srcDir: URL
9696
continue
9797
}
9898

99-
if (isInternalLink(attribute.value)) {
99+
if (shouldValidateLink(attribute.value)) {
100100
fileLinks.push(attribute.value)
101101
}
102102
}
@@ -125,7 +125,7 @@ export const remarkStarlightLinksValidator: Plugin<[{ base: string; srcDir: URL
125125
htmlNode.tagName === 'a' &&
126126
hasProperty(htmlNode, 'href') &&
127127
typeof htmlNode.properties.href === 'string' &&
128-
isInternalLink(htmlNode.properties.href)
128+
shouldValidateLink(htmlNode.properties.href)
129129
) {
130130
fileLinks.push(htmlNode.properties.href)
131131
}
@@ -145,8 +145,18 @@ export function getValidationData() {
145145
return { headings, links }
146146
}
147147

148-
function isInternalLink(link: string) {
149-
return !isAbsoluteUrl(link)
148+
function shouldValidateLink(link: string) {
149+
if (!isAbsoluteUrl(link)) {
150+
return true
151+
}
152+
153+
try {
154+
const url = new URL(link)
155+
156+
return url.hostname === 'localhost' || url.hostname === '127.0.0.1'
157+
} catch {
158+
return false
159+
}
150160
}
151161

152162
function getFilePath(base: string, filePath: string, slug: string | undefined) {

packages/starlight-links-validator/libs/validation.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export const ValidationErrorType = {
1717
InconsistentLocale: 'inconsistent locale',
1818
InvalidHash: 'invalid hash',
1919
InvalidLink: 'invalid link',
20+
LocalLink: 'local link',
2021
RelativeLink: 'relative link',
2122
TrailingSlash: 'trailing slash',
2223
} as const
@@ -115,6 +116,14 @@ function validateLink(context: ValidationContext) {
115116
return
116117
}
117118

119+
if (/^https?:\/\//.test(link)) {
120+
if (options.errorOnLocalLinks) {
121+
addError(errors, filePath, link, ValidationErrorType.LocalLink)
122+
}
123+
124+
return
125+
}
126+
118127
const sanitizedLink = link.replace(/^\//, '')
119128
const segments = sanitizedLink.split('#')
120129

packages/starlight-links-validator/tests/basics.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ test('does not build with invalid links', async () => {
2121

2222
expect(status).toBe('error')
2323

24-
expectValidationErrorCount(output, 61, 4)
24+
expectValidationErrorCount(output, 64, 4)
2525

2626
expectValidationErrors(output, 'test/', [
2727
['/https://starlight.astro.build/', ValidationErrorType.InvalidLink],
@@ -52,6 +52,9 @@ test('does not build with invalid links', async () => {
5252
['/guidelines/ui.pdf?query=string', ValidationErrorType.InvalidLink],
5353
['/unknown-ref?query=string', ValidationErrorType.InvalidLink],
5454
['?query=string#unknown-ref', ValidationErrorType.InvalidHash],
55+
['http://localhost', ValidationErrorType.LocalLink],
56+
['http://localhost:4321/', ValidationErrorType.LocalLink],
57+
['https://127.0.0.1:4321/getting-started', ValidationErrorType.LocalLink],
5558
])
5659

5760
expectValidationErrors(output, 'guides/example/', [

packages/starlight-links-validator/tests/fixtures/invalid-links/src/content/docs/test.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,9 @@ some content
7777

7878
[ref-with-query-string-unknown-page]: /unknown-ref?query=string
7979
[ref-with-query-string-invalid-hash]: ?query=string#unknown-ref
80+
81+
## Local links
82+
83+
- [Local link](http://localhost)
84+
- [Local link with port](http://localhost:4321/)
85+
- [Local link using HTTPS](https://127.0.0.1:4321/getting-started)

0 commit comments

Comments
 (0)