@stainless-api/docs 0.1.0-beta.98 → 1.0.0-beta.140

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 (138) hide show
  1. package/CHANGELOG.md +404 -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 +57 -40
  6. package/plugin/assets/languages/php.svg +4 -0
  7. package/plugin/buildAlgoliaIndex.ts +6 -12
  8. package/plugin/components/SDKSelect.astro +0 -6
  9. package/plugin/components/SnippetCode.tsx +6 -37
  10. package/plugin/components/search/SearchAlgolia.astro +1 -1
  11. package/plugin/components/search/SearchIsland.tsx +19 -13
  12. package/plugin/generateAPIReferenceLink.ts +0 -40
  13. package/plugin/globalJs/ai-dropdown-options.ts +26 -13
  14. package/plugin/globalJs/code-snippets.ts +5 -5
  15. package/plugin/globalJs/copy.ts +20 -91
  16. package/plugin/globalJs/navigation.ts +13 -13
  17. package/plugin/globalJs/summary-selection-tweak.ts +29 -0
  18. package/plugin/index.ts +107 -163
  19. package/plugin/languages.ts +2 -1
  20. package/plugin/loadPluginConfig.ts +50 -153
  21. package/plugin/markdown/highlighter.ts +100 -0
  22. package/plugin/markdown/index.ts +39 -0
  23. package/plugin/middlewareBuilder/stainlessMiddleware.d.ts +2 -0
  24. package/plugin/react/Routing.tsx +10 -244
  25. package/plugin/referencePlaceholderUtils.ts +1 -1
  26. package/plugin/replaceSidebarPlaceholderMiddleware.ts +1 -1
  27. package/plugin/routes/Docs.astro +3 -1
  28. package/plugin/routes/Overview.astro +14 -7
  29. package/plugin/routes/llms.ts +186 -0
  30. package/plugin/routes/markdown.ts +62 -13
  31. package/plugin/sidebar-utils/sidebar-builder.ts +38 -12
  32. package/plugin/specs/defaultSpecLoader.ts +192 -0
  33. package/plugin/specs/fetchSpecSSR.ts +1 -1
  34. package/plugin/specs/utils.ts +86 -0
  35. package/shared/conditionalIntegration.ts +28 -0
  36. package/shared/getProsePages.ts +6 -7
  37. package/shared/virtualModule.ts +1 -26
  38. package/stl-docs/aiChatExamples.ts +31 -0
  39. package/stl-docs/chat/docs-chat-handler.ts +17 -0
  40. package/stl-docs/chat/hook.ts +225 -0
  41. package/stl-docs/chat/schemas.ts +27 -0
  42. package/stl-docs/chat/ui/AiChat.module.css +591 -0
  43. package/stl-docs/chat/ui/AiChat.tsx +175 -0
  44. package/stl-docs/chat/ui/Trigger.tsx +154 -0
  45. package/stl-docs/chat/ui/components/ChatControls.tsx +51 -0
  46. package/stl-docs/chat/ui/components/ChatEmpty.tsx +42 -0
  47. package/stl-docs/chat/ui/components/ChatLog.tsx +93 -0
  48. package/stl-docs/chat/ui/components/ChatMessage.tsx +47 -0
  49. package/stl-docs/chat/ui/components/CodeBlock.tsx +33 -0
  50. package/stl-docs/chat/ui/components/MessageFeedback.tsx +106 -0
  51. package/stl-docs/chat/ui/components/Table.tsx +15 -0
  52. package/stl-docs/chat/ui/components/ToolCall.tsx +34 -0
  53. package/stl-docs/chat/ui/components/hljs-github.css +81 -0
  54. package/stl-docs/chat/ui/scroll-manager.ts +86 -0
  55. package/stl-docs/chat/ui/types.ts +45 -0
  56. package/stl-docs/components/AiChatIsland.tsx +10 -12
  57. package/stl-docs/components/ContentPanel.astro +9 -0
  58. package/stl-docs/components/Footer.astro +89 -0
  59. package/stl-docs/components/Header.astro +0 -5
  60. package/stl-docs/components/PageFrame.astro +23 -8
  61. package/stl-docs/components/PageSidebar.astro +11 -0
  62. package/stl-docs/components/StainlessLogo.svg +4 -0
  63. package/stl-docs/components/TwoColumnContent.astro +2 -0
  64. package/stl-docs/components/headers/DefaultHeader.astro +6 -8
  65. package/stl-docs/components/headers/StackedHeader.astro +5 -53
  66. package/stl-docs/components/mintlify-compat/Accordion.astro +2 -2
  67. package/stl-docs/components/mintlify-compat/AccordionGroup.astro +0 -4
  68. package/stl-docs/components/mintlify-compat/Columns.astro +2 -2
  69. package/stl-docs/components/mintlify-compat/Frame.astro +2 -2
  70. package/stl-docs/components/mintlify-compat/Tab.astro +2 -2
  71. package/stl-docs/components/mintlify-compat/callouts/Callout.astro +2 -2
  72. package/stl-docs/components/mintlify-compat/callouts/Check.astro +0 -4
  73. package/stl-docs/components/mintlify-compat/callouts/Danger.astro +0 -4
  74. package/stl-docs/components/mintlify-compat/callouts/Info.astro +0 -4
  75. package/stl-docs/components/mintlify-compat/callouts/Note.astro +0 -4
  76. package/stl-docs/components/mintlify-compat/callouts/Tip.astro +0 -4
  77. package/stl-docs/components/mintlify-compat/callouts/Warning.astro +0 -4
  78. package/stl-docs/components/nav-tabs/NavDropdown.astro +12 -7
  79. package/stl-docs/components/nav-tabs/NavTabs.astro +5 -3
  80. package/stl-docs/components/nav-tabs/buildNavLinks.ts +2 -0
  81. package/stl-docs/components/pagination/Pagination.astro +4 -2
  82. package/stl-docs/components/pagination/PaginationLinkEmphasized.astro +2 -2
  83. package/stl-docs/components/pagination/PaginationLinkQuiet.astro +2 -2
  84. package/stl-docs/components/pagination/util.ts +3 -3
  85. package/stl-docs/components/sidebars/BaseSidebar.astro +72 -1
  86. package/stl-docs/disableCalloutSyntax.ts +1 -1
  87. package/stl-docs/fonts.ts +5 -5
  88. package/stl-docs/index.ts +76 -53
  89. package/stl-docs/loadStlDocsConfig.ts +38 -8
  90. package/stl-docs/og-image/components/OpenGraphFunctionSignature.tsx +64 -0
  91. package/stl-docs/og-image/components/OpenGraphImage.tsx +126 -0
  92. package/stl-docs/og-image/config.ts +56 -0
  93. package/stl-docs/og-image/image-gen/generate-api-reference-og-image.tsx +188 -0
  94. package/stl-docs/og-image/image-gen/generate-og-image.tsx +119 -0
  95. package/stl-docs/og-image/image-gen/get-logo-url.ts +47 -0
  96. package/stl-docs/og-image/index.ts +135 -0
  97. package/stl-docs/og-image/routes/add-og-image.ts +45 -0
  98. package/stl-docs/og-image/routes/get-api-reference-og-image.ts +36 -0
  99. package/stl-docs/og-image/routes/get-og-image.ts +28 -0
  100. package/stl-docs/og-image/theme.ts +43 -0
  101. package/stl-docs/og-image/utils.ts +14 -0
  102. package/stl-docs/proseDocSync.test.ts +74 -0
  103. package/stl-docs/proseDocSync.ts +344 -0
  104. package/stl-docs/proseMarkdown/proseMarkdownIntegration.ts +4 -12
  105. package/stl-docs/schema-extension.ts +12 -0
  106. package/stl-docs/tabsMiddleware.ts +1 -1
  107. package/styles/overrides.css +2 -14
  108. package/styles/page.css +210 -71
  109. package/styles/sidebar.css +30 -17
  110. package/styles/sl-variables.css +3 -8
  111. package/styles/stldocs-variables.css +2 -2
  112. package/styles/toc.css +8 -0
  113. package/tsconfig.json +1 -1
  114. package/virtual-module.d.ts +35 -11
  115. package/playground-virtual-modules.d.ts +0 -96
  116. package/plugin/globalJs/create-playground.shim.ts +0 -3
  117. package/plugin/globalJs/playground-data.shim.ts +0 -1
  118. package/plugin/globalJs/playground-data.ts +0 -14
  119. package/plugin/specs/FileCache.ts +0 -99
  120. package/plugin/specs/generateSpec.ts +0 -112
  121. package/plugin/specs/index.ts +0 -132
  122. package/plugin/specs/inputResolver.ts +0 -146
  123. package/plugin/specs/worker.ts +0 -199
  124. package/plugin/vendor/preview.worker.docs.js +0 -26108
  125. package/plugin/vendor/templates/cli.md +0 -1
  126. package/plugin/vendor/templates/go.md +0 -316
  127. package/plugin/vendor/templates/java.md +0 -89
  128. package/plugin/vendor/templates/kotlin.md +0 -89
  129. package/plugin/vendor/templates/node.md +0 -235
  130. package/plugin/vendor/templates/python.md +0 -251
  131. package/plugin/vendor/templates/ruby.md +0 -147
  132. package/plugin/vendor/templates/terraform.md +0 -60
  133. package/plugin/vendor/templates/typescript.md +0 -319
  134. package/scripts/vendor_deps.ts +0 -50
  135. package/stl-docs/components/ClientRouterHead.astro +0 -41
  136. package/stl-docs/components/content-panel/ContentPanel.astro +0 -42
  137. package/stl-docs/components/headers/SplashMobileMenuToggle.astro +0 -65
  138. package/stl-docs/proseSearchIndexing.ts +0 -606
