Skip to content

Commit a7a09a6

Browse files
authored
Add buildUrl option for GH enterprise, custom URLs
Reviewed-by: Titus Wormer <[email protected]> Closes GH-23. Closes GH-31.
1 parent dc91b18 commit a7a09a6

File tree

4 files changed

+228
-31
lines changed

4 files changed

+228
-31
lines changed

index.js

Lines changed: 114 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,52 @@
11
/**
22
* @typedef {import('mdast').Root} Root
33
*
4+
* @callback DefaultBuildUrl
5+
* @param {BuildUrlValues} values
6+
* @returns {string}
7+
*
8+
* @callback BuildUrl
9+
* @param {BuildUrlValues} values
10+
* @param {DefaultBuildUrl} defaultBuildUrl
11+
* @returns {string}
12+
*
13+
* @typedef {BuildUrlCommitValues|BuildUrlCompareValues|BuildUrlIssueValues|BuildUrlMentionValues} BuildUrlValues
14+
*
15+
* @typedef BuildUrlCommitValues
16+
* Arguments for buildUrl functions for commit hash
17+
* @property {'commit'} type The type of special object
18+
* @property {string} hash The commit hash value
19+
* @property {string} project The project of the repo
20+
* @property {string} user The owner of the repo
21+
*
22+
* @typedef BuildUrlCompareValues
23+
* Arguments for buildUrl functions for commit hash ranges
24+
* @property {'compare'} type The type of special object
25+
* @property {string} base The SHA of the range start
26+
* @property {string} compare The SHA of the range end
27+
* @property {string} project The project of the repo
28+
* @property {string} user The owner of the repo
29+
*
30+
* @typedef BuildUrlIssueValues
31+
* Arguments for buildUrl functions for issues
32+
* @property {'issue'} type The type of special object
33+
* @property {string} no The parsed issue number
34+
* @property {string} project The project of the repo
35+
* @property {string} user The owner of the repo
36+
*
37+
* @typedef BuildUrlMentionValues
38+
* Arguments for buildUrl functions for mentions
39+
* @property {'mention'} type The type of special object
40+
* @property {string} user The parsed user name
41+
*
42+
* @typedef RepositoryInfo
43+
* The owner and project of the repo
44+
* @property {string} project The project/repo name
45+
* @property {string} user The user/organization name
46+
*
447
* @typedef Options
548
* Configuration.
49+
* @property {BuildUrl} [buildUrl]
650
* @property {boolean} [mentionStrong=true]
751
* Wrap mentions in `<strong>`, true by default.
852
* This makes them render more like how GitHub styles them.
@@ -118,13 +162,23 @@ export default function remarkGithub(options = {}) {
118162
: ''
119163
}
120164

165+
/**
166+
* @param {BuildUrlValues} values
167+
* @returns {string}
168+
*/
169+
function buildUrl(values) {
170+
if (options.buildUrl) return options.buildUrl(values, defaultBuildUrl)
171+
return defaultBuildUrl(values)
172+
}
173+
121174
// Parse the URL: See the tests for all possible kinds.
122175
const repositoryMatch = repoRegex.exec(repository || '')
123176

124177
if (!repositoryMatch) {
125178
throw new Error('Missing `repository` field in `options`')
126179
}
127180

181+
/** @type {RepositoryInfo} */
128182
const repositoryInfo = {user: repositoryMatch[1], project: repositoryMatch[2]}
129183

