@stainless-api/docs 0.1.0-beta.99 → 1.0.0-beta.141

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 (136) hide show
  1. package/CHANGELOG.md +401 -0
  2. package/ambient.d.ts +6 -0
  3. package/eslint-suppressions.json +22 -6
  4. package/{eslint.config.js → eslint.config.ts} +1 -7
  5. package/package.json +62 -40
  6. package/plugin/buildAlgoliaIndex.ts +6 -12
  7. package/plugin/components/SDKSelect.astro +0 -6
  8. package/plugin/components/SnippetCode.tsx +6 -37
  9. package/plugin/components/search/SearchAlgolia.astro +1 -1
  10. package/plugin/components/search/SearchIsland.tsx +19 -13
  11. package/plugin/generateAPIReferenceLink.ts +0 -40
  12. package/plugin/globalJs/ai-dropdown-options.ts +22 -9
  13. package/plugin/globalJs/code-snippets.ts +5 -5
  14. package/plugin/globalJs/copy.ts +20 -91
  15. package/plugin/globalJs/navigation.ts +13 -13
  16. package/plugin/globalJs/summary-selection-tweak.ts +29 -0
  17. package/plugin/index.ts +107 -163
  18. package/plugin/loadPluginConfig.ts +49 -151
  19. package/plugin/markdown/highlighter.ts +100 -0
  20. package/plugin/markdown/index.ts +39 -0
  21. package/plugin/middlewareBuilder/stainlessMiddleware.d.ts +2 -0
  22. package/plugin/react/Routing.tsx +10 -244
  23. package/plugin/referencePlaceholderUtils.ts +1 -1
  24. package/plugin/replaceSidebarPlaceholderMiddleware.ts +1 -1
  25. package/plugin/routes/Docs.astro +3 -1
  26. package/plugin/routes/Overview.astro +14 -7
  27. package/plugin/routes/llms.ts +186 -0
  28. package/plugin/routes/markdown.ts +62 -13
  29. package/plugin/sidebar-utils/sidebar-builder.ts +38 -12
  30. package/plugin/specs/defaultSpecLoader.ts +192 -0
  31. package/plugin/specs/fetchSpecSSR.ts +1 -1
  32. package/plugin/specs/utils.ts +86 -0
  33. package/shared/conditionalIntegration.ts +28 -0
  34. package/shared/getProsePages.ts +6 -7
  35. package/shared/virtualModule.ts +1 -26
  36. package/stl-docs/aiChatExamples.ts +31 -0
  37. package/stl-docs/chat/docs-chat-handler.ts +17 -0
  38. package/stl-docs/chat/hook.ts +225 -0
  39. package/stl-docs/chat/schemas.ts +27 -0
  40. package/stl-docs/chat/ui/AiChat.module.css +591 -0
  41. package/stl-docs/chat/ui/AiChat.tsx +175 -0
  42. package/stl-docs/chat/ui/Trigger.tsx +154 -0
  43. package/stl-docs/chat/ui/components/ChatControls.tsx +51 -0
  44. package/stl-docs/chat/ui/components/ChatEmpty.tsx +42 -0
  45. package/stl-docs/chat/ui/components/ChatLog.tsx +93 -0
  46. package/stl-docs/chat/ui/components/ChatMessage.tsx +47 -0
  47. package/stl-docs/chat/ui/components/CodeBlock.tsx +33 -0
  48. package/stl-docs/chat/ui/components/MessageFeedback.tsx +106 -0
  49. package/stl-docs/chat/ui/components/Table.tsx +15 -0
  50. package/stl-docs/chat/ui/components/ToolCall.tsx +34 -0
  51. package/stl-docs/chat/ui/components/hljs-github.css +81 -0
  52. package/stl-docs/chat/ui/scroll-manager.ts +86 -0
  53. package/stl-docs/chat/ui/types.ts +45 -0
  54. package/stl-docs/components/AiChatIsland.tsx +10 -12
  55. package/stl-docs/components/ContentPanel.astro +9 -0
  56. package/stl-docs/components/Footer.astro +89 -0
  57. package/stl-docs/components/Header.astro +0 -5
  58. package/stl-docs/components/PageFrame.astro +23 -8
  59. package/stl-docs/components/PageSidebar.astro +11 -0
  60. package/stl-docs/components/StainlessLogo.svg +4 -0
  61. package/stl-docs/components/TwoColumnContent.astro +2 -0
  62. package/stl-docs/components/headers/DefaultHeader.astro +6 -8
  63. package/stl-docs/components/headers/StackedHeader.astro +5 -53
  64. package/stl-docs/components/mintlify-compat/Accordion.astro +2 -2
  65. package/stl-docs/components/mintlify-compat/AccordionGroup.astro +0 -4
  66. package/stl-docs/components/mintlify-compat/Columns.astro +2 -2
  67. package/stl-docs/components/mintlify-compat/Frame.astro +2 -2
  68. package/stl-docs/components/mintlify-compat/Tab.astro +2 -2
  69. package/stl-docs/components/mintlify-compat/callouts/Callout.astro +2 -2
  70. package/stl-docs/components/mintlify-compat/callouts/Check.astro +0 -4
  71. package/stl-docs/components/mintlify-compat/callouts/Danger.astro +0 -4
  72. package/stl-docs/components/mintlify-compat/callouts/Info.astro +0 -4
  73. package/stl-docs/components/mintlify-compat/callouts/Note.astro +0 -4
  74. package/stl-docs/components/mintlify-compat/callouts/Tip.astro +0 -4
  75. package/stl-docs/components/mintlify-compat/callouts/Warning.astro +0 -4
  76. package/stl-docs/components/nav-tabs/NavDropdown.astro +12 -7
  77. package/stl-docs/components/nav-tabs/NavTabs.astro +5 -3
  78. package/stl-docs/components/nav-tabs/buildNavLinks.ts +2 -0
  79. package/stl-docs/components/pagination/Pagination.astro +4 -2
  80. package/stl-docs/components/pagination/PaginationLinkEmphasized.astro +2 -2
  81. package/stl-docs/components/pagination/PaginationLinkQuiet.astro +2 -2
  82. package/stl-docs/components/pagination/util.ts +3 -3
  83. package/stl-docs/components/sidebars/BaseSidebar.astro +72 -1
  84. package/stl-docs/disableCalloutSyntax.ts +1 -1
  85. package/stl-docs/fonts.ts +5 -5
  86. package/stl-docs/index.ts +76 -53
  87. package/stl-docs/loadStlDocsConfig.ts +38 -8
  88. package/stl-docs/og-image/components/OpenGraphFunctionSignature.tsx +64 -0
  89. package/stl-docs/og-image/components/OpenGraphImage.tsx +126 -0
  90. package/stl-docs/og-image/config.ts +56 -0
  91. package/stl-docs/og-image/image-gen/generate-api-reference-og-image.tsx +188 -0
  92. package/stl-docs/og-image/image-gen/generate-og-image.tsx +119 -0
  93. package/stl-docs/og-image/image-gen/get-logo-url.ts +47 -0
  94. package/stl-docs/og-image/index.ts +135 -0
  95. package/stl-docs/og-image/routes/add-og-image.ts +45 -0
  96. package/stl-docs/og-image/routes/get-api-reference-og-image.ts +36 -0
  97. package/stl-docs/og-image/routes/get-og-image.ts +28 -0
  98. package/stl-docs/og-image/theme.ts +43 -0
  99. package/stl-docs/og-image/utils.ts +14 -0
  100. package/stl-docs/proseDocSync.test.ts +74 -0
  101. package/stl-docs/proseDocSync.ts +344 -0
  102. package/stl-docs/proseMarkdown/proseMarkdownIntegration.ts +4 -12
  103. package/stl-docs/schema-extension.ts +12 -0
  104. package/stl-docs/tabsMiddleware.ts +1 -1
  105. package/styles/overrides.css +2 -14
  106. package/styles/page.css +210 -71
  107. package/styles/sidebar.css +30 -17
  108. package/styles/sl-variables.css +3 -8
  109. package/styles/stldocs-variables.css +2 -2
  110. package/styles/toc.css +8 -0
  111. package/tsconfig.json +1 -1
  112. package/virtual-module.d.ts +35 -11
  113. package/playground-virtual-modules.d.ts +0 -96
  114. package/plugin/globalJs/create-playground.shim.ts +0 -3
  115. package/plugin/globalJs/playground-data.shim.ts +0 -1
  116. package/plugin/globalJs/playground-data.ts +0 -14
  117. package/plugin/specs/FileCache.ts +0 -99
  118. package/plugin/specs/generateSpec.ts +0 -112
  119. package/plugin/specs/index.ts +0 -132
  120. package/plugin/specs/inputResolver.ts +0 -146
  121. package/plugin/specs/worker.ts +0 -199
  122. package/plugin/vendor/preview.worker.docs.js +0 -26108
  123. package/plugin/vendor/templates/cli.md +0 -1
  124. package/plugin/vendor/templates/go.md +0 -316
  125. package/plugin/vendor/templates/java.md +0 -89
  126. package/plugin/vendor/templates/kotlin.md +0 -89
  127. package/plugin/vendor/templates/node.md +0 -235
  128. package/plugin/vendor/templates/python.md +0 -251
  129. package/plugin/vendor/templates/ruby.md +0 -147
  130. package/plugin/vendor/templates/terraform.md +0 -60
  131. package/plugin/vendor/templates/typescript.md +0 -319
  132. package/scripts/vendor_deps.ts +0 -50
  133. package/stl-docs/components/ClientRouterHead.astro +0 -41
  134. package/stl-docs/components/content-panel/ContentPanel.astro +0 -42
  135. package/stl-docs/components/headers/SplashMobileMenuToggle.astro +0 -65
  136. package/stl-docs/proseSearchIndexing.ts +0 -606
