@stainless-api/docs 0.1.0-beta.0

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.
Files changed (119) hide show
  1. package/.env.example +1 -0
  2. package/CHANGELOG.md +13 -0
  3. package/README.md +11 -0
  4. package/components/variables.css +139 -0
  5. package/eslint.config.js +10 -0
  6. package/package.json +74 -0
  7. package/plugin/assets/fonts/geist/OFL.txt +93 -0
  8. package/plugin/assets/fonts/geist/geist-italic-latin-ext.woff2 +0 -0
  9. package/plugin/assets/fonts/geist/geist-italic-latin.woff2 +0 -0
  10. package/plugin/assets/fonts/geist/geist-latin-ext.woff2 +0 -0
  11. package/plugin/assets/fonts/geist/geist-latin.woff2 +0 -0
  12. package/plugin/assets/fonts/geist/geist-mono-italic-latin-ext.woff2 +0 -0
  13. package/plugin/assets/fonts/geist/geist-mono-italic-latin.woff2 +0 -0
  14. package/plugin/assets/fonts/geist/geist-mono-latin-ext.woff2 +0 -0
  15. package/plugin/assets/fonts/geist/geist-mono-latin.woff2 +0 -0
  16. package/plugin/assets/languages/curl.svg +10 -0
  17. package/plugin/assets/languages/go.svg +4 -0
  18. package/plugin/assets/languages/java.svg +7 -0
  19. package/plugin/assets/languages/kotlin.svg +10 -0
  20. package/plugin/assets/languages/powershell.svg +3 -0
  21. package/plugin/assets/languages/python.svg +19 -0
  22. package/plugin/assets/languages/ruby.svg +125 -0
  23. package/plugin/assets/languages/terraform.svg +5 -0
  24. package/plugin/assets/languages/typescript.svg +11 -0
  25. package/plugin/assets/stainless-logo-dark.png +0 -0
  26. package/plugin/assets/stainless-logo.png +0 -0
  27. package/plugin/buildAlgoliaIndex.ts +72 -0
  28. package/plugin/cms/client.ts +62 -0
  29. package/plugin/cms/server.ts +268 -0
  30. package/plugin/cms/sidebar-builder.ts +420 -0
  31. package/plugin/cms/worker.ts +122 -0
  32. package/plugin/components/SDKSelect.astro +154 -0
  33. package/plugin/components/SnippetCode.tsx +212 -0
  34. package/plugin/components/search/Search.astro +6 -0
  35. package/plugin/components/search/SearchAlgolia.astro +87 -0
  36. package/plugin/components/search/SearchIsland.tsx +100 -0
  37. package/plugin/generateAPIReferenceLink.ts +71 -0
  38. package/plugin/globalJs/ai-dropdown.ts +57 -0
  39. package/plugin/globalJs/code-snippets.ts +87 -0
  40. package/plugin/globalJs/copy.ts +37 -0
  41. package/plugin/globalJs/navigation.ts +81 -0
  42. package/plugin/globalJs/tooltip.ts +32 -0
  43. package/plugin/helpers/getPageLoadEvent.ts +8 -0
  44. package/plugin/index.ts +308 -0
  45. package/plugin/languages.ts +67 -0
  46. package/plugin/loadPluginConfig.ts +273 -0
  47. package/plugin/middlewareBuilder/stainlessMiddleware.d.ts +5 -0
  48. package/plugin/middlewareBuilder/stlStarlightMiddleware.ts +5 -0
  49. package/plugin/react/Routing.tsx +435 -0
  50. package/plugin/referencePlaceholderUtils.ts +82 -0
  51. package/plugin/replaceSidebarPlaceholderMiddleware.ts +50 -0
  52. package/plugin/routes/Docs.astro +171 -0
  53. package/plugin/routes/DocsStatic.astro +14 -0
  54. package/plugin/routes/Overview.astro +67 -0
  55. package/plugin/routes/markdown.ts +58 -0
  56. package/plugin/vendor/preview.worker.docs.js +21657 -0
  57. package/plugin/vendor/templates/go.md +314 -0
  58. package/plugin/vendor/templates/java.md +87 -0
  59. package/plugin/vendor/templates/kotlin.md +87 -0
  60. package/plugin/vendor/templates/node.md +233 -0
  61. package/plugin/vendor/templates/python.md +249 -0
  62. package/plugin/vendor/templates/ruby.md +145 -0
  63. package/plugin/vendor/templates/terraform.md +60 -0
  64. package/plugin/vendor/templates/typescript.md +317 -0
  65. package/scripts/vendor_deps.ts +50 -0
  66. package/shared/virtualModule.ts +7 -0
  67. package/stl-docs/components/APIReferenceAIDropdown.tsx +86 -0
  68. package/stl-docs/components/ClientRouterHead.astro +41 -0
  69. package/stl-docs/components/Header.astro +91 -0
  70. package/stl-docs/components/Sidebar.astro +11 -0
  71. package/stl-docs/components/ThemeSelect.astro +225 -0
  72. package/stl-docs/components/content-panel/ContentBreadcrumbs.tsx +84 -0
  73. package/stl-docs/components/content-panel/ContentPanel.astro +72 -0
  74. package/stl-docs/components/content-panel/ProseAIDropdown.tsx +64 -0
  75. package/stl-docs/components/headers/DefaultHeader.astro +36 -0
  76. package/stl-docs/components/headers/HeaderLinks.astro +16 -0
  77. package/stl-docs/components/headers/SplashMobileMenuToggle.astro +49 -0
  78. package/stl-docs/components/headers/StackedHeader.astro +75 -0
  79. package/stl-docs/components/mintlify-compat/Accordion.astro +46 -0
  80. package/stl-docs/components/mintlify-compat/AccordionGroup.astro +25 -0
  81. package/stl-docs/components/mintlify-compat/Card.tsx +32 -0
  82. package/stl-docs/components/mintlify-compat/Columns.astro +66 -0
  83. package/stl-docs/components/mintlify-compat/Frame.astro +37 -0
  84. package/stl-docs/components/mintlify-compat/Step.astro +58 -0
  85. package/stl-docs/components/mintlify-compat/Steps.astro +17 -0
  86. package/stl-docs/components/mintlify-compat/Tab.astro +13 -0
  87. package/stl-docs/components/mintlify-compat/Tabs.astro +7 -0
  88. package/stl-docs/components/mintlify-compat/callouts/Callout.astro +7 -0
  89. package/stl-docs/components/mintlify-compat/callouts/Check.astro +7 -0
  90. package/stl-docs/components/mintlify-compat/callouts/Danger.astro +7 -0
  91. package/stl-docs/components/mintlify-compat/callouts/Info.astro +7 -0
  92. package/stl-docs/components/mintlify-compat/callouts/Note.astro +7 -0
  93. package/stl-docs/components/mintlify-compat/callouts/Tip.astro +7 -0
  94. package/stl-docs/components/mintlify-compat/callouts/Warning.astro +7 -0
  95. package/stl-docs/components/mintlify-compat/callouts/index.ts +9 -0
  96. package/stl-docs/components/mintlify-compat/card.css +44 -0
  97. package/stl-docs/components/mintlify-compat/index.ts +15 -0
  98. package/stl-docs/components/nav-tabs/NavDropdown.astro +106 -0
  99. package/stl-docs/components/nav-tabs/NavTabs.astro +165 -0
  100. package/stl-docs/components/nav-tabs/SecondaryNavTabs.astro +62 -0
  101. package/stl-docs/components/nav-tabs/buildNavLinks.ts +14 -0
  102. package/stl-docs/index.ts +174 -0
  103. package/stl-docs/loadStlDocsConfig.ts +160 -0
  104. package/stl-docs/redirects.ts +33 -0
  105. package/stl-docs/tabsMiddleware.ts +183 -0
  106. package/styles/code.css +189 -0
  107. package/styles/fonts.css +68 -0
  108. package/styles/links.css +51 -0
  109. package/styles/mintlify-compat.css +1 -0
  110. package/styles/overrides.css +79 -0
  111. package/styles/page.css +76 -0
  112. package/styles/sdk_select.css +11 -0
  113. package/styles/search.css +85 -0
  114. package/styles/sidebar.css +168 -0
  115. package/styles/toc.css +42 -0
  116. package/styles/variables.css +18 -0
  117. package/theme.css +15 -0
  118. package/tsconfig.json +18 -0
  119. package/virtual-module.d.ts +43 -0
