@primer/mcp 1.1.0 → 1.1.1-rc.242ba780a

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/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { n as server, t as createServer } from "./server-CnGefk-j.js";
1
+ import { n as server, t as createServer } from "./server-malh_dny.js";
2
2
  export { createServer, server };
@@ -19,6 +19,7 @@ import functionalTypography from "@primer/primitives/dist/docs/functional/typogr
19
19
  //#region src/primer.ts
20
20
  function idToSlug(id) {
21
21
  if (id === "actionbar") return "action-bar";
22
+ if (id === "confirmationdialog") return "confirmation-dialog";
22
23
  if (id === "tooltip-v2") return "tooltip";
23
24
  if (id === "dialog_v2") return "dialog";
24
25
  return id.replaceAll("_", "-");
@@ -717,7 +718,7 @@ function runStylelint(css) {
717
718
  }
718
719
  //#endregion
719
720
  //#region package.json
720
- var version = "1.1.0";
721
+ var version = "1.1.1";
721
722
  //#endregion
722
723
  //#region src/server.ts
723
724
  const turndownService = new TurndownService();
@@ -872,7 +873,7 @@ ${turndownService.turndown(source)}`
872
873
  type: "text",
873
874
  text: `There is no component named \`${name}\` in the @primer/react package. For a full list of components, use the \`get_components\` tool.`
874
875
  }] };
875
- const url = new URL(`/product/components/${match.id}/guidelines`, "https://primer.style");
876
+ const url = new URL(`/product/components/${match.slug}/guidelines`, "https://primer.style");
876
877
  const response = await fetch(url);