130184
return (tree) => {
@@ -213,7 +267,7 @@ export default function remarkGithub(options = {}) {
213267
return {
214268
type: 'link',
215269
title: null,
216-
url: 'https://github.com/' + username,
270+
url: buildUrl({type: 'mention', user: username}),
217271
children: [node]
218272
}
219273
}
@@ -235,13 +289,12 @@ export default function remarkGithub(options = {}) {
235289
return {
236290
type: 'link',
237291
title: null,
238-
url:
239-
'https://github.com/' +
240-
repositoryInfo.user +
241-
'/' +
242-
repositoryInfo.project +
243-
'/issues/' +
292+
url: buildUrl({
293+
type: 'issue',
244294
no,
295+
project: repositoryInfo.project,
296+
user: repositoryInfo.user
297+
}),
245298
children: [{type: 'text', value}]
246299
}
247300
}
@@ -265,13 +318,13 @@ export default function remarkGithub(options = {}) {
265318
return {
266319
type: 'link',
267320
title: null,
268-
url:
269-
'https://github.com/' +
270-
repositoryInfo.user +
271-
'/' +
272-
repositoryInfo.project +
273-
'/compare/' +
274-
value,
321+
url: buildUrl({
322+
type: 'compare',
323+
base: a,
324+
compare: b,
325+
project: repositoryInfo.project,
326+
user: repositoryInfo.user
327+
}),
275328
children: [{type: 'inlineCode', value: abbr(a) + '...' + abbr(b)}]
276329
}
277330
}
@@ -296,13 +349,12 @@ export default function remarkGithub(options = {}) {
296349
return {
297350
type: 'link',
298351
title: null,
299-
url:
300-
'https://github.com/' +
301-
repositoryInfo.user +
302-
'/' +
303-
repositoryInfo.project +
304-
'/commit/' +
305-
value,
352+
url: buildUrl({
353+
type: 'commit',
354+
project: repositoryInfo.project,
355+
user: repositoryInfo.user,
356+
hash: value
357+
}),
306358
children: [{type: 'inlineCode', value: abbr(value)}]
307359
}
308360
}
@@ -350,15 +402,19 @@ export default function remarkGithub(options = {}) {
350402
return {
351403
type: 'link',
352404
title: null,
353-
url:
354-
'https://github.com/' +
355-
user +
356-
'/' +
357-
(project || repositoryInfo.project) +
358-
'/' +
359-
(no ? 'issues' : 'commit') +
360-
'/' +
361-
(no || sha),
405+
url: no
406+
? buildUrl({
407+
type: 'issue',
408+
no,
409+
project: project || repositoryInfo.project,
410+
user
411+
})
412+
: buildUrl({
413+
type: 'commit',
414+
hash: sha,
415+
project: project || repositoryInfo.project,
416+
user
417+
}),
362418
children: nodes
363419
}
364420
}
@@ -374,6 +430,34 @@ function abbr(sha) {
374430
return sha.slice(0, minShaLength)
375431
}
376432

433+
/**
434+
* Given a set of values based on the values type, returns link URL.
435+
*
436+
* @type {DefaultBuildUrl}
437+
*/
438+
function defaultBuildUrl(values) {
439+
const base = 'https://github.com'
440+
441+
if (values.type === 'mention') return [base, values.user].join('/')
442+
443+
const {project, user} = values
444+
445+
if (values.type === 'commit')
446+
return [base, user, project, 'commit', values.hash].join('/')
447+
448+
if (values.type === 'issue')
449+
return [base, user, project, 'issues', values.no].join('/')
450+
451+
// This should only run if values.type is 'compare'
452+
return [
453+
base,
454+
user,
455+
project,
456+
'compare',
457+
values.base + '...' + values.compare
458+
].join('/')
459+
}
460+
377461
/**
378462
* Parse a link and determine whether it links to GitHub.
379463
*

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
"author": "Titus Wormer <[email protected]> (https://wooorm.com)",
3131
"contributors": [
3232
"Titus Wormer <[email protected]> (https://wooorm.com)",
33-
"Anthony Maki <[email protected]>"
33+
"Anthony Maki <[email protected]>",
34+
"Ev Haus <[email protected]>"
3435
],
3536
"sideEffects": false,
3637
"type": "module",

readme.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,30 @@ in GitHub issues, PRs, and comments (see
134134
* At-mentions:
135135
`@wooorm`[**@wooorm**][mention]
136136

137+
##### Custom URLs
138+
139+
By default we build URLs to public GitHub.
140+
You can overwrite them to point to GitHub Enterprise or other places by passing
141+
a `buildUrl`.
142+
That function is given an object with different values and the default
143+
`buildUrl`.
144+
145+
```js
146+
remark()
147+
.use(remarkGithub, {
148+
// The fields in `values` depends on the kind reference:
149+
// {type: 'commit', hash, project, user}
150+
// {type: 'hashrange', base, compare, project, user}
151+
// {type: 'issue', no, project, user}
152+
// {type: 'mention', user}
153+
buildUrl: (values, defaultBuildUrl) => {
154+
return values.type === 'mention'
155+
? `https://yourwebsite.com/${values.user}/`
156+
: defaultBuildUrl(values)
157+
}
158+
})
159+
```
160+
137161
###### Repository
138162

139163
These links are generated relative to a project.

test/index.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,94 @@ test('Repositories', (t) => {
177177
t.end()
178178
})
179179

180+
test('Custom URL builder option', (t) => {
181+
t.equal(
182+
github('@wooorm', {
183+
buildUrl: (values, defaultBuildUrl) => {
184+
return values.type === 'mention'
185+
? `https://github.yourcompany.com/${values.user}/`
186+
: defaultBuildUrl(values)
187+
}
188+
}),
189+
'[**@wooorm**](https://github.yourcompany.com/wooorm/)\n',
190+
'should support custom `baseUrl` function value for mentions'
191+
)
192+
193+
t.equal(
194+
github('#123', {
195+
buildUrl: (values, defaultBuildUrl) => {
196+
return values.type === 'issue'
197+
? `https://github.yourcompany.com/${values.user}/${values.project}/issues/${values.no}`
198+
: defaultBuildUrl(values)
199+
}
200+
}),
201+
'[#123](https://github.yourcompany.com/remarkjs/remark-github/issues/123)\n',
202+
'should support custom `baseUrl` function value for issues'
203+
)
204+
205+
t.equal(
206+
github('GH-1', {
207+
buildUrl: (values, defaultBuildUrl) => {
208+
return values.type === 'issue'
209+
? `https://github.yourcompany.com/${values.user}/${values.project}/issues/${values.no}`
210+
: defaultBuildUrl(values)
211+
}
212+
}),
213+
'[GH-1](https://github.yourcompany.com/remarkjs/remark-github/issues/1)\n',
214+
'should support custom `baseUrl` function value for non-standard issues'
215+
)
216+
217+
t.equal(
218+
github('e2acebc...2aa9311', {
219+
buildUrl: (values, defaultBuildUrl) => {
220+
return values.type === 'compare'
221+
? `https://github.yourcompany.com/${values.user}/${values.project}/compare/${values.base}...${values.compare}`
222+
: defaultBuildUrl(values)
223+
}
224+
}),
225+
'[`e2acebc...2aa9311`](https://github.yourcompany.com/remarkjs/remark-github/compare/e2acebc...2aa9311)\n',
226+
'should support custom `baseUrl` function value for hash ranges'
227+
)
228+
229+
t.equal(
230+
github('1f2a4fb', {
231+
buildUrl: (values, defaultBuildUrl) => {
232+
return values.type === 'commit'
233+
? `https://github.yourcompany.com/${values.user}/${values.project}/commit/${values.hash}`
234+
: defaultBuildUrl(values)
235+
}
236+
}),
237+
'[`1f2a4fb`](https://github.yourcompany.com/remarkjs/remark-github/commit/1f2a4fb)\n',
238+
'should support custom `baseUrl` function value for commit hashes'
239+
)
240+
241+
t.equal(
242+
github('remarkjs/remark-github#1', {
243+
buildUrl: (values, defaultBuildUrl) => {
244+
return values.type === 'issue'
245+
? `https://github.yourcompany.com/${values.user}/${values.project}/issues/${values.no}`
246+
: defaultBuildUrl(values)
247+
}
248+
}),
249+
'[#1](https://github.yourcompany.com/remarkjs/remark-github/issues/1)\n',
250+
'should support custom `baseUrl` function value for cross-project issue references'
251+
)
252+
253+
t.equal(
254+
github('remarkjs/remark-github@1f2a4fb', {
255+
buildUrl: (values, defaultBuildUrl) => {
256+
return values.type === 'commit'
257+
? `https://github.yourcompany.com/${values.user}/${values.project}/commit/${values.hash}`
258+
: defaultBuildUrl(values)
259+
}
260+
}),
261+
'[@`1f2a4fb`](https://github.yourcompany.com/remarkjs/remark-github/commit/1f2a4fb)\n',
262+
'should support custom `baseUrl` function value for cross-project hash references'
263+
)
264+
265+
t.end()
266+
})
267+
180268
test('Miscellaneous', (t) => {
181269
const original = process.cwd()
182270

0 commit comments

Comments
 (0)