package/stl-docs/index.ts CHANGED
@@ -6,7 +6,8 @@ import { disableCalloutSyntaxStarlightPlugin } from './disableCalloutSyntax';
6
6
  import type { AstroIntegration } from 'astro';
7
7
 
8
8
  import { normalizeRedirects, type NormalizedRedirectConfig } from './redirects';
9
- import { join } from 'path';
9
+ import path from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
10
11
  import { mkdirSync, writeFileSync } from 'fs';
11
12
  import {
12
13
  parseStlDocsConfig,
@@ -17,13 +18,15 @@ import {
17
18
  type StarlightSidebarConfig,
18
19
  } from './loadStlDocsConfig';
19
20
  import { buildVirtualModuleString } from '../shared/virtualModule';
20
- import type * as StlDocsVirtualModule from 'virtual:stl-docs-virtual-module';
21
21
  import { resolveSrcFile } from '../resolveSrcFile';
22
22
  import { stainlessDocsMarkdownRenderer } from './proseMarkdown/proseMarkdownIntegration';
23
23
  import { setSharedLogger } from '../shared/getSharedLogger';
24
- import { stainlessDocsAlgoliaProseIndexing, stainlessDocsVectorProseIndexing } from './proseSearchIndexing';
24
+ import { stainlessDocsVectorProseIndexing } from './proseDocSync';
25
25
  import { stainlessStarlight } from '../plugin';
26
26
  import { getFontRoles, flattenFonts } from './fonts';
27
+ import conditionalIntegration from '../shared/conditionalIntegration';
28
+ import { generateExamplesVirtualModule } from './aiChatExamples';
29
+ import { ogImageStarlightPlugin } from './og-image';
27
30
 
28
31
  export * from '../plugin';
29
32
 
@@ -49,18 +52,22 @@ function stainlessDocsStarlightIntegration(config: NormalizedStainlessDocsConfig
49
52
 
50
53
  type ComponentOverrides = StarlightConfigDefined['components'];
51
54
  const componentOverrides: ComponentOverrides = {
55
+ Head: resolveSrcFile(COMPONENTS_FOLDER, './Head.astro'),
56
+
52
57
  PageFrame: resolveSrcFile(COMPONENTS_FOLDER, './PageFrame.astro'),
58
+ TwoColumnContent: resolveSrcFile(COMPONENTS_FOLDER, './TwoColumnContent.astro'),
53
59
 
54
- Head: resolveSrcFile(COMPONENTS_FOLDER, './Head.astro'),
55
60
  Header: resolveSrcFile(COMPONENTS_FOLDER, './Header.astro'),
56
61
  ThemeProvider: resolveSrcFile(COMPONENTS_FOLDER, './ThemeProvider.astro'),
57
62
  ThemeSelect: resolveSrcFile(COMPONENTS_FOLDER, './ThemeSelect.astro'),
58
63
 
59
64
  Sidebar: resolveSrcFile(COMPONENTS_FOLDER, './sidebars/BaseSidebar.astro'),
60
- ContentPanel: resolveSrcFile(COMPONENTS_FOLDER, './content-panel/ContentPanel.astro'),
65
+ ContentPanel: resolveSrcFile(COMPONENTS_FOLDER, './ContentPanel.astro'),
66
+ PageTitle: resolveSrcFile(COMPONENTS_FOLDER, './PageTitle.astro'),
67
+ PageSidebar: resolveSrcFile(COMPONENTS_FOLDER, './PageSidebar.astro'),
61
68
  TableOfContents: resolveSrcFile(COMPONENTS_FOLDER, './TableOfContents.astro'),
62
69
 
63
- PageTitle: resolveSrcFile(COMPONENTS_FOLDER, './PageTitle.astro'),
70
+ Footer: resolveSrcFile(COMPONENTS_FOLDER, './Footer.astro'),
64
71
  Pagination: resolveSrcFile(COMPONENTS_FOLDER, './pagination/Pagination.astro'),
65
72
  };
66
73
 
@@ -69,7 +76,7 @@ function stainlessDocsStarlightIntegration(config: NormalizedStainlessDocsConfig
69
76
  disableCalloutSyntaxStarlightPlugin,
70
77
  ];
71
78
 
72
- if (config.apiReference !== null) {
79
+ if (config.apiReference) {
73
80
  plugins.push(
74
81
  stainlessStarlight({
75
82
  ...config.apiReference,
@@ -84,7 +91,11 @@ function stainlessDocsStarlightIntegration(config: NormalizedStainlessDocsConfig
84
91
  componentOverrides.Search = resolveSrcFile('/plugin/components/search/Search.astro');
85
92
  }
86
93
 
87
- plugins.push(...config.starlightCompat.plugins, ...config.plugins.map((p) => p(config)));
94
+ if (config.ogImage) {
95
+ plugins.push(ogImageStarlightPlugin(config.ogImage, config));
96
+ }
97
+
98
+ plugins.push(...config.starlightCompat.plugins);
88
99
 
89
100
  // TODO: re-add once we figure out what to do with the client router
90
101
  // if (config.enableClientRouter) {
@@ -98,6 +109,7 @@ function stainlessDocsStarlightIntegration(config: NormalizedStainlessDocsConfig
98
109
 
99
110
  return starlight({
100
111
  ...config.starlightPassThrough,
112
+ pagefind: config.starlightCompat.pagefind,
101
113
  sidebar,
102
114
  components: {
103
115
  ...componentOverrides,
@@ -154,8 +166,6 @@ function stainlessDocsIntegration(
154
166
  config: NormalizedStainlessDocsConfig,
155
167
  apiReferenceBasePath: string | null,
156
168
  ): AstroIntegration {
157
- // The '\0' prefix tells Vite “this is a virtual module” and prevents it from being resolved again.
158
- const resolveVirtualModuleId = (id: string) => `\0${id}`;
159
169
  let redirects: NormalizedRedirectConfig | null = null;
160
170
 
161
171
  return {
@@ -168,43 +178,64 @@ function stainlessDocsIntegration(
168
178
  redirects = normalizeRedirects(astroConfig.redirects);
169
179
  }
170
180
 
171
- const virtualModules = new Map(
172
- Object.entries({
173
- 'virtual:stl-docs-virtual-module': buildVirtualModuleString({
174
- TABS: config.tabs,
181
+ const base = astroConfig.base ?? '/';
182
+ const withBase = (link: string) =>
183
+ /^([a-z][a-z0-9+.-]*:|\/\/)/.test(link) ? link : path.posix.join(base, link);
184
+
185
+ let vmAiChatHandlerExport = 'export const AI_CHAT_HANDLER = undefined;';
186
+ if (config.aiChat?.handlerEntrypoint) {
187
+ const rawEntrypoint = config.aiChat.handlerEntrypoint;
188
+ const handlerEntrypoint = rawEntrypoint.startsWith('file://')
189
+ ? fileURLToPath(rawEntrypoint)
190
+ : path.isAbsolute(rawEntrypoint)
191
+ ? rawEntrypoint
192
+ : path.resolve(fileURLToPath(astroConfig.root), rawEntrypoint);
193
+ vmAiChatHandlerExport = `export { default as AI_CHAT_HANDLER } from '${handlerEntrypoint}';`;
194
+ }
195
+
196
+ const virtualModules = new Map<string, string>([
197
+ [
198
+ 'virtual:stl-docs-virtual-module',
199
+ buildVirtualModuleString({
200
+ TABS: config.tabs.map((tab) => ({ ...tab, link: withBase(tab.link) })),
175
201
  SPLIT_TABS_ENABLED: config.splitTabsEnabled,
176
- HEADER_LINKS: config.header.links,
202
+ HEADER_LINKS: config.header.links.map((link) => ({ ...link, link: withBase(link.link) })),
177
203
  HEADER_LAYOUT: config.header.layout,
178
204
  ENABLE_CLIENT_ROUTER: config.enableClientRouter,
179
205
  API_REFERENCE_BASE_PATH: apiReferenceBasePath ?? '/api',
180
206
  ENABLE_PROSE_MARKDOWN_RENDERING: config.enableProseMarkdownRendering,
181
- ENABLE_CONTEXT_MENU: config.contextMenu, // TODO: do not duplicate this between both virtual modules
207
+ ENABLE_CONTEXT_MENU: !!config.contextMenu, // TODO: do not duplicate this between both virtual modules
208
+ CONTEXT_MENU_ENABLE_THIRD_PARTY:
209
+ (typeof config.contextMenu === 'object' ? config.contextMenu.thirdParty : null) ?? true,
182
210
  RENDER_PAGE_DESCRIPTIONS: config.renderPageDescriptions,
183
211
  FONTS: getFontRoles(config.fonts),
184
- } satisfies typeof StlDocsVirtualModule),
185
-
186
- 'virtual:stl-docs/components/AiChat.tsx': `
187
- ${
188
- config.aiChat
189
- ? `export { default } from ${JSON.stringify(config.aiChat.chatComponentPath)};`
190
- : // export null when no AI chat component is provided
191
- `export default null;`
212
+ LINK_GROUP_TITLES_TO_OVERVIEW_PAGES: config.linkGroupTitlesToOverviewPages,
213
+ RENDER_CREDITS: config.credits,
214
+ SITE_TITLE: config.siteTitle,
215
+ }),
216
+ ],
217
+ ['virtual:stl-docs-ai-chat', vmAiChatHandlerExport],
218
+ ]);
219
+ if (config.aiChat) {
220
+ virtualModules.set(
221
+ 'virtual:stl-docs-ai-chat-examples',
222
+ generateExamplesVirtualModule(config.aiChat.examples),
223
+ );
192
224
  }
193
- export const STAINLESS_PROJECT = ${config.apiReference ? JSON.stringify(config.apiReference.stainlessProject) : 'undefined'};
194
- `,
195
- }),
196
- );
197
225
 
198
226
  updateConfig({
199
- experimental: {
200
- fonts: [...flattenFonts(config.fonts), ...(astroConfig.experimental?.fonts ?? [])],
201
- },
227
+ fonts: [...flattenFonts(config.fonts), ...(astroConfig?.fonts ?? [])],
202
228
  vite: {
229
+ define: {
230
+ __STLDOCS_HAS_API_REFERENCE__: !!config.apiReference,
231
+ __STLDOCS_ENABLE_AI_CHAT__: !!config.aiChat,
232
+ },
203
233
  plugins: [
204
234
  {
205
- name: 'stl-docs-vite',
235
+ name: 'stl-docs-virtual-modules',
206
236
  resolveId(id) {
207
- if (virtualModules.has(id)) return resolveVirtualModuleId(id);
237
+ // The '\0' prefix tells Vite "this is a virtual module" and prevents it from being resolved again.
238
+ if (virtualModules.has(id)) return `\0${id}`;
208
239
  },
209
240
  load(id) {
210
241
  const bare = id.replace(/^\0/, '');
@@ -212,18 +243,6 @@ function stainlessDocsIntegration(
212
243
  },
213
244
  },
214
245
  ],
215
- optimizeDeps: {
216
- include: config.aiChat
217
- ? [
218
- '@stainless-api/docs-ai-chat > @stainless-api/ai-chat > lucide-react',
219
- '@stainless-api/docs-ai-chat > @stainless-api/ai-chat > motion',
220
- '@stainless-api/docs-ai-chat > @stainless-api/ai-chat > motion > framer-motion',
221
- '@stainless-api/docs-ai-chat > @stainless-api/ai-chat > react-markdown',
222
- '@stainless-api/docs-ai-chat > @stainless-api/ai-chat > react-syntax-highlighter',
223
- '@stainless-api/docs-ai-chat > @stainless-api/ai-chat > remark-gfm',
224
- ]
225
- : [],
226
- },
227
246
  },
228
247
  build: {
229
248
  ...astroConfig.build,
@@ -233,9 +252,9 @@ function stainlessDocsIntegration(
233
252
  },
234
253
  'astro:build:done': ({ dir }) => {
235
254
  if (redirects !== null) {
236
- const stainlessDir = join(dir.pathname, '_stainless');
255
+ const stainlessDir = path.join(dir.pathname, '_stainless');
237
256
  mkdirSync(stainlessDir, { recursive: true });
238
- const outputPath = join(stainlessDir, 'redirects.json');
257
+ const outputPath = path.join(stainlessDir, 'redirects.json');
239
258
  writeFileSync(outputPath, JSON.stringify(redirects, null, 2), {
240
259
  encoding: 'utf-8',
241
260
  });
@@ -256,7 +275,7 @@ function sharedLoggerIntegration(): AstroIntegration {
256
275
  };
257
276
  }
258
277
 
259
- export function stainlessDocs(config: StainlessDocsUserConfig): StarlightPlugin[] {
278
+ export function stainlessDocs(config: StainlessDocsUserConfig): AstroIntegration[] {
260
279
  const normalizedConfigResult = parseStlDocsConfig(config);
261
280
  if (normalizedConfigResult.result === 'error') {
262
281
  // TODO: would be good to use the astro logger somehow
@@ -279,11 +298,15 @@ export function stainlessDocs(config: StainlessDocsUserConfig): StarlightPlugin[
279
298
  react(),
280
299
  stainlessDocsStarlightIntegration(normalizedConfig),
281
300
  stainlessDocsIntegration(normalizedConfig, apiReferenceBasePath),
282
- stainlessDocsMarkdownRenderer({
283
- enabled: normalizedConfig.enableProseMarkdownRendering,
284
- apiReferenceBasePath,
301
+ conditionalIntegration({
302
+ condition: !config.experimental?.disableProseMarkdownRendering,
303
+ integration: stainlessDocsMarkdownRenderer({ apiReferenceBasePath }),
304
+ reason: 'disabled by experimental config "disableProseMarkdownRendering"',
305
+ }),
306
+ conditionalIntegration({
307
+ condition: !config.experimental?.disableStainlessProseIndexing,
308
+ integration: stainlessDocsVectorProseIndexing(normalizedConfig, apiReferenceBasePath),
309
+ reason: 'disabled by experimental config "disableStainlessProseIndexing"',
285
310
  }),
286
- stainlessDocsAlgoliaProseIndexing({ apiReferenceBasePath }),
287
- stainlessDocsVectorProseIndexing(normalizedConfig, apiReferenceBasePath),
288
311
  ];
289
312
  }
@@ -1,9 +1,11 @@
1
1
  import type { StainlessStarlightUserConfig } from '../plugin/loadPluginConfig';
2
- import type { StarlightPlugin, StarlightUserConfig } from '@astrojs/starlight/types';
2
+ import type { StarlightUserConfig } from '@astrojs/starlight/types';
3
3
  import type { ButtonVariant } from '@stainless-api/ui-primitives';
4
4
  import type { AnchorHTMLAttributes } from 'react';
5
5
  import type starlight from '@astrojs/starlight';
6
6
  import { normalizeFonts, type StlDocsFontConfig } from './fonts';
7
+ import type { ExamplePromptResponse } from './aiChatExamples';
8
+ import type { OGImageConfig } from './og-image/config';
7
9
 
8
10
  type StarlightConfig = Parameters<typeof starlight>[0];
9
11
 
@@ -38,7 +40,7 @@ type PassThroughStarlightConfigOptions = Pick<
38
40
 
39
41
  type ExperimentalStarlightCompatibilityConfig = Pick<
40
42
  StarlightConfigDefined,
41
- 'components' | 'routeMiddleware' | 'plugins' | 'prerender'
43
+ 'components' | 'routeMiddleware' | 'plugins' | 'prerender' | 'pagefind'
42
44
  >;
43
45
 
44
46
  export type Tabs = {
@@ -67,6 +69,7 @@ export type StainlessDocsUserConfig = {
67
69
  layout?: HeaderLayout;
68
70
  links?: HeaderLink[];
69
71
  };
72
+ disableCredits?: boolean;
70
73
  fonts?: StlDocsFontConfig;
71
74
  experimental?: {
72
75
  starlightCompat?: ExperimentalStarlightCompatibilityConfig;
@@ -77,14 +80,21 @@ export type StainlessDocsUserConfig = {
77
80
  * @default false
78
81
  */
79
82
  disableProseMarkdownRendering?: boolean;
80
- aiChat?: { chatComponentPath: string };
83
+ disableStainlessProseIndexing?: boolean;
84
+ aiChat?: { handlerEntrypoint: string; examples?: ExamplePromptResponse };
85
+ /**
86
+ * Whether to link group titles to overview pages. Note: overview pages must already be present in the sidebar for this to work.
87
+ *
88
+ * @default false
89
+ */
90
+ linkGroupTitlesToOverviewPages?: boolean;
81
91
  };
82
92
  /**
83
93
  * Whether to show the context menu with options like "Copy as Markdown" and "Open in ChatGPT".
84
94
  *
85
95
  * @default true
86
96
  */
87
- contextMenu?: boolean;
97
+ contextMenu?: boolean | { thirdParty?: boolean };
88
98
 
89
99
  /**
90
100
  * Whether to render page descriptions in prose page headers
@@ -93,10 +103,11 @@ export type StainlessDocsUserConfig = {
93
103
  */
94
104
  renderPageDescriptions?: boolean;
95
105
  /**
96
- * Stainless Docs plugins.
97
- * Each plugin is a function that receives the normalized config and returns a Starlight plugin.
106
+ * Enable and configure Open Graph image generation.
107
+ *
108
+ * Requires `takumi-js` to be installed as a dependency.
98
109
  */
99
- plugins?: ((config: Exclude<NormalizedStainlessDocsConfig, 'plugins'>) => StarlightPlugin)[];
110
+ ogImage?: OGImageConfig;
100
111
  } & PassThroughStarlightConfigOptions;
101
112
 
102
113
  type HeaderLayout = 'default' | 'stacked';
@@ -128,6 +139,19 @@ function normalizeRouteMiddleware(userConfig: StainlessDocsUserConfig) {
128
139
  return entry;
129
140
  }
130
141
 
142
+ function normalizeSiteTitle(userConfig: StainlessDocsUserConfig) {
143
+ if (typeof userConfig.title === 'string') {
144
+ return userConfig.title;
145
+ }
146
+ if (typeof userConfig.title === 'object') {
147
+ const firstValue = Object.values(userConfig.title)[0];
148
+ if (typeof firstValue === 'string') {
149
+ return firstValue;
150
+ }
151
+ }
152
+ throw new Error('Site title provided in config is not valid.');
153
+ }
154
+
131
155
  function normalizeConfig(userConfig: StainlessDocsUserConfig) {
132
156
  const splitTabsEnabled = areSplitTabsEnabled(userConfig);
133
157
 
@@ -160,18 +184,24 @@ function normalizeConfig(userConfig: StainlessDocsUserConfig) {
160
184
  starlightCompat: {
161
185
  components: userConfig.experimental?.starlightCompat?.components ?? {},
162
186
  plugins: userConfig.experimental?.starlightCompat?.plugins ?? [],
187
+ pagefind: userConfig.experimental?.starlightCompat?.pagefind ?? true,
163
188
  routeMiddleware: normalizeRouteMiddleware(userConfig),
164
189
  },
165
190
  enableClientRouter: userConfig.experimental?.enableClientRouter ?? false,
166
191
  apiReference: userConfig.apiReference ?? null,
167
192
  sidebar: userConfig.sidebar,
193
+ enableStainlessProseIndexing:
194
+ userConfig.experimental?.disableStainlessProseIndexing === true ? false : true,
168
195
  enableProseMarkdownRendering:
169
196
  userConfig.experimental?.disableProseMarkdownRendering === true ? false : true,
170
197
  contextMenu: userConfig.contextMenu ?? true,
171
198
  expressiveCode: userConfig.expressiveCode,
172
199
  renderPageDescriptions: userConfig.renderPageDescriptions ?? true,
173
- plugins: userConfig.plugins ?? [],
174
200
  aiChat: userConfig.experimental?.aiChat,
201
+ linkGroupTitlesToOverviewPages: userConfig.experimental?.linkGroupTitlesToOverviewPages ?? false,
202
+ credits: !userConfig.disableCredits,
203
+ siteTitle: normalizeSiteTitle(userConfig),
204
+ ogImage: userConfig.ogImage ?? null,
175
205
  };
176
206
 
177
207
  return configWithDefaults;
@@ -0,0 +1,64 @@
1
+ import { darkThemeVars, lightThemeVars } from '../theme';
2
+
3
+ export default function OpenGraphFunctionSignature({
4
+ params,
5
+ fullyQualifiedName,
6
+ theme,
7
+ }: {
8
+ params: { ident: string; optional?: boolean }[] | undefined;
9
+ fullyQualifiedName: string | undefined;
10
+ theme?: 'light' | 'dark';
11
+ }) {
12
+ if (!params || !fullyQualifiedName) {
13
+ return null;
14
+ }
15
+ const colors = theme === 'dark' ? darkThemeVars : lightThemeVars;
16
+
17
+ const joinedParams = params
18
+ .map((param) => {
19
+ if (param.optional) {
20
+ return `${param.ident}?`;
21
+ }
22
+ return param.ident;
23
+ })
24
+ .filter((p): p is string => p !== null)
25
+ .join(', ');
26
+
27
+ return (
28
+ <p
29
+ style={{
30
+ lineClamp: 1,
31
+ textOverflow: 'ellipsis',
32
+ fontSize: '33px',
33
+ lineHeight: '150%',
34
+ overflow: 'hidden',
35
+ width: '100%',
36
+ color: colors.foreground,
37
+ marginBottom: 0,
38
+ marginTop: 0,
39
+ }}
40
+ >
41
+ <span
42
+ style={{
43
+ color: colors.foregroundReduced,
44
+ }}
45
+ >
46
+ {fullyQualifiedName.split('.').slice(0, -1).join('.')}.
47
+ </span>
48
+ <span
49
+ style={{
50
+ fontWeight: 600,
51
+ }}
52
+ >
53
+ {fullyQualifiedName.split('.').slice(-1)}
54
+ </span>
55
+ <span
56
+ style={{
57
+ fontWeight: 600,
58
+ }}
59
+ >
60
+ ({joinedParams})
61
+ </span>
62
+ </p>
63
+ );
64
+ }
@@ -0,0 +1,126 @@
1
+ import { darkThemeVars, lightThemeVars, typography } from '../theme';
2
+ import { resolveLocalImageFile } from '../image-gen/get-logo-url';
3
+ import { OG_IMAGE_OPTIONS } from 'virtual:stainless-docs/docs-og-image';
4
+
5
+ /* The default open graph image template. It is expected to be used with takumi-js */
6
+ export default function OpenGraphImage({
7
+ title,
8
+ description,
9
+ logo,
10
+ children,
11
+ theme,
12
+ breadcrumbs,
13
+ }: {
14
+ title: string;
15
+ description?: string;
16
+ logo?: string;
17
+ children?: React.ReactNode;
18
+ theme?: 'light' | 'dark';
19
+ breadcrumbs?: string[];
20
+ }) {
21
+ const colors = theme === 'dark' ? darkThemeVars : lightThemeVars;
22
+
23
+ const testLogo = OG_IMAGE_OPTIONS?.backgroundImage
24
+ ? resolveLocalImageFile(OG_IMAGE_OPTIONS.backgroundImage.src)
25
+ : undefined;
26
+
27
+ return (
28
+ <div
29
+ style={{
30
+ backgroundColor: colors.background,
31
+ width: '100%',
32
+ height: '100%',
33
+ display: 'flex',
34
+ alignItems: 'flex-start',
35
+ justifyContent: 'space-between',
36
+ flexDirection: 'column',
37
+ padding: '72px',
38
+ position: 'relative',
39
+ fontFeatureSettings: "'ss01' on, 'ss03' on, 'ss04' on, 'ss06' on",
40
+ lineHeight: `${typography.baseLineHeight}`,
41
+ fontSize: `${typography.baseFontSize}`,
42
+ letterSpacing: `${typography.baseLetterSpacing}`,
43
+ }}
44
+ >
45
+ {testLogo && (
46
+ <img
47
+ src={testLogo}
48
+ alt="Background"
49
+ style={{
50
+ position: 'absolute',
51
+ top: 0,
52
+ right: 0,
53
+ zIndex: -1,
54
+ ...OG_IMAGE_OPTIONS?.backgroundImage?.style,
55
+ }}
56
+ />
57
+ )}
58
+ {logo && <img src={logo} alt="Logo" style={{ height: '80px', marginBottom: '24px' }} />}
59
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '16px', width: '100%' }}>
60
+ {breadcrumbs && breadcrumbs.length > 0 && (
61
+ <div style={{ display: 'flex', alignItems: 'center', gap: '8px', color: colors.foregroundMuted }}>
62
+ {breadcrumbs.map((crumb, index) => (
63
+ <div key={index} style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
64
+ {index > 0 && (
65
+ <svg
66
+ width="32"
67
+ height="32"
68
+ viewBox="0 0 32 32"
69
+ fill="none"
70
+ xmlns="http://www.w3.org/2000/svg"
71
+ >
72
+ <g opacity="0.6">
73
+ <path
74
+ d="M11.0573 7.05727C11.578 6.53657 12.422 6.53657 12.9427 7.05727L20.9427 15.0573C21.4634 15.578 21.4634 16.422 20.9427 16.9427L12.9427 24.9427C12.422 25.4634 11.578 25.4634 11.0573 24.9427C10.5366 24.422 10.5366 23.578 11.0573 23.0573L18.1146 16L11.0573 8.94269C10.5366 8.42199 10.5366 7.57797 11.0573 7.05727Z"
75
+ fill={colors.foreground}
76
+ />
77
+ </g>
78
+ </svg>
79
+ )}
80
+ <span
81
+ style={{
82
+ fontSize: `${typography.breadcrumbFontSize}`,
83
+ lineHeight: `${typography.breadcrumbLineHeight}`,
84
+ letterSpacing: `${typography.breadcrumbLetterSpacing}`,
85
+ }}
86
+ >
87
+ {crumb}
88
+ </span>
89
+ </div>
90
+ ))}
91
+ </div>
92
+ )}
93
+ <h1
94
+ style={{
95
+ marginBottom: '0',
96
+ marginTop: '0',
97
+ fontWeight: 600,
98
+ color: colors.foreground,
99
+ letterSpacing: `${typography.headerLetterSpacing}`,
100
+ lineClamp: 2,
101
+ textOverflow: 'ellipsis',
102
+ lineHeight: `${typography.headerLineHeight}`,
103
+ fontSize: `${typography.headerFontSize}`,
104
+ textWrap: 'balance',
105
+ }}
106
+ >
107
+ {title}
108
+ </h1>
109
+ {description && (
110
+ <p
111
+ style={{
112
+ color: colors.foregroundMuted,
113
+ marginBottom: 0,
114
+ marginTop: 0,
115
+ lineClamp: 2,
116
+ textOverflow: 'ellipsis',
117
+ }}
118
+ >
119
+ {description}
120
+ </p>
121
+ )}
122
+ {children}
123
+ </div>
124
+ </div>
125
+ );
126
+ }
@@ -0,0 +1,56 @@
1
+ // Exported utilites for @stainless-api/docs consumers
2
+
3
+ import type { ImageResponseOptions } from 'takumi-js/response';
4
+ import type { CSSProperties } from 'react';
5
+
6
+ export type OGImageConfig = {
7
+ /**
8
+ * Path to source file for logo to include in generated OG images
9
+ *
10
+ * example: './src/assets/og-logo.png'
11
+ */
12
+ logo?: string;
13
+ /**
14
+ * Takumi ImageResponseOptions for OG image generation
15
+ */
16
+ renderOptions?: Omit<ImageResponseOptions, 'fonts'>;
17
+ /**
18
+ * A background image for the OG images. A tailwind `tw` string can be provided to style the image.
19
+ *
20
+ * example: './src/assets/og-background-logo.png'
21
+ */
22
+ backgroundImage?: {
23
+ /**
24
+ * Path to source file for background image
25
+ *
26
+ * example: './src/assets/og-background-logo.png'
27
+ */
28
+ src: string;
29
+ /**
30
+ * Style applied to the background image using React CSSProperties
31
+ *
32
+ * example: { right: -20px }
33
+ */
34
+ style?: CSSProperties;
35
+ };
36
+ /** Preferred theme for the OG images
37
+ */
38
+ theme?: 'light' | 'dark';
39
+ /** The base path for the docs site. To be used when setting `base` within your astro config is not sufficient depending on hosting strategy.
40
+ * If your docs site is hosted at a subpath (e.g. example.com/docs), set the basePath to '/docs'.
41
+ *
42
+ * example: '/docs'
43
+ */
44
+ basePath?: string;
45
+ /**
46
+ * Override the default OG image components with custom implementations.
47
+ * Each value should be a file path to a component that exports a default React component.
48
+ *
49
+ * You can import the default components from `@stainless-api/docs/og-image/components/OpenGraphImage`
50
+ * and `@stainless-api/docs/og-image/components/OpenGraphFunctionSignature` to compose with them.
51
+ */
52
+ components?: {
53
+ OpenGraphImage?: string;
54
+ OpenGraphFunctionSignature?: string;
55
+ };
56
+ };