@@ -0,0 +1,435 @@
1
+ import * as React from 'react';
2
+ import { marked } from 'marked';
3
+ import hljs from 'highlight.js';
4
+ import { createMarkdownProcessor, type MarkdownProcessor } from '@astrojs/markdown-remark';
5
+ import remarkGfmAlerts from 'remark-github-alerts';
6
+
7
+ import type { MarkdownHeading } from 'astro';
8
+ import type { StarlightRouteData } from '@astrojs/starlight/route-data';
9
+ import type * as SDKJSON from '~/lib/json-spec-v2/types';
10
+ import type { DocsLanguage } from '@stainless-api/docs-ui/src/routing';
11
+
12
+ import {
13
+ generateRouteList,
14
+ generateRoute,
15
+ parseStainlessPath,
16
+ walkTree,
17
+ SupportedLanguageSyntaxes,
18
+ getLanguageSnippet,
19
+ } from '@stainless-api/docs-ui/src/routing';
20
+
21
+ import {
22
+ DocsProvider,
23
+ MarkdownProvider,
24
+ NavigationProvider,
25
+ useSpec,
26
+ type ContentPanelLayout,
27
+ } from '@stainless-api/docs-ui/src/contexts';
28
+
29
+ import { flatResources, getResourceFromSpec } from '@stainless-api/docs-ui/src/utils';
30
+
31
+ import {
32
+ SDKMethod,
33
+ SDKResource,
34
+ type SDKRequestTitleProps,
35
+ SDKBreadcrumbs,
36
+ Dropdown,
37
+ DropdownTrigger,
38
+ DropdownMenu,
39
+ DropdownItem,
40
+ SDKIcon,
41
+ SDKOverview,
42
+ SDKLanguageBlock,
43
+ } from '@stainless-api/docs-ui/src/components';
44
+ import {
45
+ BASE_PATH,
46
+ EXCLUDE_LANGUAGES,
47
+ EXPAND_RESOURCES,
48
+ HIGHLIGHT_THEMES,
49
+ BREADCRUMB_CONFIG,
50
+ PROPERTY_SETTINGS,
51
+ } from 'virtual:stl-starlight-virtual-module';
52
+ import style from '@stainless-api/docs-ui/src/style';
53
+ import { createHighlighter, type BundledLanguage, type BundledTheme, type HighlighterGeneric } from 'shiki';
54
+ import { SnippetCode, SnippetContainer, SnippetRequestContainer } from '../components/SnippetCode';
55
+ import clsx from 'clsx';
56
+ import type { StlStarlightMiddleware } from '../middlewareBuilder/stainlessMiddleware';
57
+ import { ComponentProvider } from '@stainless-api/docs-ui/src/contexts/component';
58
+ import { APIReferenceAIDropdown } from '../../stl-docs/components/APIReferenceAIDropdown';
59
+
60
+ export function generateDocsRoutes(spec: SDKJSON.Spec) {
61
+ const paths = generateRouteList({
62
+ spec,
63
+ excludeLanguages: EXCLUDE_LANGUAGES as DocsLanguage[],
64
+ });
65
+ const readmes = Object.entries(spec.readme)
66
+ .filter(([language]) => language !== 'http')
67
+ .filter(([language]) => !EXCLUDE_LANGUAGES.includes(language as DocsLanguage))
68
+ .map(([language]) => ({
69
+ slug: language,
70
+ stainlessPath: null,
71
+ language: language as DocsLanguage,
72
+ title: 'Readme',
73
+ kind: 'readme',
74
+ }));
75
+
76
+ return [...paths, ...readmes].map(({ slug, stainlessPath, language, title, kind }) => {
77
+ return {
78
+ params: { slug },
79
+ props: { stainlessPath, language, title, kind },
80
+ };
81
+ });
82
+ }
83
+
84
+ function isResourceNonEmpty(resource: SDKJSON.Resource) {
85
+ return (
86
+ Object.keys(resource.methods ?? {}).length > 0 ||
87
+ Object.keys(resource.subresources ?? {}).length > 0 ||
88
+ Object.keys(resource.models ?? {}).length > 0
89
+ );
90
+ }
91
+
92
+ type SidebarEntry = StarlightRouteData['sidebar'][number];
93
+
94
+ export function buildSidebar(
95
+ basePath: string,
96
+ language: DocsLanguage,
97
+ current: string,
98
+ spec: SDKJSON.Spec,
99
+ resources?: Record<string, SDKJSON.Resource>,
100
+ nested?: boolean,
101
+ ): StarlightRouteData['sidebar'] {
102
+ const totalRoutes = Array.from(walkTree(spec, false)).filter(
103
+ (item) => item.data.kind === 'http_method',
104
+ ).length;
105
+
106
+ return Object.values(resources ?? spec.resources ?? [])
107
+ .filter((resource) => isResourceNonEmpty(resource))
108
+ .sort((a, b) => (a.name.startsWith('$') === b.name.startsWith('$') ? 0 : a.name.startsWith('$') ? 1 : -1))
109
+ .map((resource) => {
110
+ const subs = buildSidebar(basePath, language, current, spec, resource.subresources, true);
111
+
112
+ const overview: SidebarEntry = {
113
+ type: 'link',
114
+ isCurrent: current === resource.stainlessPath,
115
+ attrs: { 'data-stldocs-overview': resource.name },
116
+ label: 'Overview',
117
+ href: generateRoute(basePath, language, resource.stainlessPath) ?? basePath,
118
+ badge: undefined,
119
+ };
120
+
121
+ const meths: SidebarEntry[] = Object.values(resource.methods ?? [])
122
+ .filter((method) => spec.decls?.[language]?.[method.stainlessPath])
123
+ .toSorted((first, second) => first.name.localeCompare(second.name))
124
+ .map((method) => ({
125
+ type: 'link',
126
+ isCurrent: current === method.stainlessPath,
127
+ attrs: { 'data-stldocs-method': method.httpMethod },
128
+ label: method.summary ?? method.name,
129
+ href: generateRoute(basePath, language, method.stainlessPath) ?? basePath,
130
+ badge: undefined,
131
+ }));
132
+
133
+ const shouldExpand = EXPAND_RESOURCES ?? totalRoutes < 20;
134
+
135
+ return {
136
+ type: 'group',
137
+ label: resource.title,
138
+ badge: undefined,
139
+ collapsed: !shouldExpand || nested === true,
140
+ entries: [...(resources ? [] : [overview]), ...meths, ...subs],
141
+ };
142
+ });
143
+ }
144
+
145
+ export function buildPageNavigation(resource: SDKJSON.Resource, depth: number = 2): MarkdownHeading[] {
146
+ const output: MarkdownHeading[] = [{ depth, slug: resource.stainlessPath, text: resource.title }];
147
+
148
+ const subs = Object.values(resource.subresources ?? {}).flatMap((sub) =>
149
+ buildPageNavigation(sub, depth + 1),
150
+ );
151
+
152
+ return [...output, ...subs];
153
+ }
154
+
155
+ function renderMarkdown(content: string) {
156
+ return marked.parse(content, { gfm: true }) as string;
157
+ }
158
+
159
+ async function highlight(content: string, language?: string) {
160
+ if (language === 'json') return hljs.highlight(content, { language }).value;
161
+ const highlighter = await astroHighlight();
162
+ return highlighter.codeToHtml(content, {
163
+ lang: language ?? 'javascript',
164
+ themes: HIGHLIGHT_THEMES || {},
165
+ });
166
+ }
167
+
168
+ export function SDKSelectReactComponent({
169
+ selected,
170
+ languages,
171
+ id,
172
+ className,
173
+ }: {
174
+ selected: DocsLanguage;
175
+ languages: DocsLanguage[];
176
+ id: string;
177
+ className?: string;
178
+ }) {
179
+ return (
180
+ <Dropdown id={id} data-current-value={selected} className={className}>
181
+ <DropdownTrigger
182
+ className="dropdown-toggle stldocs-button-tertiary"
183
+ type="button"
184
+ id="stldocs-snippet-title-button"
185
+ aria-expanded="false"
186
+ withChevron
187
+ >
188
+ <SDKIcon language={getLanguageSnippet(selected)} size={16} />
189
+ <span className={clsx('stl-snippet-dropdown-button-text', selected)}>{selected}</span>
190
+ </DropdownTrigger>
191
+ <DropdownMenu
192
+ className="dropdown-menu stl-sdk-select-dropdown-menu"
193
+ position="below"
194
+ aria-labelledby="stldocs-snippet-title-button"
195
+ >
196
+ {languages.map((item) => (
197
+ <DropdownItem key={item} value={item} selected={item === selected}>
198
+ <div>
199
+ <SDKIcon language={getLanguageSnippet(item)} size={16} />
200
+ <span className={clsx('stl-snippet-dropdown-button-text', item)}>{item}</span>
201
+ </div>
202
+ </DropdownItem>
203
+ ))}
204
+ </DropdownMenu>
205
+ </Dropdown>
206
+ );
207
+ }
208
+
209
+ function SDKRequestTitle({ snippetLanguage }: SDKRequestTitleProps) {
210
+ const spec = useSpec();
211
+
212
+ const selected = snippetLanguage.split('.').at(0) as DocsLanguage;
213
+ const languages = (spec?.docs?.languages ?? ['http']).filter((lang) => !EXCLUDE_LANGUAGES.includes(lang));
214
+
215
+ return (
216
+ <SDKSelectReactComponent
217
+ selected={selected || 'http'}
218
+ languages={languages}
219
+ id="stldocs-snippet-select"
220
+ className="stl-sdk-select"
221
+ />
222
+ );
223
+ }
224
+
225
+ export type SpecMetadata = [
226
+ 'http' | 'node' | 'python' | 'go' | 'typescript' | 'terraform' | 'ruby' | 'java' | 'kotlin',
227
+ {
228
+ repo_url?: string;
229
+ code_url?: string;
230
+ package_title?: string;
231
+ version?: string;
232
+ install?: string;
233
+ },
234
+ ][];
235
+
236
+ export function RenderLibraries({ metadata }: { metadata: SpecMetadata }) {
237
+ return (
238
+ <ComponentProvider components={{}}>
239
+ {metadata.map(([language, data]) => (
240
+ <SDKLanguageBlock
241
+ language={language}
242
+ version={data.version || ''}
243
+ install={data.install || ''}
244
+ links={{ repo: data.repo_url || '#', docs: `${BASE_PATH}/${language}` }}
245
+ />
246
+ ))}
247
+ </ComponentProvider>
248
+ );
249
+ }
250
+
251
+ export function RenderSpecOverview({ spec, language }: { spec: SDKJSON.Spec; language: DocsLanguage }) {
252
+ const resources = React.useMemo(() => flatResources(spec.resources, []), [spec]);
253
+
254
+ return (
255
+ <DocsProvider spec={spec} language={language ?? 'node'}>
256
+ <ComponentProvider
257
+ components={{
258
+ SDKRequestTitle,
259
+ SnippetCode,
260
+ SnippetContainer,
261
+ SnippetRequestContainer,
262
+ }}
263
+ >
264
+ <NavigationProvider basePath={BASE_PATH}>
265
+ <MarkdownProvider render={renderMarkdown} highlight={highlight}>
266
+ <div className={style.Overview}>
267
+ {resources
268
+ .filter(({ resource }) => !resource.name.startsWith('$'))
269
+ .map(({ resource, parents }) => (
270
+ <SDKResource
271
+ key={resource.stainlessPath}
272
+ resource={resource}
273
+ parents={parents}
274
+ showModels={false}
275
+ />
276
+ ))}
277
+ </div>
278
+ </MarkdownProvider>
279
+ </NavigationProvider>
280
+ </ComponentProvider>
281
+ </DocsProvider>
282
+ );
283
+ }
284
+
285
+ export function RenderSpec({
286
+ spec,
287
+ kind,
288
+ path,
289
+ language,
290
+ currentPath,
291
+ contentPanelLayout = 'double-pane',
292
+ transformRequestSnippet,
293
+ }: {
294
+ spec: SDKJSON.Spec;
295
+ kind: string;
296
+ path: string;
297
+ language: DocsLanguage;
298
+ currentPath: string;
299
+ contentPanelLayout?: ContentPanelLayout;
300
+ transformRequestSnippet?: StlStarlightMiddleware['transformRequestSnippet'];
301
+ }) {
302
+ const parsed = parseStainlessPath(path);
303
+ const resource = getResourceFromSpec(path, spec);
304
+
305
+ if (!resource || !parsed) return null;
306
+
307
+ return (
308
+ <DocsProvider
309
+ spec={spec as SDKJSON.Spec}
310
+ language={language ?? 'node'}
311
+ settings={{
312
+ contentPanelLayout,
313
+ properties: PROPERTY_SETTINGS,
314
+ }}
315
+ >
316
+ <ComponentProvider
317
+ components={{
318
+ SDKRequestTitle,
319
+ SnippetCode,
320
+ SnippetContainer,
321
+ SnippetRequestContainer,
322
+ }}
323
+ >
324
+ <NavigationProvider basePath={BASE_PATH} selectedPath={path}>
325
+ <MarkdownProvider render={renderMarkdown} highlight={highlight}>
326
+ {kind === 'http_method' ? (
327
+ <div className="stldocs-root stl-ui-not-prose">
328
+ <div className="stl-page-nav-container">
329
+ <SDKBreadcrumbs
330
+ spec={spec as SDKJSON.Spec}
331
+ currentPath={currentPath}
332
+ basePath={BASE_PATH}
333
+ config={BREADCRUMB_CONFIG}
334
+ />
335
+ <APIReferenceAIDropdown />
336
+ </div>
337
+ <SDKMethod
338
+ method={resource.methods[parsed.method]}
339
+ transformRequestSnippet={transformRequestSnippet}
340
+ />
341
+ </div>
342
+ ) : (
343
+ <div className="stldocs-root stl-ui-not-prose">
344
+ <div className="stl-page-nav-container">
345
+ <SDKBreadcrumbs
346
+ spec={spec as SDKJSON.Spec}
347
+ currentPath={currentPath}
348
+ basePath={BASE_PATH}
349
+ config={BREADCRUMB_CONFIG}
350
+ />
351
+ <APIReferenceAIDropdown />
352
+ </div>
353
+ <SDKOverview resource={resource} />
354
+ </div>
355
+ )}
356
+ </MarkdownProvider>
357
+ </NavigationProvider>
358
+ </ComponentProvider>
359
+ </DocsProvider>
360
+ );
361
+ }
362
+
363
+ export function RenderMethod({ path }: { path: string }) {
364
+ const spec = useSpec();
365
+ if (!spec) return null;
366
+
367
+ const parsed = parseStainlessPath(path);
368
+ const resource = getResourceFromSpec(path, spec);
369
+ if (!resource || !parsed) return null;
370
+
371
+ const meth = resource.methods[parsed.method];
372
+ return <SDKMethod method={meth} />;
373
+ }
374
+
375
+ export async function getReadmeContent(spec: SDKJSON.Spec, language: DocsLanguage) {
376
+ const repoUrl = spec.metadata?.[language]?.repo_url;
377
+
378
+ try {
379
+ if (repoUrl) {
380
+ const rawUrl = repoUrl.replace('www.github.com', 'raw.githubusercontent.com');
381
+
382
+ const response = await fetch(`${rawUrl}/refs/heads/main/README.md`);
383
+ if (response && response.ok) return response.text();
384
+ }
385
+ } catch {
386
+ // ignore
387
+ }
388
+
389
+ return spec.readme[language];
390
+ }
391
+
392
+ // Astro's markdown processor is a singleton
393
+ // Need to cache it instead of instanting per request
394
+ let astroMarkdownProcessor: MarkdownProcessor;
395
+ async function astroMarkdown() {
396
+ if (!astroMarkdownProcessor) {
397
+ astroMarkdownProcessor = await createMarkdownProcessor({
398
+ gfm: true,
399
+ remarkPlugins: [remarkGfmAlerts],
400
+ shikiConfig: {
401
+ themes: HIGHLIGHT_THEMES,
402
+ },
403
+ });
404
+ }
405
+
406
+ return astroMarkdownProcessor;
407
+ }
408
+
409
+ let astroShikiHighlighter: HighlighterGeneric<BundledLanguage, BundledTheme>;
410
+ async function astroHighlight() {
411
+ if (!astroShikiHighlighter) {
412
+ astroShikiHighlighter = await createHighlighter({
413
+ themes: ['github-light', 'github-dark'],
414
+ langs: SupportedLanguageSyntaxes,
415
+ });
416
+ }
417
+
418
+ return astroShikiHighlighter;
419
+ }
420
+
421
+ export async function astroMarkdownRender(content: string) {
422
+ const md = await astroMarkdown();
423
+ const output = await md.render(content);
424
+
425
+ // Map GFM callouts to the closest Starlight equivalent
426
+ output.code = output.code
427
+ .replaceAll('markdown-alert-caution', 'markdown-alert-danger')
428
+ .replaceAll('markdown-alert-warning', 'markdown-alert-caution')
429
+ .replaceAll('markdown-alert-important', 'markdown-alert-caution')
430
+ .replaceAll('markdown-alert-title', 'starlight-aside__title')
431
+ .replaceAll('markdown-alert-', 'starlight-aside--')
432
+ .replaceAll('markdown-alert', 'starlight-aside');
433
+
434
+ return output;
435
+ }
@@ -0,0 +1,82 @@
1
+ import type { StarlightRouteData } from '@astrojs/starlight/route-data';
2
+ import type starlight from '@astrojs/starlight';
3
+
4
+ // file is required because of build errors when importing from index.ts in the replaceSidebarPlaceholderMiddleware.ts file
5
+ const INTERNAL_REFERENCE_ENTRY_MARKER = 'STL_STARLIGHT_API_REFERENCE_ITEMS_PLACEHOLDER';
6
+
7
+ export function makePlaceholderItems(id: number) {
8
+ return [
9
+ {
10
+ label: 'API Reference',
11
+ link: '/', // this gets replaced during plugin initialization
12
+ attrs: {
13
+ // we hack-ily use/abuse the about attribute to identify the placeholder item
14
+ // using "about" was sort of a random choice, but it seems good enough
15
+ about: INTERNAL_REFERENCE_ENTRY_MARKER,
16
+ 'data-stldocs-sidebar-id': id,
17
+ },
18
+ },
19
+ ];
20
+ }
21
+
22
+ type StarlightConfig = Parameters<typeof starlight>[0];
23
+
24
+ type SidebarConfigEntry = Exclude<StarlightConfig['sidebar'], undefined>[number];
25
+
26
+ export function getAPIReferencePlaceholderItemFromSidebarConfig(
27
+ sidebar: SidebarConfigEntry[],
28
+ ): SidebarConfigEntry | null {
29
+ for (const item of sidebar) {
30
+ if (typeof item === 'string') {
31
+ continue;
32
+ }
33
+ if ('items' in item) {
34
+ const found = getAPIReferencePlaceholderItemFromSidebarConfig(item.items);
35
+ if (found) {
36
+ return found;
37
+ }
38
+ }
39
+ if ('attrs' in item && item.attrs?.about === INTERNAL_REFERENCE_ENTRY_MARKER) {
40
+ return item;
41
+ }
42
+ }
43
+
44
+ return null;
45
+ }
46
+
47
+ type SidebarEntry = StarlightRouteData['sidebar'][number];
48
+
49
+ type PlaceholderItemResult = {
50
+ index: number;
51
+ group: SidebarEntry[];
52
+ placeholder: SidebarEntry;
53
+ sidebarId: number;
54
+ };
55
+
56
+ function recursiveGetPlaceholderItems(
57
+ sidebar: SidebarEntry[],
58
+ items: PlaceholderItemResult[],
59
+ ): PlaceholderItemResult[] {
60
+ for (let i = 0; i < sidebar.length; i++) {
61
+ const entry = sidebar[i];
62
+ if ('attrs' in entry && entry.attrs?.about === INTERNAL_REFERENCE_ENTRY_MARKER) {
63
+ items.push({
64
+ index: i,
65
+ group: sidebar,
66
+ placeholder: entry,
67
+ sidebarId: entry.attrs?.['data-stldocs-sidebar-id'],
68
+ });
69
+ }
70
+ if (entry.type === 'group') {
71
+ const foundEntries = getAPIReferencePlaceholderItems(entry.entries);
72
+ if (foundEntries) {
73
+ items.push(...foundEntries);
74
+ }
75
+ }
76
+ }
77
+ return items;
78
+ }
79
+
80
+ export function getAPIReferencePlaceholderItems(sidebar: SidebarEntry[]): PlaceholderItemResult[] {
81
+ return recursiveGetPlaceholderItems(sidebar, []);
82
+ }
@@ -0,0 +1,50 @@
1
+ import { defineRouteMiddleware } from '@astrojs/starlight/route-data';
2
+
3
+ import { cmsClient } from './cms/client';
4
+ import { BASE_PATH } from 'virtual:stl-starlight-virtual-module';
5
+ import { getAPIReferencePlaceholderItems } from './referencePlaceholderUtils';
6
+ import { getMethodFromSDKJSON, recursiveReplacePlaceholderItems } from './generateAPIReferenceLink';
7
+ import { forceGenerateRoute } from './cms/sidebar-builder';
8
+ import { parseRoute } from '@stainless-api/docs-ui/src/routing';
9
+
10
+ // this fn is loaded in the plugin via addRouteMiddleware
11
+
12
+ export const onRequest = defineRouteMiddleware(async (context) => {
13
+ // if using content collection schema, use: context.locals.starlightRoute.entry.data.stainlessStarlight
14
+ // this worked without collections but relied on hijacking starlightRoute: context.props.frontmatter.stainlessStarlight
15
+
16
+ const slug = `/${context.locals.starlightRoute.id}`; // same as .slug but not deprecated
17
+
18
+ const apiReferencePlaceholderItems = getAPIReferencePlaceholderItems(context.locals.starlightRoute.sidebar);
19
+
20
+ const spec = await cmsClient.getSpec();
21
+
22
+ const { language, stainlessPath } = parseRoute(BASE_PATH, slug);
23
+
24
+ // This is probably temporary, but it fills in functionality needed for Mintlify imports
25
+ recursiveReplacePlaceholderItems(context.locals.starlightRoute.sidebar, (entry, { endpoint, label }) => {
26
+ const method = getMethodFromSDKJSON(spec, endpoint);
27
+
28
+ const route = forceGenerateRoute({
29
+ basePath: BASE_PATH,
30
+ stainlessPath: method.stainlessPath,
31
+ language,
32
+ });
33
+ entry.href = route;
34
+ entry.isCurrent = method.stainlessPath === stainlessPath;
35
+ if (!label) {
36
+ entry.label = method.summary ?? method.name;
37
+ }
38
+ entry.attrs['data-stldocs-method'] = method.httpMethod;
39
+ });
40
+
41
+ for (const item of apiReferencePlaceholderItems) {
42
+ const entries = await cmsClient.buildSidebar({
43
+ basePath: BASE_PATH,
44
+ currentSlug: slug,
45
+ sidebarId: item.sidebarId,
46
+ });
47
+
48
+ item.group.splice(item.index, 1, ...entries);
49
+ }
50
+ });