877
878
  if (!response.ok) {
878
879
  if (response.status >= 400 && response.status < 500 || response.status >= 300 && response.status < 400) return { content: [{
package/dist/stdio.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as createServer } from "./server-CnGefk-j.js";
1
+ import { t as createServer } from "./server-malh_dny.js";
2
2
  import { serveStdio } from "@modelcontextprotocol/server/stdio";
3
3
  //#region src/transports/stdio.ts
4
4
  serveStdio(createServer);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@primer/mcp",
3
3
  "description": "An MCP server that connects AI tools to the Primer Design System",
4
- "version": "1.1.0",
4
+ "version": "1.1.1-rc.242ba780a",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "mcp": "./bin/mcp.js"
@@ -37,11 +37,11 @@
37
37
  "watch": "rolldown -c -w"
38
38
  },
39
39
  "dependencies": {
40
- "@modelcontextprotocol/client": "^2.0.0",
40
+ "@modelcontextprotocol/client": "^2.2.0",
41
41
  "@modelcontextprotocol/server": "^2.0.0",
42
42
  "@primer/octicons": "^19.15.5",
43
43
  "@primer/primitives": "10.x || 11.x",
44
- "@primer/react": "^38.38.0",
44
+ "@primer/react": "^38.42.0",
45
45
  "cheerio": "^1.0.0",
46
46
  "turndown": "^7.2.0",
47
47
  "zod": "^4.3.5"
@@ -51,7 +51,7 @@
51
51
  "@primer/vitest-config": "^0.0.0",
52
52
  "@types/turndown": "^5.0.5",
53
53
  "rimraf": "^6.0.1",
54
- "rolldown": "^1.2.5",
54
+ "rolldown": "^1.2.12",
55
55
  "rolldown-plugin-dts": "^0.26.0",
56
56
  "typescript": "^6.0.3"
57
57
  }
package/src/primer.ts CHANGED
@@ -13,6 +13,10 @@ function idToSlug(id: string): string {
13
13
  return 'action-bar'
14
14
  }
15
15
 
16
+ if (id === 'confirmationdialog') {
17
+ return 'confirmation-dialog'
18
+ }
19
+
16
20
  if (id === 'tooltip-v2') {
17
21
  return 'tooltip'
18
22
  }
@@ -2,6 +2,302 @@ import {Client, InMemoryTransport} from '@modelcontextprotocol/client'
2
2
  import {afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi} from 'vitest'
3
3
  import {server} from './server'
4
4
 
5
+ interface UsageGuidelineRoute {
6
+ name: string
7
+ expectedPath: string
8
+ rawIdPath?: string
9
+ publication: 'published' | 'unpublished'
10
+ }
11
+
12
+ const usageGuidelineRoutes: ReadonlyArray<UsageGuidelineRoute> = [
13
+ {
14
+ name: 'ActionBar',
15
+ expectedPath: '/product/components/action-bar/guidelines',
16
+ rawIdPath: '/product/components/actionbar/guidelines',
17
+ publication: 'published',
18
+ },
19
+ {
20
+ name: 'ActionList',
21
+ expectedPath: '/product/components/action-list/guidelines',
22
+ rawIdPath: '/product/components/action_list/guidelines',
23
+ publication: 'published',
24
+ },
25
+ {
26
+ name: 'ActionMenu',
27
+ expectedPath: '/product/components/action-menu/guidelines',
28
+ rawIdPath: '/product/components/action_menu/guidelines',
29
+ publication: 'published',
30
+ },
31
+ {
32
+ name: 'AnchoredOverlay',
33
+ expectedPath: '/product/components/anchored-overlay/guidelines',
34
+ rawIdPath: '/product/components/anchored_overlay/guidelines',
35
+ publication: 'unpublished',
36
+ },
37
+ {name: 'Autocomplete', expectedPath: '/product/components/autocomplete/guidelines', publication: 'published'},
38
+ {name: 'Avatar', expectedPath: '/product/components/avatar/guidelines', publication: 'unpublished'},
39
+ {
40
+ name: 'AvatarStack',
41
+ expectedPath: '/product/components/avatar-stack/guidelines',
42
+ rawIdPath: '/product/components/avatar_stack/guidelines',
43
+ publication: 'unpublished',
44
+ },
45
+ {name: 'Banner', expectedPath: '/product/components/banner/guidelines', publication: 'published'},
46
+ {name: 'Blankslate', expectedPath: '/product/components/blankslate/guidelines', publication: 'published'},
47
+ {
48
+ name: 'BranchName',
49
+ expectedPath: '/product/components/branch-name/guidelines',
50
+ rawIdPath: '/product/components/branch_name/guidelines',
51
+ publication: 'unpublished',
52
+ },
53
+ {name: 'Breadcrumbs', expectedPath: '/product/components/breadcrumbs/guidelines', publication: 'published'},
54
+ {name: 'Button', expectedPath: '/product/components/button/guidelines', publication: 'published'},
55
+ {
56
+ name: 'IconButton',
57
+ expectedPath: '/product/components/icon-button/guidelines',
58
+ rawIdPath: '/product/components/icon_button/guidelines',
59
+ publication: 'published',
60
+ },
61
+ {
62
+ name: 'ButtonGroup',
63
+ expectedPath: '/product/components/button-group/guidelines',
64
+ rawIdPath: '/product/components/button_group/guidelines',
65
+ publication: 'published',
66
+ },
67
+ {name: 'Card', expectedPath: '/product/components/card/guidelines', publication: 'unpublished'},
68
+ {name: 'Checkbox', expectedPath: '/product/components/checkbox/guidelines', publication: 'unpublished'},
69
+ {
70
+ name: 'CheckboxGroup',
71
+ expectedPath: '/product/components/checkbox-group/guidelines',
72
+ rawIdPath: '/product/components/checkbox_group/guidelines',
73
+ publication: 'published',
74
+ },
75
+ {
76
+ name: 'CircleBadge',
77
+ expectedPath: '/product/components/circle-badge/guidelines',
78
+ rawIdPath: '/product/components/circle_badge/guidelines',
79
+ publication: 'unpublished',
80
+ },
81
+ {
82
+ name: 'ConfirmationDialog',
83
+ expectedPath: '/product/components/confirmation-dialog/guidelines',
84
+ rawIdPath: '/product/components/confirmationdialog/guidelines',
85
+ publication: 'published',
86
+ },
87
+ {
88
+ name: 'CounterLabel',
89
+ expectedPath: '/product/components/counter-label/guidelines',
90
+ rawIdPath: '/product/components/counter_label/guidelines',
91
+ publication: 'unpublished',
92
+ },
93
+ {
94
+ name: 'DataTable',
95
+ expectedPath: '/product/components/data-table/guidelines',
96
+ rawIdPath: '/product/components/data_table/guidelines',
97
+ publication: 'published',
98
+ },
99
+ {name: 'Details', expectedPath: '/product/components/details/guidelines', publication: 'unpublished'},
100
+ {
101
+ name: 'Dialog',
102
+ expectedPath: '/product/components/dialog/guidelines',
103
+ rawIdPath: '/product/components/dialog_v2/guidelines',
104
+ publication: 'published',
105
+ },
106
+ {
107
+ name: 'FeatureFlags',
108
+ expectedPath: '/product/components/feature-flags/guidelines',
109
+ rawIdPath: '/product/components/feature_flags/guidelines',
110
+ publication: 'unpublished',
111
+ },
112
+ {name: 'Flash', expectedPath: '/product/components/flash/guidelines', publication: 'unpublished'},
113
+ {
114
+ name: 'FormControl',
115
+ expectedPath: '/product/components/form-control/guidelines',
116
+ rawIdPath: '/product/components/form_control/guidelines',
117
+ publication: 'unpublished',
118
+ },
119
+ {name: 'Header', expectedPath: '/product/components/header/guidelines', publication: 'unpublished'},
120
+ {name: 'Heading', expectedPath: '/product/components/heading/guidelines', publication: 'unpublished'},
121
+ {name: 'Hidden', expectedPath: '/product/components/hidden/guidelines', publication: 'unpublished'},
122
+ {
123
+ name: 'InlineMessage',
124
+ expectedPath: '/product/components/inline-message/guidelines',
125
+ rawIdPath: '/product/components/inline_message/guidelines',
126
+ publication: 'unpublished',
127
+ },
128
+ {name: 'KeybindingHint', expectedPath: '/product/components/KeybindingHint/guidelines', publication: 'unpublished'},
129
+ {name: 'Label', expectedPath: '/product/components/label/guidelines', publication: 'unpublished'},
130
+ {
131
+ name: 'LabelGroup',
132
+ expectedPath: '/product/components/label-group/guidelines',
133
+ rawIdPath: '/product/components/label_group/guidelines',
134
+ publication: 'unpublished',
135
+ },
136
+ {name: 'Link', expectedPath: '/product/components/link/guidelines', publication: 'published'},
137
+ {
138
+ name: 'NavList',
139
+ expectedPath: '/product/components/nav-list/guidelines',
140
+ rawIdPath: '/product/components/nav_list/guidelines',
141
+ publication: 'unpublished',
142
+ },
143
+ {name: 'Octicon', expectedPath: '/product/components/octicon/guidelines', publication: 'unpublished'},
144
+ {name: 'Overlay', expectedPath: '/product/components/overlay/guidelines', publication: 'unpublished'},
145
+ {
146
+ name: 'PageHeader',
147
+ expectedPath: '/product/components/page-header/guidelines',
148
+ rawIdPath: '/product/components/page_header/guidelines',
149
+ publication: 'published',
150
+ },
151
+ {
152
+ name: 'PageLayout',
153
+ expectedPath: '/product/components/page-layout/guidelines',
154
+ rawIdPath: '/product/components/page_layout/guidelines',
155
+ publication: 'unpublished',
156
+ },
157
+ {name: 'Pagehead', expectedPath: '/product/components/pagehead/guidelines', publication: 'unpublished'},
158
+ {name: 'Pagination', expectedPath: '/product/components/pagination/guidelines', publication: 'published'},
159
+ {name: 'Popover', expectedPath: '/product/components/popover/guidelines', publication: 'published'},
160
+ {name: 'Portal', expectedPath: '/product/components/portal/guidelines', publication: 'unpublished'},
161
+ {
162
+ name: 'ProgressBar',
163
+ expectedPath: '/product/components/progress-bar/guidelines',
164
+ rawIdPath: '/product/components/progress_bar/guidelines',
165
+ publication: 'unpublished',
166
+ },
167
+ {name: 'Radio', expectedPath: '/product/components/radio/guidelines', publication: 'unpublished'},
168
+ {
169
+ name: 'RadioGroup',
170
+ expectedPath: '/product/components/radio-group/guidelines',
171
+ rawIdPath: '/product/components/radio_group/guidelines',
172
+ publication: 'published',
173
+ },
174
+ {
175
+ name: 'RelativeTime',
176
+ expectedPath: '/product/components/relative-time/guidelines',
177
+ rawIdPath: '/product/components/relative_time/guidelines',
178
+ publication: 'published',
179
+ },
180
+ {
181
+ name: 'SegmentedControl',
182
+ expectedPath: '/product/components/segmented-control/guidelines',
183
+ rawIdPath: '/product/components/segmented_control/guidelines',
184
+ publication: 'published',
185
+ },
186
+ {name: 'Select', expectedPath: '/product/components/select/guidelines', publication: 'published'},
187
+ {
188
+ name: 'SelectPanel',
189
+ expectedPath: '/product/components/select-panel/guidelines',
190
+ rawIdPath: '/product/components/select_panel/guidelines',
191
+ publication: 'published',
192
+ },
193
+ {
194
+ name: 'SkeletonBox',
195
+ expectedPath: '/product/components/skeleton-box/guidelines',
196
+ rawIdPath: '/product/components/skeleton_box/guidelines',
197
+ publication: 'unpublished',
198
+ },
199
+ {
200
+ name: 'SkeletonAvatar',
201
+ expectedPath: '/product/components/skeleton-avatar/guidelines',
202
+ rawIdPath: '/product/components/skeleton_avatar/guidelines',
203
+ publication: 'unpublished',
204
+ },
205
+ {
206
+ name: 'SkeletonText',
207
+ expectedPath: '/product/components/skeleton-text/guidelines',
208
+ rawIdPath: '/product/components/skeleton_text/guidelines',
209
+ publication: 'unpublished',
210
+ },
211
+ {name: 'Spinner', expectedPath: '/product/components/spinner/guidelines', publication: 'unpublished'},
212
+ {
213
+ name: 'SplitPageLayout',
214
+ expectedPath: '/product/components/split-page-layout/guidelines',
215
+ rawIdPath: '/product/components/split_page_layout/guidelines',
216
+ publication: 'unpublished',
217
+ },
218
+ {name: 'Stack', expectedPath: '/product/components/stack/guidelines', publication: 'unpublished'},
219
+ {
220
+ name: 'StateLabel',
221
+ expectedPath: '/product/components/state-label/guidelines',
222
+ rawIdPath: '/product/components/state_label/guidelines',
223
+ publication: 'unpublished',
224
+ },
225
+ {
226
+ name: 'SubNav',
227
+ expectedPath: '/product/components/sub-nav/guidelines',
228
+ rawIdPath: '/product/components/sub_nav/guidelines',
229
+ publication: 'unpublished',
230
+ },
231
+ {
232
+ name: 'TabNav',
233
+ expectedPath: '/product/components/tab-nav/guidelines',
234
+ rawIdPath: '/product/components/tab_nav/guidelines',
235
+ publication: 'unpublished',
236
+ },
237
+ {name: 'Text', expectedPath: '/product/components/text/guidelines', publication: 'unpublished'},
238
+ {
239
+ name: 'TextInput',
240
+ expectedPath: '/product/components/text-input/guidelines',
241
+ rawIdPath: '/product/components/text_input/guidelines',
242
+ publication: 'published',
243
+ },
244
+ {
245
+ name: 'TextInputWithTokens',
246
+ expectedPath: '/product/components/text-input-with-tokens/guidelines',
247
+ rawIdPath: '/product/components/text_input_with_tokens/guidelines',
248
+ publication: 'published',
249
+ },
250
+ {name: 'Textarea', expectedPath: '/product/components/textarea/guidelines', publication: 'published'},
251
+ {name: 'Timeline', expectedPath: '/product/components/timeline/guidelines', publication: 'unpublished'},
252
+ {
253
+ name: 'ToggleSwitch',
254
+ expectedPath: '/product/components/toggle-switch/guidelines',
255
+ rawIdPath: '/product/components/toggle_switch/guidelines',
256
+ publication: 'published',
257
+ },
258
+ {name: 'Token', expectedPath: '/product/components/token/guidelines', publication: 'published'},
259
+ {name: 'Tooltip', expectedPath: '/product/components/tooltip/guidelines', publication: 'published'},
260
+ {
261
+ name: 'TopicTag',
262
+ expectedPath: '/product/components/topic-tag/guidelines',
263
+ rawIdPath: '/product/components/topic_tag/guidelines',
264
+ publication: 'unpublished',
265
+ },
266
+ {
267
+ name: 'TreeView',
268
+ expectedPath: '/product/components/tree-view/guidelines',
269
+ rawIdPath: '/product/components/tree_view/guidelines',
270
+ publication: 'published',
271
+ },
272
+ {name: 'Truncate', expectedPath: '/product/components/truncate/guidelines', publication: 'unpublished'},
273
+ {
274
+ name: 'UnderlineNav',
275
+ expectedPath: '/product/components/underline-nav/guidelines',
276
+ rawIdPath: '/product/components/underline_nav/guidelines',
277
+ publication: 'published',
278
+ },
279
+ {name: 'AriaAlert', expectedPath: '/product/components/aria-alert/guidelines', publication: 'unpublished'},
280
+ {name: 'AriaStatus', expectedPath: '/product/components/aria-status/guidelines', publication: 'unpublished'},
281
+ {
282
+ name: 'FilteredSearch',
283
+ expectedPath: '/product/components/filtered-search/guidelines',
284
+ rawIdPath: '/product/components/filtered_search/guidelines',
285
+ publication: 'unpublished',
286
+ },
287
+ {
288
+ name: 'IssueLabel',
289
+ expectedPath: '/product/components/issue-label/guidelines',
290
+ rawIdPath: '/product/components/issue_label/guidelines',
291
+ publication: 'unpublished',
292
+ },
293
+ {
294
+ name: 'UnderlinePanels',
295
+ expectedPath: '/product/components/underline-panels/guidelines',
296
+ rawIdPath: '/product/components/underline_panels/guidelines',
297
+ publication: 'published',
298
+ },
299
+ ]
300
+
5
301
  describe('MCP server', () => {
6
302
  const client = new Client(
7
303
  {name: 'mcp-server-test', version: '1.0.0'},
@@ -50,6 +346,12 @@ describe('MCP server', () => {
50
346
  })
51
347
  }
52
348
 
349
+ const responseWithFinalUrl = (body: string, url: string) => {
350
+ const response = new Response(body)
351
+ Object.defineProperty(response, 'url', {value: url})
352
+ return response
353
+ }
354
+
53
355
  it('documents which component tool to use for each supported count', async () => {
54
356
  const {tools} = await client.listTools()
55
357
  const getComponent = tools.find(tool => tool.name === 'get_component')
@@ -110,6 +412,142 @@ describe('MCP server', () => {
110
412
  )
111
413
  })
112
414
 
415
+ it('covers every advertised component with a named usage-guidelines routing case', async () => {
416
+ const result = await client.callTool({name: 'list_components'})
417
+ expect(result.isError).not.toBe(true)
418
+ expect(result.content).toHaveLength(1)
419
+
420
+ const [content] = result.content
421
+ if (content.type !== 'text') {
422
+ throw new Error('Expected list_components to return a text component list')
423
+ }
424
+
425
+ const names = content.text
426
+ .split('\n')
427
+ .filter(line => line.startsWith('- '))
428
+ .map(line => line.slice(2))
429
+
430
+ expect([...new Set(names)].sort()).toEqual(usageGuidelineRoutes.map(route => route.name).sort())
431
+ expect(names.filter((name, index) => names.indexOf(name) !== index).sort()).toEqual([
432
+ 'Dialog',
433
+ 'SelectPanel',
434
+ 'Tooltip',
435
+ 'UnderlineNav',
436
+ ])
437
+ expect(fetch).not.toHaveBeenCalled()
438
+ })
439
+
440
+ it.each(usageGuidelineRoutes)(
441
+ '$name usage request follows its expected route (guidelines $publication)',
442
+ async ({name, expectedPath, rawIdPath}) => {
443
+ const expectedUrl = `https://primer.style${expectedPath}`
444
+ const rawIdUrl = rawIdPath ? `https://primer.style${rawIdPath}` : undefined
445
+ const responseBody = `synthetic-${name.toLowerCase()}-usage-body`
446
+ const homepageBody = `homepage-body-not-${name.toLowerCase()}-guidance`
447
+ const fetchMock = vi.mocked(fetch)
448
+ fetchMock.mockImplementation(async input => {
449
+ const url = input.toString()
450
+
451
+ if (url === expectedUrl) {
452
+ return responseWithFinalUrl(`<main><p>${responseBody}</p></main>`, expectedUrl)
453
+ }
454
+
455
+ if (url === rawIdUrl) {
456
+ return responseWithFinalUrl(`<main><nav>${name}</nav><p>${homepageBody}</p></main>`, 'https://primer.style/')
457
+ }
458
+
459
+ throw new Error(`Unexpected fetch request: ${url}`)
460
+ })
461
+
462
+ const result = await client.callTool({
463
+ name: 'get_component_usage_guidelines',
464
+ arguments: {name},
465
+ })
466
+
467
+ expect(result.isError).not.toBe(true)
468
+ expect(result.content).toContainEqual(
469
+ expect.objectContaining({
470
+ type: 'text',
471
+ text: expect.stringContaining(responseBody),
472
+ }),
473
+ )
474
+ expect(result.content).not.toContainEqual(
475
+ expect.objectContaining({
476
+ type: 'text',
477
+ text: expect.stringContaining(homepageBody),
478
+ }),
479
+ )
480
+ expect(fetchMock).toHaveBeenCalledTimes(1)
481
+ expect(fetchMock.mock.calls[0]?.[0].toString()).toBe(expectedUrl)
482
+ },
483
+ )
484
+
485
+ it.each(['ConfirmationDialog', 'confirmationdialog'])(
486
+ 'retrieves ConfirmationDialog documentation for %s from its published URL',
487
+ async name => {
488
+ const url = 'https://primer.style/product/components/confirmation-dialog/llms.txt'
489
+ const body = '# ConfirmationDialog\n\nSynthetic component documentation.'
490
+ const fetchMock = vi.mocked(fetch)
491
+ fetchMock.mockImplementation(async input => {
492
+ if (input.toString() === url) {
493
+ return new Response(body)
494
+ }
495
+
496
+ if (input.toString() === 'https://primer.style/product/components/confirmationdialog/llms.txt') {
497
+ return new Response(null, {status: 404})
498
+ }
499
+
500
+ throw new Error(`Unexpected fetch request: ${input}`)
501
+ })
502
+
503
+ const result = await client.callTool({name: 'get_component', arguments: {name}})
504
+
505
+ expect(result.isError).not.toBe(true)
506
+ expect(result.content).toEqual([{type: 'text', text: body}])
507
+ expect(fetchMock).toHaveBeenCalledTimes(1)
508
+ expect(fetchMock.mock.calls[0]?.[0].toString()).toBe(url)
509
+ },
510
+ )
511
+
512
+ it.each(['ConfirmationDialog', 'confirmationdialog'])(
513
+ 'retrieves ConfirmationDialog documentation in a mixed batch for %s from its published URL',
514
+ async name => {
515
+ const confirmationUrl = 'https://primer.style/product/components/confirmation-dialog/llms.txt'
516
+ const buttonUrl = 'https://primer.style/product/components/button/llms.txt'
517
+ const confirmationBody = '# ConfirmationDialog\n\nSynthetic component documentation.'
518
+ const buttonBody = '# Button\n\nSynthetic component documentation.'
519
+ const fetchMock = vi.mocked(fetch)
520
+ fetchMock.mockImplementation(async input => {
521
+ const url = input.toString()
522
+ if (url === confirmationUrl) {
523
+ return new Response(confirmationBody)
524
+ }
525
+
526
+ if (url === buttonUrl) {
527
+ return new Response(buttonBody)
528
+ }
529
+
530
+ if (url === 'https://primer.style/product/components/confirmationdialog/llms.txt') {
531
+ return new Response(null, {status: 404})
532
+ }
533
+
534
+ throw new Error(`Unexpected fetch request: ${url}`)
535
+ })
536
+
537
+ const result = await callBatch([name, 'Button'])
538
+
539
+ expect(result.isError).not.toBe(true)
540
+ expect(result.content).toEqual([
541
+ {type: 'text', text: confirmationBody},
542
+ {type: 'text', text: buttonBody},
543
+ ])
544
+ expect(fetchMock).toHaveBeenCalledTimes(2)
545
+ expect(fetchMock.mock.calls.map(([input]) => input.toString()).sort()).toEqual(
546
+ [confirmationUrl, buttonUrl].sort(),
547
+ )
548
+ },
549
+ )
550
+
113
551
  it('requires between 2 and 10 names', async () => {
114
552
  const tooFew = await callBatch(['Button'])
115
553
  const tooMany = await callBatch(Array.from({length: 11}, (_, index) => `Component${index}`))
package/src/server.ts CHANGED
@@ -314,7 +314,7 @@ ${text}`,
314
314
  }
315
315
  }
316
316
 
317
- const url = new URL(`/product/components/${match.id}/guidelines`, 'https://primer.style')
317
+ const url = new URL(`/product/components/${match.slug}/guidelines`, 'https://primer.style')
318
318
  const response = await fetch(url)
319
319
  if (!response.ok) {
320
320
  if ((response.status >= 400 && response.status < 500) || (response.status >= 300 && response.status < 400)) {