@@ -1,31 +1,19 @@
1
1
  import * as React from 'react';
2
- import { marked, Tokens } from 'marked';
3
2
  import { getDocsLanguages } from '../helpers/multiSpec';
4
3
 
5
- import {
6
- createMarkdownProcessor,
7
- CreateShikiHighlighterOptions,
8
- type MarkdownProcessor,
9
- } from '@astrojs/markdown-remark';
10
- import remarkGfmAlerts from 'remark-github-alerts';
4
+ import { astroMarkdownRenderText } from '../markdown';
5
+ import { highlight } from '../markdown/highlighter';
11
6
 
12
7
  import type { MarkdownHeading } from 'astro';
13
- import type { StarlightRouteData } from '@astrojs/starlight/route-data';
14
8
  import type * as SDKJSON from '@stainless/sdk-json';
15
- import { LanguageNames, SupportedLanguageSyntaxes, type DocsLanguage } from '@stainless-api/docs-ui/routing';
9
+ import { LanguageNames, type DocsLanguage } from '@stainless-api/docs-ui/routing';
16
10
 
17
- import {
18
- generateRoute,
19
- parseStainlessPath,
20
- walkTree,
21
- getLanguageSnippet,
22
- } from '@stainless-api/docs-ui/routing';
11
+ import { parseStainlessPath, getLanguageSnippet } from '@stainless-api/docs-ui/routing';
23
12
 
