Skip to content

Commit 4f25c87

Browse files
authored
fix: internal spec links rewritten to relative docs links (#200)
## PR Checklist - [x] Linked issue added (e.g., `Fixes #123`) - [x] I have run `bun run format` to ensure code is properly formatted - [x] I have verified that `bun run lint` passes without errors - [ ] If blog post was added: - [ ] Ensure images have been optimised - [ ] Update dates to reflect the actual publishing date when merged (file names, folder names, and frontmatter) ## Summary <!-- What has been updated and why --> Internal spec links were broken, they were copied straight over from the rfcs (in a separate repo) and so didn't have teh correct context here. This Pr fixes the broken RFC links by rewriting relative markdown links to local docs routes. Fixes https://linear.app/interledger/issue/DEVPORT-34/bug-report-broken-links-on-the-ilp-specs You can go to https://interledger.org/developers/rfcs/interledger-protocol/ and see as you click internal links within the spec they break, E.g. https://interledger.org/developers/rfcs/0029-stream/0029-stream.md Now if you do the same with the deploy preview you'll see the links take us to the relevant spec content on our site, and if we don't have it then they direct to GitHub instead. STREAM receipts have been deprecated and so were also removed as part of this PR
1 parent 77178a0 commit 4f25c87

8 files changed

Lines changed: 314 additions & 65 deletions

File tree

.github/workflows/sync-mdx-to-strapi.yml

Lines changed: 0 additions & 32 deletions
This file was deleted.

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ Inside this project, you'll see the following folders and files:
2525

2626
Starlight looks for `.md` or `.mdx` files in the `src/content/docs/` directory. Each file is exposed as a route based on its file name.
2727

28+
RFC pages under `src/content/docs/rfcs/` are a small exception. Those route files wrap the upstream markdown from the `interledger/rfcs` repository via `src/components/Rfc.astro`. Internal RFC-to-RFC links are rewritten in `src/utils/rewriteRfcLinks.ts` so the rendered docs point at local Starlight routes instead of upstream `.md` source paths.
29+
2830
Static assets, like favicons or images, can be placed in the `public/` directory. When referencing these assets in your markdown, you do not have to include `public/` in the file path, so an image would have a path like:
2931

3032
```md

astro.config.mjs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,6 @@ export default defineConfig({
109109
label: 'Bilateral Transfer Protocol',
110110
link: '/rfcs/bilateral-transfer-protocol'
111111
},
112-
{
113-
label: 'STREAM Receipts',
114-
link: '/rfcs/stream-receipts'
115-
},
116112
{
117113
label: 'Hashed-Timelock Agreements',
118114
link: '/rfcs/hashed-timelock-agreements'

src/components/Rfc.astro

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,38 @@
11
---
2-
import { parse } from 'node-html-parser'
32
import showdown from 'showdown'
3+
import { rewriteRfcLinks } from '../utils/rewriteRfcLinks'
44
5-
const { source } = Astro.props
5+
interface Props {
6+
source: string
7+
docsBasePath?: string
8+
}
9+
10+
const { source, docsBasePath = '/developers' } = Astro.props
611
7-
const getApiData = async () => {
8-
const data = await fetch(source).then((response) => response.text())
9-
const frontMatterPos = data.indexOf('---', 1) + 3
10-
const specBody = data.slice(frontMatterPos)
11-
return specBody
12+
const response = await fetch(source)
13+
14+
if (!response.ok) {
15+
throw new Error(
16+
`Failed to fetch RFC markdown from ${source}: ${response.status} ${response.statusText}`
17+
)
1218
}
1319
14-
const specBody = await getApiData()
20+
const markdown = await response.text()
21+
22+
const frontMatterPos = markdown.indexOf('---', 1) + 3
23+
const specBody = markdown.slice(frontMatterPos)
24+
1525
const converter = new showdown.Converter({
1626
tables: true,
1727
ghCompatibleHeaderId: true
1828
})
19-
const html = converter.makeHtml(specBody)
20-
const output = parse(html)
29+
30+
const renderedHtml = converter.makeHtml(specBody)
31+
32+
const html = rewriteRfcLinks(renderedHtml, {
33+
sourceUrl: source,
34+
docsBasePath
35+
})
2136
---
2237

23-
{output}
38+
<div set:html={html} />

src/content/docs/rfcs/stream-receipts.mdx

Lines changed: 0 additions & 18 deletions
This file was deleted.

src/data/rfcs.ts

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { parseRawGitHubPath } from '../utils/parseRawGitHubPath'
2+
3+
export type PublishedRfc = {
4+
id: string
5+
title: string
6+
route: `/rfcs/${string}`
7+
sourceRawUrl: string
8+
}
9+
10+
export const PUBLISHED_RFCS = [
11+
{
12+
id: 'interledger-protocol',
13+
title: 'Interledger Protocol V4 (ILPv4)',
14+
route: '/rfcs/interledger-protocol',
15+
sourceRawUrl:
16+
'https://raw.githubusercontent.com/interledger/rfcs/master/0027-interledger-protocol-4/0027-interledger-protocol-4.md'
17+
},
18+
{
19+
id: 'interledger-architecture',
20+
title: 'Interledger Architecture',
21+
route: '/rfcs/interledger-architecture',
22+
sourceRawUrl:
23+
'https://raw.githubusercontent.com/interledger/rfcs/master/0001-interledger-architecture/0001-interledger-architecture.md'
24+
},
25+
{
26+
id: 'ilp-addresses',
27+
title: 'Interledger Addresses',
28+
route: '/rfcs/ilp-addresses',
29+
sourceRawUrl:
30+
'https://raw.githubusercontent.com/interledger/rfcs/master/0015-ilp-addresses/0015-ilp-addresses.md'
31+
},
32+
{
33+
id: 'stream-protocol',
34+
title: 'STREAM Protocol',
35+
route: '/rfcs/stream-protocol',
36+
sourceRawUrl:
37+
'https://raw.githubusercontent.com/interledger/rfcs/master/0029-stream/0029-stream.md'
38+
},
39+
{
40+
id: 'simple-payment-setup-protocol',
41+
title: 'Simple Payment Setup Protocol (SPSP)',
42+
route: '/rfcs/simple-payment-setup-protocol',
43+
sourceRawUrl:
44+
'https://raw.githubusercontent.com/interledger/rfcs/master/0009-simple-payment-setup-protocol/0009-simple-payment-setup-protocol.md'
45+
},
46+
{
47+
id: 'peering-clearing-settling',
48+
title: 'Peering, Clearing and Settling',
49+
route: '/rfcs/peering-clearing-settling',
50+
sourceRawUrl:
51+
'https://raw.githubusercontent.com/interledger/rfcs/master/0032-peering-clearing-settlement/0032-peering-clearing-settlement.md'
52+
},
53+
{
54+
id: 'settlement-engines',
55+
title: 'Settlement Engines',
56+
route: '/rfcs/settlement-engines',
57+
sourceRawUrl:
58+
'https://raw.githubusercontent.com/interledger/rfcs/master/0038-settlement-engines/0038-settlement-engines.md'
59+
},
60+
{
61+
id: 'ilp-over-http',
62+
title: 'ILP Over HTTP',
63+
route: '/rfcs/ilp-over-http',
64+
sourceRawUrl:
65+
'https://raw.githubusercontent.com/interledger/rfcs/master/0035-ilp-over-http/0035-ilp-over-http.md'
66+
},
67+
{
68+
id: 'bilateral-transfer-protocol',
69+
title: 'Bilateral Transfer Protocol',
70+
route: '/rfcs/bilateral-transfer-protocol',
71+
sourceRawUrl:
72+
'https://raw.githubusercontent.com/interledger/rfcs/master/0023-bilateral-transfer-protocol/0023-bilateral-transfer-protocol.md'
73+
},
74+
{
75+
id: 'hashed-timelock-agreements',
76+
title: 'Hashed-Timelock Agreements',
77+
route: '/rfcs/hashed-timelock-agreements',
78+
sourceRawUrl:
79+
'https://raw.githubusercontent.com/interledger/rfcs/master/0022-hashed-timelock-agreements/0022-hashed-timelock-agreements.md'
80+
}
81+
] as const satisfies readonly PublishedRfc[]
82+
83+
export function getPublishedRfcRouteBySourcePath(): Map<string, string> {
84+
return new Map(
85+
PUBLISHED_RFCS.map((rfc) => {
86+
const { sourcePath } = parseRawGitHubPath(
87+
rfc.sourceRawUrl,
88+
'raw GitHub URL'
89+
)
90+
return [sourcePath, withTrailingSlash(rfc.route)]
91+
})
92+
)
93+
}
94+
95+
export function getRfcById(id: PublishedRfc['id']): PublishedRfc {
96+
const rfc = PUBLISHED_RFCS.find((entry) => entry.id === id)
97+
98+
if (!rfc) {
99+
throw new Error(`Unknown RFC id: ${id}`)
100+
}
101+
102+
return rfc
103+
}
104+
105+
function withTrailingSlash(route: string): string {
106+
return route.endsWith('/') ? route : `${route}/`
107+
}

src/utils/parseRawGitHubPath.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
export type RawGitHubParts = {
2+
branch: string
3+
sourcePath: string
4+
}
5+
6+
const RAW_GITHUB_HOST = 'raw.githubusercontent.com'
7+
const EXPECTED_OWNER = 'interledger'
8+
const EXPECTED_REPO = 'rfcs'
9+
10+
export function parseRawGitHubPath(
11+
inputUrl: string,
12+
context: string
13+
): RawGitHubParts {
14+
const url = new URL(inputUrl)
15+
16+
if (url.hostname !== RAW_GITHUB_HOST) {
17+
throw new Error(
18+
`Expected ${context} to use raw.githubusercontent.com, received: ${inputUrl}`
19+
)
20+
}
21+
22+
const segments = url.pathname.split('/').filter(Boolean)
23+
24+
if (segments.length < 4) {
25+
throw new Error(`Unexpected ${context} format: ${inputUrl}`)
26+
}
27+
28+
const [owner, repo, branch, ...pathSegments] = segments
29+
30+
if (owner !== EXPECTED_OWNER || repo !== EXPECTED_REPO) {
31+
throw new Error(`Unexpected RFC repository in ${context}: ${inputUrl}`)
32+
}
33+
34+
if (!branch) {
35+
throw new Error(`Missing branch in ${context}: ${inputUrl}`)
36+
}
37+
38+
if (pathSegments.length === 0) {
39+
throw new Error(`Missing source path in ${context}: ${inputUrl}`)
40+
}
41+
42+
return {
43+
branch,
44+
sourcePath: pathSegments.join('/')
45+
}
46+
}

0 commit comments

Comments
 (0)