@levino/shipyard-docs 0.6.2 → 0.6.3
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.
- package/README.md +40 -1
- package/astro/Layout.astro +159 -5
- package/package.json +4 -2
- package/src/index.ts +797 -146
- package/src/rehypeVersionLinks.test.ts +319 -0
- package/src/rehypeVersionLinks.ts +156 -0
- package/src/routeHelpers.test.ts +657 -1
- package/src/routeHelpers.ts +221 -0
- package/src/sidebarEntries.test.ts +154 -1
- package/src/sidebarEntries.ts +33 -0
- package/src/versionHelpers.ts +64 -0
- package/src/versionSchema.test.ts +404 -0
- package/src/virtual-module.d.ts +68 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import type { Element, Root } from 'hast'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { rehypeVersionLinks } from './rehypeVersionLinks'
|
|
4
|
+
|
|
5
|
+
// Helper to create a simple HAST tree with an anchor element
|
|
6
|
+
const createTree = (href: string): Root => ({
|
|
7
|
+
type: 'root',
|
|
8
|
+
children: [
|
|
9
|
+
{
|
|
10
|
+
type: 'element',
|
|
11
|
+
tagName: 'a',
|
|
12
|
+
properties: { href },
|
|
13
|
+
children: [{ type: 'text', value: 'Link' }],
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
// Helper to get the href from the tree after transformation
|
|
19
|
+
const getHref = (tree: Root): string | undefined => {
|
|
20
|
+
const anchor = tree.children[0] as Element
|
|
21
|
+
return anchor.properties?.href as string | undefined
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe('rehypeVersionLinks', () => {
|
|
25
|
+
const defaultOptions = {
|
|
26
|
+
routeBasePath: 'docs',
|
|
27
|
+
currentVersion: 'v2',
|
|
28
|
+
availableVersions: ['v1', 'v2'],
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe('external links', () => {
|
|
32
|
+
it('should not modify http links', () => {
|
|
33
|
+
const tree = createTree('http://example.com')
|
|
34
|
+
rehypeVersionLinks(defaultOptions)(tree)
|
|
35
|
+
expect(getHref(tree)).toBe('http://example.com')
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('should not modify https links', () => {
|
|
39
|
+
const tree = createTree('https://example.com/page')
|
|
40
|
+
rehypeVersionLinks(defaultOptions)(tree)
|
|
41
|
+
expect(getHref(tree)).toBe('https://example.com/page')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('should not modify mailto links', () => {
|
|
45
|
+
const tree = createTree('mailto:test@example.com')
|
|
46
|
+
rehypeVersionLinks(defaultOptions)(tree)
|
|
47
|
+
expect(getHref(tree)).toBe('mailto:test@example.com')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('should not modify tel links', () => {
|
|
51
|
+
const tree = createTree('tel:+1234567890')
|
|
52
|
+
rehypeVersionLinks(defaultOptions)(tree)
|
|
53
|
+
expect(getHref(tree)).toBe('tel:+1234567890')
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('should not modify anchor links', () => {
|
|
57
|
+
const tree = createTree('#section-id')
|
|
58
|
+
rehypeVersionLinks(defaultOptions)(tree)
|
|
59
|
+
expect(getHref(tree)).toBe('#section-id')
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
describe('cross-version links', () => {
|
|
64
|
+
it('should transform @version:/path syntax to versioned URL', () => {
|
|
65
|
+
const tree = createTree('@v1:/installation')
|
|
66
|
+
rehypeVersionLinks(defaultOptions)(tree)
|
|
67
|
+
expect(getHref(tree)).toBe('/docs/v1/installation')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('should handle cross-version links with leading slash', () => {
|
|
71
|
+
const tree = createTree('@v1:/guide/intro')
|
|
72
|
+
rehypeVersionLinks(defaultOptions)(tree)
|
|
73
|
+
expect(getHref(tree)).toBe('/docs/v1/guide/intro')
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('should handle cross-version links without leading slash', () => {
|
|
77
|
+
const tree = createTree('@v1:guide/intro')
|
|
78
|
+
rehypeVersionLinks(defaultOptions)(tree)
|
|
79
|
+
expect(getHref(tree)).toBe('/docs/v1/guide/intro')
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('should handle @latest cross-version links', () => {
|
|
83
|
+
const tree = createTree('@latest:/getting-started')
|
|
84
|
+
rehypeVersionLinks(defaultOptions)(tree)
|
|
85
|
+
expect(getHref(tree)).toBe('/docs/latest/getting-started')
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('should handle nested paths in cross-version links', () => {
|
|
89
|
+
const tree = createTree('@v2:/api/methods/create')
|
|
90
|
+
rehypeVersionLinks(defaultOptions)(tree)
|
|
91
|
+
expect(getHref(tree)).toBe('/docs/v2/api/methods/create')
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('should handle version with dots', () => {
|
|
95
|
+
const options = {
|
|
96
|
+
...defaultOptions,
|
|
97
|
+
availableVersions: ['1.0.0', '2.0.0'],
|
|
98
|
+
}
|
|
99
|
+
const tree = createTree('@1.0.0:/page')
|
|
100
|
+
rehypeVersionLinks(options)(tree)
|
|
101
|
+
expect(getHref(tree)).toBe('/docs/1.0.0/page')
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
describe('auto-versioning absolute docs paths', () => {
|
|
106
|
+
it('should add current version to unversioned absolute docs path', () => {
|
|
107
|
+
const tree = createTree('/docs/installation')
|
|
108
|
+
rehypeVersionLinks(defaultOptions)(tree)
|
|
109
|
+
expect(getHref(tree)).toBe('/docs/v2/installation')
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('should add current version to nested unversioned path', () => {
|
|
113
|
+
const tree = createTree('/docs/guide/advanced/topic')
|
|
114
|
+
rehypeVersionLinks(defaultOptions)(tree)
|
|
115
|
+
expect(getHref(tree)).toBe('/docs/v2/guide/advanced/topic')
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('should not modify already versioned absolute path', () => {
|
|
119
|
+
const tree = createTree('/docs/v1/installation')
|
|
120
|
+
rehypeVersionLinks(defaultOptions)(tree)
|
|
121
|
+
expect(getHref(tree)).toBe('/docs/v1/installation')
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('should not modify path with current version', () => {
|
|
125
|
+
const tree = createTree('/docs/v2/installation')
|
|
126
|
+
rehypeVersionLinks(defaultOptions)(tree)
|
|
127
|
+
expect(getHref(tree)).toBe('/docs/v2/installation')
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('should not modify path with latest alias', () => {
|
|
131
|
+
const tree = createTree('/docs/latest/installation')
|
|
132
|
+
rehypeVersionLinks(defaultOptions)(tree)
|
|
133
|
+
expect(getHref(tree)).toBe('/docs/latest/installation')
|
|
134
|
+
})
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
describe('relative links', () => {
|
|
138
|
+
it('should not modify relative links starting with ./', () => {
|
|
139
|
+
const tree = createTree('./other-page')
|
|
140
|
+
rehypeVersionLinks(defaultOptions)(tree)
|
|
141
|
+
expect(getHref(tree)).toBe('./other-page')
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('should not modify relative links starting with ../', () => {
|
|
145
|
+
const tree = createTree('../parent/page')
|
|
146
|
+
rehypeVersionLinks(defaultOptions)(tree)
|
|
147
|
+
expect(getHref(tree)).toBe('../parent/page')
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it('should not modify relative links without prefix', () => {
|
|
151
|
+
const tree = createTree('sibling-page')
|
|
152
|
+
rehypeVersionLinks(defaultOptions)(tree)
|
|
153
|
+
expect(getHref(tree)).toBe('sibling-page')
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
describe('non-docs absolute paths', () => {
|
|
158
|
+
it('should not modify absolute paths outside docs base', () => {
|
|
159
|
+
const tree = createTree('/about')
|
|
160
|
+
rehypeVersionLinks(defaultOptions)(tree)
|
|
161
|
+
expect(getHref(tree)).toBe('/about')
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('should not modify absolute paths to other sections', () => {
|
|
165
|
+
const tree = createTree('/blog/post')
|
|
166
|
+
rehypeVersionLinks(defaultOptions)(tree)
|
|
167
|
+
expect(getHref(tree)).toBe('/blog/post')
|
|
168
|
+
})
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
describe('custom routeBasePath', () => {
|
|
172
|
+
it('should handle custom routeBasePath', () => {
|
|
173
|
+
const options = {
|
|
174
|
+
routeBasePath: 'guides',
|
|
175
|
+
currentVersion: 'v2',
|
|
176
|
+
availableVersions: ['v1', 'v2'],
|
|
177
|
+
}
|
|
178
|
+
const tree = createTree('/guides/installation')
|
|
179
|
+
rehypeVersionLinks(options)(tree)
|
|
180
|
+
expect(getHref(tree)).toBe('/guides/v2/installation')
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('should handle cross-version links with custom routeBasePath', () => {
|
|
184
|
+
const options = {
|
|
185
|
+
routeBasePath: 'api',
|
|
186
|
+
currentVersion: 'v3',
|
|
187
|
+
availableVersions: ['v2', 'v3'],
|
|
188
|
+
}
|
|
189
|
+
const tree = createTree('@v2:/endpoint')
|
|
190
|
+
rehypeVersionLinks(options)(tree)
|
|
191
|
+
expect(getHref(tree)).toBe('/api/v2/endpoint')
|
|
192
|
+
})
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
describe('edge cases', () => {
|
|
196
|
+
it('should handle empty href', () => {
|
|
197
|
+
const tree: Root = {
|
|
198
|
+
type: 'root',
|
|
199
|
+
children: [
|
|
200
|
+
{
|
|
201
|
+
type: 'element',
|
|
202
|
+
tagName: 'a',
|
|
203
|
+
properties: { href: '' },
|
|
204
|
+
children: [],
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
}
|
|
208
|
+
rehypeVersionLinks(defaultOptions)(tree)
|
|
209
|
+
expect(getHref(tree)).toBe('')
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
it('should handle anchor without href', () => {
|
|
213
|
+
const tree: Root = {
|
|
214
|
+
type: 'root',
|
|
215
|
+
children: [
|
|
216
|
+
{
|
|
217
|
+
type: 'element',
|
|
218
|
+
tagName: 'a',
|
|
219
|
+
properties: {},
|
|
220
|
+
children: [],
|
|
221
|
+
},
|
|
222
|
+
],
|
|
223
|
+
}
|
|
224
|
+
rehypeVersionLinks(defaultOptions)(tree)
|
|
225
|
+
expect(getHref(tree)).toBeUndefined()
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
it('should handle non-anchor elements', () => {
|
|
229
|
+
const tree: Root = {
|
|
230
|
+
type: 'root',
|
|
231
|
+
children: [
|
|
232
|
+
{
|
|
233
|
+
type: 'element',
|
|
234
|
+
tagName: 'div',
|
|
235
|
+
properties: { href: '/docs/page' },
|
|
236
|
+
children: [],
|
|
237
|
+
},
|
|
238
|
+
],
|
|
239
|
+
}
|
|
240
|
+
rehypeVersionLinks(defaultOptions)(tree)
|
|
241
|
+
// Should not modify non-anchor elements
|
|
242
|
+
expect((tree.children[0] as Element).properties?.href).toBe('/docs/page')
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
it('should handle docs path that matches version name', () => {
|
|
246
|
+
const options = {
|
|
247
|
+
routeBasePath: 'docs',
|
|
248
|
+
currentVersion: 'v2',
|
|
249
|
+
availableVersions: ['v1', 'v2', 'beta'],
|
|
250
|
+
}
|
|
251
|
+
// Path starting with 'beta' which is a version
|
|
252
|
+
const tree = createTree('/docs/beta/page')
|
|
253
|
+
rehypeVersionLinks(options)(tree)
|
|
254
|
+
expect(getHref(tree)).toBe('/docs/beta/page')
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
it('should handle index path', () => {
|
|
258
|
+
const tree = createTree('/docs/index')
|
|
259
|
+
rehypeVersionLinks(defaultOptions)(tree)
|
|
260
|
+
expect(getHref(tree)).toBe('/docs/v2/index')
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
it('should handle root docs path', () => {
|
|
264
|
+
const tree = createTree('/docs/')
|
|
265
|
+
rehypeVersionLinks(defaultOptions)(tree)
|
|
266
|
+
expect(getHref(tree)).toBe('/docs/v2/')
|
|
267
|
+
})
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
describe('multiple links in tree', () => {
|
|
271
|
+
it('should transform all links in the tree', () => {
|
|
272
|
+
const tree: Root = {
|
|
273
|
+
type: 'root',
|
|
274
|
+
children: [
|
|
275
|
+
{
|
|
276
|
+
type: 'element',
|
|
277
|
+
tagName: 'a',
|
|
278
|
+
properties: { href: '/docs/page1' },
|
|
279
|
+
children: [],
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
type: 'element',
|
|
283
|
+
tagName: 'p',
|
|
284
|
+
properties: {},
|
|
285
|
+
children: [
|
|
286
|
+
{
|
|
287
|
+
type: 'element',
|
|
288
|
+
tagName: 'a',
|
|
289
|
+
properties: { href: '@v1:/page2' },
|
|
290
|
+
children: [],
|
|
291
|
+
},
|
|
292
|
+
],
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
type: 'element',
|
|
296
|
+
tagName: 'a',
|
|
297
|
+
properties: { href: 'https://external.com' },
|
|
298
|
+
children: [],
|
|
299
|
+
},
|
|
300
|
+
],
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
rehypeVersionLinks(defaultOptions)(tree)
|
|
304
|
+
|
|
305
|
+
// First link auto-versioned
|
|
306
|
+
expect((tree.children[0] as Element).properties?.href).toBe(
|
|
307
|
+
'/docs/v2/page1',
|
|
308
|
+
)
|
|
309
|
+
// Nested link cross-version transformed
|
|
310
|
+
expect(
|
|
311
|
+
((tree.children[1] as Element).children[0] as Element).properties?.href,
|
|
312
|
+
).toBe('/docs/v1/page2')
|
|
313
|
+
// External link unchanged
|
|
314
|
+
expect((tree.children[2] as Element).properties?.href).toBe(
|
|
315
|
+
'https://external.com',
|
|
316
|
+
)
|
|
317
|
+
})
|
|
318
|
+
})
|
|
319
|
+
})
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import type { Root } from 'hast'
|
|
2
|
+
import { visit } from 'unist-util-visit'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Options for the rehype version links plugin.
|
|
6
|
+
*/
|
|
7
|
+
export interface RehypeVersionLinksOptions {
|
|
8
|
+
/**
|
|
9
|
+
* The base path for docs routes (e.g., 'docs', 'guides').
|
|
10
|
+
*/
|
|
11
|
+
routeBasePath: string
|
|
12
|
+
/**
|
|
13
|
+
* The current version being rendered.
|
|
14
|
+
* Links without explicit version will resolve to this version.
|
|
15
|
+
*/
|
|
16
|
+
currentVersion: string
|
|
17
|
+
/**
|
|
18
|
+
* Available versions for validation.
|
|
19
|
+
* Cross-version links to non-existent versions will log warnings.
|
|
20
|
+
*/
|
|
21
|
+
availableVersions?: string[]
|
|
22
|
+
/**
|
|
23
|
+
* Enable verbose logging for link transformations.
|
|
24
|
+
* @default false
|
|
25
|
+
*/
|
|
26
|
+
debug?: boolean
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Cross-version link syntax: @version:/path
|
|
31
|
+
* Examples:
|
|
32
|
+
* - @v1:/installation → /docs/v1/installation
|
|
33
|
+
* - @v2:/guide/intro → /docs/v2/guide/intro
|
|
34
|
+
* - @latest:/getting-started → /docs/latest/getting-started
|
|
35
|
+
*/
|
|
36
|
+
const CROSS_VERSION_LINK_REGEX = /^@([^:]+):(.+)$/
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Rehype plugin to handle version-aware link resolution in documentation.
|
|
40
|
+
*
|
|
41
|
+
* Features:
|
|
42
|
+
* 1. Relative links (./page, ../other) resolve within the same version
|
|
43
|
+
* 2. Cross-version links (@v1:/path) allow explicit version targeting
|
|
44
|
+
* 3. Absolute docs links (/docs/page) automatically get versioned
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* // In astro.config.mjs
|
|
49
|
+
* export default defineConfig({
|
|
50
|
+
* markdown: {
|
|
51
|
+
* rehypePlugins: [
|
|
52
|
+
* [rehypeVersionLinks, {
|
|
53
|
+
* routeBasePath: 'docs',
|
|
54
|
+
* currentVersion: 'v2',
|
|
55
|
+
* availableVersions: ['v1', 'v2', 'latest'],
|
|
56
|
+
* }]
|
|
57
|
+
* ]
|
|
58
|
+
* }
|
|
59
|
+
* })
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export function rehypeVersionLinks(options: RehypeVersionLinksOptions) {
|
|
63
|
+
const {
|
|
64
|
+
routeBasePath,
|
|
65
|
+
currentVersion,
|
|
66
|
+
availableVersions = [],
|
|
67
|
+
debug,
|
|
68
|
+
} = options
|
|
69
|
+
|
|
70
|
+
return (tree: Root) => {
|
|
71
|
+
visit(tree, 'element', (node) => {
|
|
72
|
+
// Only process anchor elements
|
|
73
|
+
if (node.tagName !== 'a') return
|
|
74
|
+
|
|
75
|
+
const href = node.properties?.href
|
|
76
|
+
if (typeof href !== 'string') return
|
|
77
|
+
|
|
78
|
+
// Skip external links, anchors, and protocol links
|
|
79
|
+
if (
|
|
80
|
+
href.startsWith('http://') ||
|
|
81
|
+
href.startsWith('https://') ||
|
|
82
|
+
href.startsWith('mailto:') ||
|
|
83
|
+
href.startsWith('#') ||
|
|
84
|
+
href.startsWith('tel:')
|
|
85
|
+
) {
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let newHref = href
|
|
90
|
+
|
|
91
|
+
// Check for cross-version link syntax: @version:/path
|
|
92
|
+
const crossVersionMatch = href.match(CROSS_VERSION_LINK_REGEX)
|
|
93
|
+
if (crossVersionMatch) {
|
|
94
|
+
const [, targetVersion, targetPath] = crossVersionMatch
|
|
95
|
+
const cleanPath = targetPath.startsWith('/')
|
|
96
|
+
? targetPath.slice(1)
|
|
97
|
+
: targetPath
|
|
98
|
+
|
|
99
|
+
// Validate version exists
|
|
100
|
+
if (
|
|
101
|
+
availableVersions.length > 0 &&
|
|
102
|
+
!availableVersions.includes(targetVersion) &&
|
|
103
|
+
targetVersion !== 'latest'
|
|
104
|
+
) {
|
|
105
|
+
// biome-ignore lint/suspicious/noConsole: Intentional warning for invalid cross-version links
|
|
106
|
+
console.warn(
|
|
107
|
+
`[rehypeVersionLinks] Unknown version "${targetVersion}" in link: ${href}`,
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
newHref = `/${routeBasePath}/${targetVersion}/${cleanPath}`
|
|
112
|
+
|
|
113
|
+
if (debug) {
|
|
114
|
+
// biome-ignore lint/suspicious/noConsole: Debug logging when enabled
|
|
115
|
+
console.log(
|
|
116
|
+
`[rehypeVersionLinks] Cross-version link: ${href} → ${newHref}`,
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Check if it's an absolute path starting with the docs base path
|
|
121
|
+
// e.g., /docs/installation or /docs/guide/intro
|
|
122
|
+
else if (href.startsWith(`/${routeBasePath}/`)) {
|
|
123
|
+
const pathAfterBase = href.slice(`/${routeBasePath}/`.length)
|
|
124
|
+
|
|
125
|
+
// Check if it already has a version
|
|
126
|
+
const firstSegment = pathAfterBase.split('/')[0]
|
|
127
|
+
const hasVersion =
|
|
128
|
+
availableVersions.includes(firstSegment) ||
|
|
129
|
+
firstSegment === 'latest' ||
|
|
130
|
+
firstSegment === currentVersion
|
|
131
|
+
|
|
132
|
+
if (!hasVersion) {
|
|
133
|
+
// Add the current version to the path
|
|
134
|
+
newHref = `/${routeBasePath}/${currentVersion}/${pathAfterBase}`
|
|
135
|
+
|
|
136
|
+
if (debug) {
|
|
137
|
+
// biome-ignore lint/suspicious/noConsole: Debug logging when enabled
|
|
138
|
+
console.log(
|
|
139
|
+
`[rehypeVersionLinks] Auto-versioned link: ${href} → ${newHref}`,
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Relative paths are handled by the browser/Astro based on current URL
|
|
145
|
+
// We don't need to transform them since they'll naturally stay in the same version
|
|
146
|
+
|
|
147
|
+
// Update the href if changed
|
|
148
|
+
if (newHref !== href) {
|
|
149
|
+
node.properties = node.properties ?? {}
|
|
150
|
+
node.properties.href = newHref
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export default rehypeVersionLinks
|