24
13
  import {
25
14
  DocsProvider,
26
15
  MarkdownProvider,
27
16
  NavigationProvider,
28
- useSpec,
29
17
  type ContentPanelLayout,
30
18
  } from '@stainless-api/docs-ui/contexts';
31
19
 
@@ -45,15 +33,13 @@ import { Dropdown } from '@stainless-api/docs/components';
45
33
 
46
34
  import {
47
35
  RESOLVED_API_REFERENCE_PATH,
48
- EXPAND_RESOURCES,
49
- HIGHLIGHT_THEMES,
50
36
  BREADCRUMB_CONFIG,
51
37
  PROPERTY_SETTINGS,
52
38
  ENABLE_CONTEXT_MENU,
53
39
  EXPERIMENTAL_COLLAPSIBLE_METHOD_DESCRIPTIONS,
40
+ MIDDLEWARE,
54
41
  } from 'virtual:stl-starlight-virtual-module';
55
42
  import style from '@stainless-api/docs-ui/style';
56
- import { BundledTheme, createHighlighter, HighlighterGeneric, type BundledLanguage } from 'shiki';
57
43
  import {
58
44
  SnippetCode,
59
45
  SnippetContainer,
@@ -67,66 +53,6 @@ import { AIDropdown } from '../../stl-docs/components/AIDropdown';
67
53
  import { ChevronsUpDownIcon } from 'lucide-react';
68
54
  import { MethodDescription } from '../components/MethodDescription';
69
55
 
70
- function isResourceNonEmpty(resource: SDKJSON.Resource) {
71
- return (
72
- Object.keys(resource.methods ?? {}).length > 0 ||
73
- Object.keys(resource.subresources ?? {}).length > 0 ||
74
- Object.keys(resource.models ?? {}).length > 0
75
- );
76
- }
77
-
78
- type SidebarEntry = StarlightRouteData['sidebar'][number];
79
-
80
- export function buildSidebar(
81
- basePath: string,
82
- language: DocsLanguage,
83
- current: string,
84
- spec: SDKJSON.Spec,
85
- resources?: Record<string, SDKJSON.Resource>,
86
- nested?: boolean,
87
- ): StarlightRouteData['sidebar'] {
88
- const totalRoutes = Array.from(walkTree(spec, false)).filter(
89
- (item) => item.data.kind === 'http_method',
90
- ).length;
91
-
92
- return Object.values(resources ?? spec.resources ?? [])
93
- .filter((resource) => isResourceNonEmpty(resource))
94
- .sort((a, b) => (a.name.startsWith('$') === b.name.startsWith('$') ? 0 : a.name.startsWith('$') ? 1 : -1))
95
- .map((resource) => {
96
- const subs = buildSidebar(basePath, language, current, spec, resource.subresources, true);
97
-
98
- const overview: SidebarEntry = {
99
- type: 'link',
100
- isCurrent: current === resource.stainlessPath,
101
- attrs: { 'data-stldocs-overview': resource.name },
102
- label: 'Overview',
103
- href: generateRoute(basePath, language, resource.stainlessPath) ?? basePath,
104
- badge: undefined,
105
- };
106
-
107
- const meths: SidebarEntry[] = Object.values(resource.methods ?? [])
108
- .filter((method) => spec.decls?.[language]?.[method.stainlessPath])
109
- .map((method) => ({
110
- type: 'link',
111
- isCurrent: current === method.stainlessPath,
112
- attrs: { 'data-stldocs-method': method.httpMethod },
113
- label: method.summary ?? method.name,
114
- href: generateRoute(basePath, language, method.stainlessPath) ?? basePath,
115
- badge: undefined,
116
- }));
117
-
118
- const shouldExpand = EXPAND_RESOURCES ?? totalRoutes < 20;
119
-
120
- return {
121
- type: 'group',
122
- label: resource.title,
123
- badge: undefined,
124
- collapsed: !shouldExpand || nested === true,
125
- entries: [...(resources ? [] : [overview]), ...meths, ...subs],
126
- };
127
- });
128
- }
129
-
130
56
  export function buildPageNavigation(resource: SDKJSON.Resource, depth: number = 2): MarkdownHeading[] {
131
57
  const output: MarkdownHeading[] = [{ depth, slug: resource.stainlessPath, text: resource.title }];
132
58
 
@@ -137,58 +63,6 @@ export function buildPageNavigation(resource: SDKJSON.Resource, depth: number =
137
63
  return [...output, ...subs];
138
64
  }
139
65
 
140
- async function renderMarkdown(content: string) {
141
- const highlighter = await astroHighlight();
142
-
143
- const renderer = {
144
- code({ text, lang }: Tokens.Code) {
145
- return shikiHighlight({
146
- highlighter,
147
- content: text,
148
- language: lang,
149
- });
150
- },
151
- };
152
-
153
- marked.use({ renderer });
154
- return marked.parse(content, {
155
- gfm: true,
156
- }) as string;
157
- }
158
-
159
- function shikiHighlight({
160
- highlighter,
161
- content,
162
- language,
163
- themes,
164
- }: {
165
- highlighter: HighlighterGeneric<BundledLanguage, BundledTheme>;
166
- content: string;
167
- language?: string;
168
- themes?: CreateShikiHighlighterOptions['themes'] | Record<string, 'stainless-docs-json'>;
169
- }) {
170
- let _themes = themes;
171
- if (!themes && language === 'json') {
172
- _themes = {
173
- light: 'stainless-docs-json',
174
- dark: 'stainless-docs-json',
175
- };
176
- }
177
-
178
- if (!_themes) {
179
- _themes = HIGHLIGHT_THEMES;
180
- }
181
- return highlighter.codeToHtml(content, {
182
- lang: language ?? 'javascript',
183
- themes: _themes || {},
184
- });
185
- }
186
-
187
- async function highlight(content: string, language?: string) {
188
- const highlighter = await astroHighlight();
189
- return shikiHighlight({ highlighter, content, language });
190
- }
191
-
192
66
  export function SDKSelectReactComponent({
193
67
  selected,
194
68
  languages,
@@ -268,6 +142,7 @@ const componentOverrides = {
268
142
  SnippetFooter,
269
143
  SnippetResponse,
270
144
  ...(EXPERIMENTAL_COLLAPSIBLE_METHOD_DESCRIPTIONS ? { MethodDescription } : {}),
145
+ ...MIDDLEWARE.componentOverrides,
271
146
  } satisfies React.ComponentProps<typeof ComponentProvider>['components'];
272
147
 
273
148
  export function RenderLibraries({ metadata }: { metadata: SpecMetadata }) {
@@ -293,7 +168,7 @@ export function RenderSpecOverview({ spec, language }: { spec: SDKJSON.Spec; lan
293
168
  <DocsProvider spec={spec} language={language ?? 'node'}>
294
169
  <ComponentProvider components={componentOverrides}>
295
170
  <NavigationProvider basePath={RESOLVED_API_REFERENCE_PATH}>
296
- <MarkdownProvider render={renderMarkdown} highlight={highlight}>
171
+ <MarkdownProvider render={astroMarkdownRenderText} highlight={highlight}>
297
172
  <div className={style.Overview}>
298
173
  {resources
299
174
  .filter(({ resource }) => !resource.name.startsWith('$'))
@@ -340,7 +215,7 @@ export function RenderSpec({
340
215
 
341
216
  return (
342
217
  <DocsProvider
343
- spec={spec as SDKJSON.Spec}
218
+ spec={spec}
344
219
  language={language ?? 'node'}
345
220
  settings={{
346
221
  contentPanelLayout,
@@ -349,12 +224,12 @@ export function RenderSpec({
349
224
  >
350
225
  <ComponentProvider components={componentOverrides}>
351
226
  <NavigationProvider basePath={RESOLVED_API_REFERENCE_PATH} selectedPath={path}>
352
- <MarkdownProvider render={renderMarkdown} highlight={highlight}>
227
+ <MarkdownProvider render={astroMarkdownRenderText} highlight={highlight}>
353
228
  {
354
229
  <div className="stldocs-root stl-ui-not-prose">
355
230
  <div className="stl-page-nav-container">
356
231
  <SDKBreadcrumbs
357
- spec={spec as SDKJSON.Spec}
232
+ spec={spec}
358
233
  currentPath={currentPath}
359
234
  basePath={RESOLVED_API_REFERENCE_PATH}
360
235
  config={BREADCRUMB_CONFIG}
@@ -378,22 +253,6 @@ export function RenderSpec({
378
253
  );
379
254
  }
380
255
 
381
- export function RenderMethod({ path }: { path: string }) {
382
- const spec = useSpec();
383
- if (!spec) return null;
384
-
385
- const parsed = parseStainlessPath(path);
386
- const resource = getResourceFromSpec(path, spec);
387
-
388
- if (!resource || !parsed) {
389
- console.warn(`Could not find resource or parsed path for '${path}'`);
390
- return null;
391
- }
392
-
393
- const method = resource.methods[parsed.method!]!;
394
- return <SDKMethod method={method} />;
395
- }
396
-
397
256
  export async function getReadmeContent(spec: SDKJSON.Spec, language: DocsLanguage) {
398
257
  const repoUrl = spec.metadata?.[language]?.repo_url;
399
258
 
@@ -410,96 +269,3 @@ export async function getReadmeContent(spec: SDKJSON.Spec, language: DocsLanguag
410
269
 
411
270
  return spec.readme[language];
412
271
  }
413
-
414
- let astroShikiHighlighter:
415
- | HighlighterGeneric<BundledLanguage, BundledTheme>
416
- | Promise<HighlighterGeneric<BundledLanguage, BundledTheme>>
417
- | null = null;
418
-
419
- async function astroHighlight() {
420
- if (astroShikiHighlighter) {
421
- return astroShikiHighlighter;
422
- }
423
-
424
- astroShikiHighlighter = createHighlighter({
425
- themes: [
426
- 'github-light',
427
- 'github-dark',
428
- {
429
- name: 'stainless-docs-json',
430
- colors: {
431
- 'editor.background': 'var(--stl-color-background)',
432
- 'editor.foreground': 'var(--stl-color-foreground)',
433
- },
434
-
435
- tokenColors: [
436
- {
437
- scope: ['comment', 'punctuation.definition.comment'],
438
- settings: { foreground: 'var(--stl-color-foreground-muted)' },
439
- },
440
- // numbers, booleans, null
441
- {
442
- scope: ['constant.numeric', 'constant.language'],
443
- settings: { foreground: 'var(--stl-color-orange-foreground)' },
444
- },
445
- // strings
446
- {
447
- scope: ['string', 'string.quoted', 'string.template'],
448
- settings: { foreground: 'var(--stl-color-green-foreground)' },
449
- },
450
- // Keys, brackets
451
- {
452
- scope: ['support.type', 'meta'],
453
- settings: { foreground: 'var(--stl-color-foreground)' },
454
- },
455
- // brackets
456
- {
457
- scope: ['meta'],
458
- settings: { foreground: 'var(--stl-color-foreground-muted)' },
459
- },
460
- // built-in types
461
- {
462
- scope: ['support.type.builtin'],
463
- settings: { foreground: 'var(--stl-color-purple-foreground)' },
464
- },
465
- ],
466
- },
467
- ],
468
- langs: SupportedLanguageSyntaxes,
469
- });
470
-
471
- return astroShikiHighlighter;
472
- }
473
-
474
- // Astro's markdown processor is a singleton
475
- // Need to cache it instead of instantiating per request
476
- let astroMarkdownProcessor: MarkdownProcessor;
477
- async function astroMarkdown() {
478
- if (!astroMarkdownProcessor) {
479
- astroMarkdownProcessor = await createMarkdownProcessor({
480
- gfm: true,
481
- remarkPlugins: [remarkGfmAlerts],
482
- shikiConfig: {
483
- themes: HIGHLIGHT_THEMES,
484
- },
485
- });
486
- }
487
-
488
- return astroMarkdownProcessor;
489
- }
490
-
491
- export async function astroMarkdownRender(content: string) {
492
- const md = await astroMarkdown();
493
- const output = await md.render(content);
494
-
495
- // Map GFM callouts to the closest Starlight equivalent
496
- output.code = output.code
497
- .replaceAll('markdown-alert-caution', 'markdown-alert-danger')
498
- .replaceAll('markdown-alert-warning', 'markdown-alert-caution')
499
- .replaceAll('markdown-alert-important', 'markdown-alert-caution')
500
- .replaceAll('markdown-alert-title', 'starlight-aside__title')
501
- .replaceAll('markdown-alert-', 'starlight-aside--')
502
- .replaceAll('markdown-alert', 'starlight-aside');
503
-
504
- return output;
505
- }
@@ -21,7 +21,7 @@ export function makePlaceholderItems(id: number) {
21
21
 
22
22
  type StarlightConfig = Parameters<typeof starlight>[0];
23
23
 
24
- export type SidebarConfigEntry = Exclude<StarlightConfig['sidebar'], undefined>[number];
24
+ type SidebarConfigEntry = Exclude<StarlightConfig['sidebar'], undefined>[number];
25
25
 
26
26
  export function getAPIReferencePlaceholderItemFromSidebarConfig(
27
27
  sidebar: SidebarConfigEntry[],
@@ -32,7 +32,7 @@ function markCurrentItems(sidebar: SidebarEntry[], currentSlug: string) {
32
32
  return mutableSidebarInstance;
33
33
  }
34
34
 
35
- export const onRequest = defineRouteMiddleware(async (context) => {
35
+ export const onRequest = defineRouteMiddleware((context) => {
36
36
  // if using content collection schema, use: context.locals.starlightRoute.entry.data.stainlessStarlight
37
37
  // this worked without collections but relied on hijacking starlightRoute: context.props.frontmatter.stainlessStarlight
38
38
  const slug = path.posix.join(import.meta.env.BASE_URL ?? '', `/${context.locals.starlightRoute.id}`); // same as .slug but not deprecated
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  import StarlightPage from '@astrojs/starlight/components/StarlightPage.astro';
3
- import { getReadmeContent, buildPageNavigation, RenderSpec, astroMarkdownRender } from '../react/Routing';
3
+ import { getReadmeContent, buildPageNavigation, RenderSpec } from '../react/Routing';
4
+ import { astroMarkdownRender } from '../markdown';
4
5
  import { getResourceFromSpec } from '@stainless-api/docs-ui/utils';
5
6
  import { parseRoute, parseStainlessPath } from '@stainless-api/docs-ui/routing';
6
7
  import {
@@ -120,3 +121,4 @@ if (props.kind === 'http_method' && props.stainlessPath) {
120
121
  <script src="../globalJs/tooltip.ts"></script>
121
122
  <script src="../globalJs/code-snippets.ts"></script>
122
123
  <script src="../globalJs/method-descriptions.ts"></script>
124
+ <script src="../globalJs/summary-selection-tweak.ts"></script>
@@ -29,7 +29,7 @@ const metadata: SpecMetadata = langsWithSpecs.map(({ language, spec }) => [
29
29
 
30
30
  // PageTitle override will skip rendering the default Starlight title
31
31
  Astro.locals._stlStarlightPage = {
32
- hasMarkdownRoute: false,
32
+ hasMarkdownRoute: true,
33
33
  };
34
34
  ---
35
35
 
@@ -41,11 +41,17 @@ Astro.locals._stlStarlightPage = {
41
41
  }}
42
42
  >
43
43
  <div class="stl-overview">
44
- <h3>Libraries</h3>
44
+ {
45
+ metadata.length > 0 && (
46
+ <>
47
+ <h3>Libraries</h3>
45
48
 
46
- <div class="stldocs-root language-blocks stl-ui-not-prose not-content">
47
- <RenderLibraries metadata={metadata} />
48
- </div>
49
+ <div class="stldocs-root language-blocks stl-ui-not-prose not-content">
50
+ <RenderLibraries metadata={metadata} />
51
+ </div>
52
+ </>
53
+ )
54
+ }
49
55
 
50
56
  <h3>API Overview</h3>
51
57
 
@@ -62,8 +68,9 @@ Astro.locals._stlStarlightPage = {
62
68
  gap: 1rem;
63
69
  margin-top: 2rem;
64
70
 
65
- .stldocs-language-block {
66
- width: 350px;
71
+ > * {
72
+ width: 0;
73
+ flex: 1 1 350px;
67
74
  }
68
75
  }
69
76
 
@@ -0,0 +1,186 @@
1
+ import type { APIRoute } from 'astro';
2
+ import { getCollection } from 'astro:content';
3
+ import { base as ASTRO_BASE } from 'astro:config/server';
4
+ import { SITE_TITLE, API_REFERENCE_BASE_PATH } from 'virtual:stl-docs-virtual-module';
5
+ import { LLMS_TXT_DESCRIPTION, LLMS_TXT_DETAIL_THRESHOLD } from 'virtual:stl-starlight-virtual-module';
6
+ import { getSDKJSONInSSR } from '../specs/fetchSpecSSR';
7
+ import Markdoc from '@markdoc/markdoc';
8
+ import type { Node } from '@markdoc/markdoc';
9
+ import * as md from '@stainless-api/docs-ui/markdown/md';
10
+ import type * as SDKJSON from '@stainless/sdk-json';
11
+ import { generateRoute, walkTree } from '@stainless-api/docs-ui/routing';
12
+
13
+ export const prerender = true;
14
+
15
+ function joinUrlParts(...parts: (string | boolean | null | undefined)[]) {
16
+ return (
17
+ '/' +
18
+ parts
19
+ .map((p) => {
20
+ if (typeof p === 'string') {
21
+ return p.split('/');
22
+ }
23
+ return p;
24
+ })
25
+ .flat()
26
+ .filter(Boolean)
27
+ .join('/')
28
+ );
29
+ }
30
+
31
+ export type ProsePageEntry = {
32
+ id: string;
33
+ title: string;
34
+ description?: string;
35
+ };
36
+
37
+ function isResourceEmpty(resource: SDKJSON.Resource) {
38
+ return Object.values(resource.methods).length < 1 && Object.values(resource.subresources ?? {}).length < 1;
39
+ }
40
+
41
+ function trimPath(path: string) {
42
+ return path.endsWith('/') ? path.slice(0, -1) : path;
43
+ }
44
+
45
+ function apiHref(basePath: string, language: string, stainlessPath: string) {
46
+ const href = generateRoute(basePath, language, stainlessPath);
47
+ if (!href) return null;
48
+ return joinUrlParts(ASTRO_BASE, href, 'index.md');
49
+ }
50
+
51
+ function linkListItem(title: string, url: string, description?: string): Node {
52
+ const children = [md.link(url, title)];
53
+ if (description) children.push(md.text(`: ${description}`));
54
+ return md.item(md.inline(...children));
55
+ }
56
+
57
+ function generateProseIndex(routes: ProsePageEntry[], detailed: boolean) {
58
+ const pageEntryLink = (page: ProsePageEntry) =>
59
+ linkListItem(
60
+ page.title,
61
+ joinUrlParts(ASTRO_BASE, page.id === 'index' ? null : page.id, 'index.md'),
62
+ detailed ? page.description : undefined,
63
+ );
64
+ return [md.heading(2, 'Docs'), md.list(...routes.map(pageEntryLink))];
65
+ }
66
+
67
+ function renderMethods(basePath: string, language: string, methods: Record<string, SDKJSON.Method>) {
68
+ const output: Node[] = [];
69
+
70
+ for (const method of Object.values(methods)) {
71
+ const href = apiHref(basePath, language, method.stainlessPath);
72
+ if (href) output.push(linkListItem(method.title, href, method.summary));
73
+ }
74
+
75
+ return md.list(...output);
76
+ }
77
+
78
+ function renderResource(
79
+ basePath: string,
80
+ language: string,
81
+ detailed: boolean,
82
+ resource: SDKJSON.Resource,
83
+ ): Node {
84
+ const href = apiHref(basePath, language, resource.stainlessPath);
85
+ const output = [md.paragraph(href ? md.link(href, resource.title) : md.text(resource.title))];
86
+ if (resource.methods && detailed) output.push(renderMethods(basePath, language, resource.methods));
87
+ if (resource.subresources) output.push(renderSubs(basePath, language, detailed, resource.subresources));
88
+ return md.item(...output);
89
+ }
90
+
91
+ function renderSubs(
92
+ basePath: string,
93
+ language: string,
94
+ detailed: boolean,
95
+ resources: Record<string, SDKJSON.Resource>,
96
+ ): Node {
97
+ return md.list(
98
+ ...Object.values(resources)
99
+ .filter((res) => !isResourceEmpty(res))
100
+ .map((sub) => renderResource(basePath, language, detailed, sub)),
101
+ );
102
+ }
103
+
104
+ function renderLanguageNote(basePath: string, languages: string[]) {
105
+ return [
106
+ md.paragraph(
107
+ md.text('Links below point to language-neutral HTTP documentation. '),
108
+ md.text(
109
+ 'SDK-specific docs follow the same URL structure with the language inserted after the base path: ',
110
+ ),
111
+ md.code(`${trimPath(basePath)}/{language}/resources/...`),
112
+ md.text('.'),
113
+ ),
114
+ md.paragraph(md.text(`Available languages: ${languages.join(', ')}`)),
115
+ ];
116
+ }
117
+
118
+ function generateRefIndex(
119
+ basePath: string,
120
+ languages: string[],
121
+ language: string,
122
+ detailed: boolean,
123
+ spec: SDKJSON.Spec,
124
+ ) {
125
+ const output = [md.heading(2, 'API Reference'), ...renderLanguageNote(basePath, languages)];
126
+
127
+ for (const resource of Object.values(spec.resources)) {
128
+ if (isResourceEmpty(resource)) continue;
129
+ const href = apiHref(basePath, language, resource.stainlessPath);
130
+ output.push(md.heading(3, [href ? md.link(href, resource.title) : md.text(resource.title)]));
131
+ if (resource.methods && detailed) output.push(renderMethods(basePath, language, resource.methods));
132
+ if (resource.subresources) output.push(renderSubs(basePath, language, detailed, resource.subresources));
133
+ }
134
+
135
+ return output;
136
+ }
137
+
138
+ function generateMarkdownIndex({
139
+ siteTitle,
140
+ description,
141
+ basePath,
142
+ languages,
143
+ spec,
144
+ detailed,
145
+ proseRoutes,
146
+ }: {
147
+ siteTitle: string;
148
+ description: string | null;
149
+ basePath: string;
150
+ languages: string[];
151
+ spec: SDKJSON.Spec;
152
+ detailed: boolean;
153
+ proseRoutes: ProsePageEntry[];
154
+ }) {
155
+ const output: Node[] = [md.heading(1, siteTitle)];
156
+ if (description) output.push(md.paragraph(md.text(description)));
157
+ if (proseRoutes.length > 0) output.push(...generateProseIndex(proseRoutes, detailed));
158
+ if (languages.length > 0) output.push(...generateRefIndex(basePath, languages, 'http', detailed, spec));
159
+
160
+ const doc = new Markdoc.Ast.Node('document', {}, output);
161
+ return Markdoc.format(doc);
162
+ }
163
+
164
+ export const GET: APIRoute = async () => {
165
+ const [docsCollection, spec] = await Promise.all([getCollection('docs'), getSDKJSONInSSR('http')]);
166
+ const apiEntries = [...walkTree(spec)].filter((n) => n.data.kind !== 'model');
167
+ const proseRoutes = docsCollection.map((entry) => ({
168
+ id: entry.id,
169
+ title: entry.data.title,
170
+ description: entry.data.description,
171
+ }));
172
+
173
+ const content = generateMarkdownIndex({
174
+ siteTitle: SITE_TITLE,
175
+ description: LLMS_TXT_DESCRIPTION,
176
+ basePath: API_REFERENCE_BASE_PATH,
177
+ languages: spec.docs?.languages ?? [],
178
+ detailed: apiEntries.length + proseRoutes.length < LLMS_TXT_DETAIL_THRESHOLD,
179
+ spec,
180
+ proseRoutes,
181
+ });
182
+
183
+ return new Response(content, {
184
+ headers: { 'Content-Type': 'text/plain; charset=utf-8' },
185
+ });
186
+ };