@stainless-api/docs 0.1.0-beta.10 → 0.1.0-beta.101

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 (145) hide show
  1. package/CHANGELOG.md +855 -0
  2. package/eslint-suppressions.json +27 -0
  3. package/locals.d.ts +17 -0
  4. package/package.json +49 -40
  5. package/playground-virtual-modules.d.ts +96 -0
  6. package/plugin/assets/languages/cli.svg +14 -0
  7. package/plugin/assets/languages/csharp.svg +1 -0
  8. package/plugin/assets/languages/php.svg +4 -0
  9. package/plugin/buildAlgoliaIndex.ts +40 -39
  10. package/plugin/components/MethodDescription.tsx +54 -0
  11. package/plugin/components/RequestBuilder/ParamEditor.tsx +55 -0
  12. package/plugin/components/RequestBuilder/SnippetStainlessIsland.tsx +107 -0
  13. package/plugin/components/RequestBuilder/index.tsx +37 -0
  14. package/plugin/components/RequestBuilder/props.ts +9 -0
  15. package/plugin/components/RequestBuilder/spec-helpers.ts +47 -0
  16. package/plugin/components/RequestBuilder/styles.css +67 -0
  17. package/plugin/components/SDKSelect.astro +18 -105
  18. package/plugin/components/SnippetCode.tsx +111 -66
  19. package/plugin/components/StainlessIslands.tsx +126 -0
  20. package/plugin/components/search/SearchAlgolia.astro +45 -28
  21. package/plugin/components/search/SearchIsland.tsx +47 -29
  22. package/plugin/generateAPIReferenceLink.ts +2 -2
  23. package/plugin/globalJs/ai-dropdown-options.ts +243 -0
  24. package/plugin/globalJs/code-snippets.ts +40 -11
  25. package/plugin/globalJs/copy.ts +95 -17
  26. package/plugin/globalJs/create-playground.shim.ts +3 -0
  27. package/plugin/globalJs/method-descriptions.ts +33 -0
  28. package/plugin/globalJs/navigation.ts +12 -32
  29. package/plugin/globalJs/playground-data.shim.ts +1 -0
  30. package/plugin/globalJs/playground-data.ts +14 -0
  31. package/plugin/helpers/generateDocsRoutes.ts +59 -0
  32. package/plugin/helpers/multiSpec.ts +8 -0
  33. package/plugin/index.ts +299 -118
  34. package/plugin/languages.ts +8 -2
  35. package/plugin/loadPluginConfig.ts +251 -107
  36. package/plugin/middlewareBuilder/stainlessMiddleware.d.ts +1 -1
  37. package/plugin/react/Routing.tsx +210 -141
  38. package/plugin/referencePlaceholderUtils.ts +18 -15
  39. package/plugin/replaceSidebarPlaceholderMiddleware.ts +38 -34
  40. package/plugin/routes/Docs.astro +70 -119
  41. package/plugin/routes/DocsStatic.astro +6 -5
  42. package/plugin/routes/Overview.astro +37 -27
  43. package/plugin/routes/markdown.ts +13 -12
  44. package/plugin/{cms → sidebar-utils}/sidebar-builder.ts +49 -60
  45. package/plugin/specs/FileCache.ts +99 -0
  46. package/plugin/specs/fetchSpecSSR.ts +27 -0
  47. package/plugin/specs/generateSpec.ts +112 -0
  48. package/plugin/specs/index.ts +132 -0
  49. package/plugin/specs/inputResolver.ts +146 -0
  50. package/plugin/{cms → specs}/worker.ts +82 -5
  51. package/plugin/vendor/preview.worker.docs.js +22406 -17955
  52. package/plugin/vendor/templates/cli.md +1 -0
  53. package/plugin/vendor/templates/go.md +4 -2
  54. package/plugin/vendor/templates/java.md +3 -1
  55. package/plugin/vendor/templates/kotlin.md +3 -1
  56. package/plugin/vendor/templates/node.md +4 -2
  57. package/plugin/vendor/templates/python.md +4 -2
  58. package/plugin/vendor/templates/ruby.md +4 -2
  59. package/plugin/vendor/templates/terraform.md +1 -1
  60. package/plugin/vendor/templates/typescript.md +3 -1
  61. package/resolveSrcFile.ts +10 -0
  62. package/scripts/vendor_deps.ts +5 -5
  63. package/shared/getProsePages.ts +41 -0
  64. package/shared/getSharedLogger.ts +15 -0
  65. package/shared/terminalUtils.ts +3 -0
  66. package/shared/virtualModule.ts +54 -1
  67. package/src/content.config.ts +9 -0
  68. package/stl-docs/components/AIDropdown.tsx +63 -0
  69. package/stl-docs/components/AiChatIsland.tsx +14 -0
  70. package/stl-docs/components/{content-panel/ContentBreadcrumbs.tsx → ContentBreadcrumbs.tsx} +2 -2
  71. package/stl-docs/components/Head.astro +20 -0
  72. package/stl-docs/components/Header.astro +6 -8
  73. package/stl-docs/components/PageFrame.astro +18 -0
  74. package/stl-docs/components/PageTitle.astro +82 -0
  75. package/stl-docs/components/TableOfContents.astro +34 -0
  76. package/stl-docs/components/ThemeProvider.astro +36 -0
  77. package/stl-docs/components/ThemeSelect.astro +84 -139
  78. package/stl-docs/components/content-panel/ContentPanel.astro +16 -46
  79. package/stl-docs/components/headers/SplashMobileMenuToggle.astro +17 -1
  80. package/stl-docs/components/headers/StackedHeader.astro +29 -24
  81. package/stl-docs/components/icons/chat-gpt.tsx +2 -2
  82. package/stl-docs/components/icons/cursor.tsx +10 -0
  83. package/stl-docs/components/icons/gemini.tsx +19 -0
  84. package/stl-docs/components/icons/markdown.tsx +1 -1
  85. package/stl-docs/components/index.ts +1 -0
  86. package/stl-docs/components/mintlify-compat/Accordion.astro +5 -3
  87. package/stl-docs/components/mintlify-compat/AccordionGroup.astro +3 -3
  88. package/stl-docs/components/mintlify-compat/Columns.astro +40 -42
  89. package/stl-docs/components/mintlify-compat/Frame.astro +16 -18
  90. package/stl-docs/components/mintlify-compat/card.css +33 -35
  91. package/stl-docs/components/mintlify-compat/index.ts +2 -4
  92. package/stl-docs/components/nav-tabs/NavDropdown.astro +31 -70
  93. package/stl-docs/components/nav-tabs/NavTabs.astro +78 -80
  94. package/stl-docs/components/nav-tabs/SecondaryNavTabs.astro +15 -8
  95. package/stl-docs/components/nav-tabs/buildNavLinks.ts +3 -2
  96. package/stl-docs/components/pagination/HomeLink.astro +10 -0
  97. package/stl-docs/components/pagination/Pagination.astro +175 -0
  98. package/stl-docs/components/pagination/PaginationLinkEmphasized.astro +22 -0
  99. package/stl-docs/components/pagination/PaginationLinkQuiet.astro +13 -0
  100. package/stl-docs/components/pagination/util.ts +71 -0
  101. package/stl-docs/components/scripts.ts +1 -0
  102. package/stl-docs/components/sidebars/BaseSidebar.astro +79 -2
  103. package/stl-docs/components/sidebars/SidebarWithComponents.tsx +10 -0
  104. package/stl-docs/components/sidebars/convertAstroSidebarToStl.tsx +62 -0
  105. package/stl-docs/disableCalloutSyntax.ts +36 -0
  106. package/stl-docs/fonts.ts +186 -0
  107. package/stl-docs/index.ts +154 -51
  108. package/stl-docs/loadStlDocsConfig.ts +58 -8
  109. package/stl-docs/proseMarkdown/proseMarkdownIntegration.ts +61 -0
  110. package/stl-docs/proseMarkdown/proseMarkdownMiddleware.ts +41 -0
  111. package/stl-docs/proseMarkdown/toMarkdown.ts +158 -0
  112. package/stl-docs/proseSearchIndexing.ts +606 -0
  113. package/stl-docs/tabsMiddleware.ts +13 -4
  114. package/styles/code.css +133 -136
  115. package/styles/links.css +11 -48
  116. package/styles/method-descriptions.css +36 -0
  117. package/styles/overrides.css +49 -57
  118. package/styles/page.css +100 -59
  119. package/styles/sdk_select.css +9 -7
  120. package/styles/search.css +57 -69
  121. package/styles/sidebar.css +26 -156
  122. package/styles/{variables.css → sl-variables.css} +3 -2
  123. package/styles/stldocs-variables.css +6 -0
  124. package/styles/toc.css +41 -34
  125. package/theme.css +11 -11
  126. package/tsconfig.json +2 -5
  127. package/virtual-module.d.ts +64 -8
  128. package/components/variables.css +0 -135
  129. package/plugin/cms/client.ts +0 -62
  130. package/plugin/cms/server.ts +0 -268
  131. package/plugin/globalJs/ai-dropdown.ts +0 -57
  132. package/stl-docs/components/APIReferenceAIDropdown.tsx +0 -58
  133. package/stl-docs/components/content-panel/ProseAIDropdown.tsx +0 -55
  134. package/stl-docs/components/mintlify-compat/Step.astro +0 -58
  135. package/stl-docs/components/mintlify-compat/Steps.astro +0 -17
  136. package/styles/fonts.css +0 -68
  137. /package/{plugin/assets → assets}/fonts/geist/OFL.txt +0 -0
  138. /package/{plugin/assets → assets}/fonts/geist/geist-italic-latin-ext.woff2 +0 -0
  139. /package/{plugin/assets → assets}/fonts/geist/geist-italic-latin.woff2 +0 -0
  140. /package/{plugin/assets → assets}/fonts/geist/geist-latin-ext.woff2 +0 -0
  141. /package/{plugin/assets → assets}/fonts/geist/geist-latin.woff2 +0 -0
  142. /package/{plugin/assets → assets}/fonts/geist/geist-mono-italic-latin-ext.woff2 +0 -0
  143. /package/{plugin/assets → assets}/fonts/geist/geist-mono-italic-latin.woff2 +0 -0
  144. /package/{plugin/assets → assets}/fonts/geist/geist-mono-latin-ext.woff2 +0 -0
  145. /package/{plugin/assets → assets}/fonts/geist/geist-mono-latin.woff2 +0 -0
package/stl-docs/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import starlight from '@astrojs/starlight';
2
2
  import react from '@astrojs/react';
3
- import { stainlessStarlight } from '../plugin';
3
+ import type { StarlightPlugin } from '@astrojs/starlight/types';
4
+ import { disableCalloutSyntaxStarlightPlugin } from './disableCalloutSyntax';
4
5
 
5
6
  import type { AstroIntegration } from 'astro';
6
7
 
@@ -16,11 +17,18 @@ import {
16
17
  type StarlightSidebarConfig,
17
18
  } from './loadStlDocsConfig';
18
19
  import { buildVirtualModuleString } from '../shared/virtualModule';
19
-
20
- import geistPath from '../plugin/assets/fonts/geist/geist-latin.woff2?url';
20
+ import type * as StlDocsVirtualModule from 'virtual:stl-docs-virtual-module';
21
+ import { resolveSrcFile } from '../resolveSrcFile';
22
+ import { stainlessDocsMarkdownRenderer } from './proseMarkdown/proseMarkdownIntegration';
23
+ import { setSharedLogger } from '../shared/getSharedLogger';
24
+ import { stainlessDocsAlgoliaProseIndexing, stainlessDocsVectorProseIndexing } from './proseSearchIndexing';
25
+ import { stainlessStarlight } from '../plugin';
26
+ import { getFontRoles, flattenFonts } from './fonts';
21
27
 
22
28
  export * from '../plugin';
23
29
 
30
+ const COMPONENTS_FOLDER = '/stl-docs/components';
31
+
24
32
  function stainlessDocsStarlightIntegration(config: NormalizedStainlessDocsConfig) {
25
33
  // We transform our tabs into a Starlight sidebar
26
34
  // This gives them all the built-in features of Starlight (eg. auto-generated entries by directory)
@@ -40,21 +48,44 @@ function stainlessDocsStarlightIntegration(config: NormalizedStainlessDocsConfig
40
48
  }
41
49
 
42
50
  type ComponentOverrides = StarlightConfigDefined['components'];
43
- const plugins = [...config.starlightCompat.plugins];
44
-
45
51
  const componentOverrides: ComponentOverrides = {
46
- Sidebar: '@stainless-api/docs/BaseSidebar',
47
- Header: '@stainless-api/docs/Header',
48
- ThemeSelect: '@stainless-api/docs/ThemeSelect',
49
- ContentPanel: '@stainless-api/docs/ContentPanel',
52
+ PageFrame: resolveSrcFile(COMPONENTS_FOLDER, './PageFrame.astro'),
53
+
54
+ Head: resolveSrcFile(COMPONENTS_FOLDER, './Head.astro'),
55
+ Header: resolveSrcFile(COMPONENTS_FOLDER, './Header.astro'),
56
+ ThemeProvider: resolveSrcFile(COMPONENTS_FOLDER, './ThemeProvider.astro'),
57
+ ThemeSelect: resolveSrcFile(COMPONENTS_FOLDER, './ThemeSelect.astro'),
58
+
59
+ Sidebar: resolveSrcFile(COMPONENTS_FOLDER, './sidebars/BaseSidebar.astro'),
60
+ ContentPanel: resolveSrcFile(COMPONENTS_FOLDER, './content-panel/ContentPanel.astro'),
61
+ TableOfContents: resolveSrcFile(COMPONENTS_FOLDER, './TableOfContents.astro'),
62
+
63
+ PageTitle: resolveSrcFile(COMPONENTS_FOLDER, './PageTitle.astro'),
64
+ Pagination: resolveSrcFile(COMPONENTS_FOLDER, './pagination/Pagination.astro'),
50
65
  };
51
66
 
67
+ const plugins: StarlightPlugin[] = [
68
+ // Disable starlight callout syntax in favor of our own component
69
+ disableCalloutSyntaxStarlightPlugin,
70
+ ];
71
+
52
72
  if (config.apiReference !== null) {
53
- componentOverrides.Sidebar = '@stainless-api/docs/SDKSelectSidebar';
54
- componentOverrides.Search = '@stainless-api/docs/Search';
55
- plugins.unshift(stainlessStarlight(config.apiReference));
73
+ plugins.push(
74
+ stainlessStarlight({
75
+ ...config.apiReference,
76
+ contextMenu: config.contextMenu,
77
+ experimentalPrerender:
78
+ config.apiReference.experimentalPrerender === undefined
79
+ ? config.starlightPassThrough.prerender
80
+ : config.apiReference.experimentalPrerender,
81
+ }),
82
+ );
83
+ componentOverrides.Sidebar = resolveSrcFile(COMPONENTS_FOLDER, './sidebars/SDKSelectSidebar.astro');
84
+ componentOverrides.Search = resolveSrcFile('/plugin/components/search/Search.astro');
56
85
  }
57
86
 
87
+ plugins.push(...config.starlightCompat.plugins, ...config.plugins.map((p) => p(config)));
88
+
58
89
  // TODO: re-add once we figure out what to do with the client router
59
90
  // if (config.enableClientRouter) {
60
91
  // // logger.info(`Client router is enabled`);
@@ -63,6 +94,8 @@ function stainlessDocsStarlightIntegration(config: NormalizedStainlessDocsConfig
63
94
  // // logger.info(`Client router is disabled`);
64
95
  // }
65
96
 
97
+ const userExpressiveCode = typeof config.expressiveCode === 'object' ? config.expressiveCode : {};
98
+
66
99
  return starlight({
67
100
  ...config.starlightPassThrough,
68
101
  sidebar,
@@ -86,69 +119,112 @@ function stainlessDocsStarlightIntegration(config: NormalizedStainlessDocsConfig
86
119
  setupNavLinksInitial();
87
120
  `,
88
121
  },
89
- // TODO: for users who are overriding the font stack in their own styles, how can we know that
90
- // and preload their font instead of ours?
91
- {
92
- tag: 'link',
93
- attrs: {
94
- rel: 'preload',
95
- as: 'font',
96
- type: 'font/woff2',
97
- crossorigin: 'anonymous',
98
- href: geistPath,
122
+ ],
123
+ routeMiddleware: [
124
+ ...config.starlightCompat.routeMiddleware,
125
+ resolveSrcFile('/stl-docs/tabsMiddleware.ts'),
126
+ ],
127
+ customCss: [resolveSrcFile('/theme.css'), ...config.customCss],
128
+
129
+ expressiveCode: {
130
+ ...userExpressiveCode,
131
+ themes: userExpressiveCode.themes ?? ['github-light', 'github-dark'],
132
+ styleOverrides: {
133
+ ...userExpressiveCode.styleOverrides,
134
+ textMarkers: {
135
+ insBackground: 'var(--stl-color-green-muted-background)',
136
+ insBorderColor: 'var(--stl-color-green-border)',
137
+ insDiffIndicatorColor: 'var(--stl-color-green-foreground-reduced)',
138
+
139
+ delBackground: 'var(--stl-color-red-muted-background)',
140
+ delBorderColor: 'var(--stl-color-red-border)',
141
+ delDiffIndicatorColor: 'var(--stl-color-red-foreground-reduced)',
142
+
143
+ markBackground: 'var(--stl-color-blue-muted-background)',
144
+ markBorderColor: 'var(--stl-color-blue-border)',
145
+ ...userExpressiveCode.styleOverrides?.textMarkers,
99
146
  },
100
147
  },
101
- ],
102
- routeMiddleware: [...config.starlightCompat.routeMiddleware, '@stainless-api/docs/tabsMiddleware'],
103
- customCss: ['@stainless-api/docs/theme', ...config.customCss],
148
+ },
104
149
  plugins,
105
150
  });
106
151
  }
107
152
 
108
- function stainlessDocsIntegration(config: NormalizedStainlessDocsConfig): AstroIntegration {
109
- const virtualId = `virtual:stl-docs-virtual-module`;
153
+ function stainlessDocsIntegration(
154
+ config: NormalizedStainlessDocsConfig,
155
+ apiReferenceBasePath: string | null,
156
+ ): AstroIntegration {
110
157
  // The '\0' prefix tells Vite “this is a virtual module” and prevents it from being resolved again.
111
- const resolvedId = `\0${virtualId}`;
158
+ const resolveVirtualModuleId = (id: string) => `\0${id}`;
112
159
  let redirects: NormalizedRedirectConfig | null = null;
113
160
 
114
161
  return {
115
- name: 'stl-docs-integration',
162
+ name: 'stl-docs-astro',
116
163
  hooks: {
117
164
  'astro:config:setup': ({ updateConfig, command, config: astroConfig }) => {
118
- // // we only handle redirects for builds
119
- // // in dev, Astro handles them for us
165
+ // we only handle redirects for builds
166
+ // in dev, Astro handles them for us
120
167
  if (command === 'build' && astroConfig.redirects) {
121
168
  redirects = normalizeRedirects(astroConfig.redirects);
122
169
  }
123
170
 
171
+ const virtualModules = new Map(
172
+ Object.entries({
173
+ 'virtual:stl-docs-virtual-module': buildVirtualModuleString({
174
+ TABS: config.tabs,
175
+ SPLIT_TABS_ENABLED: config.splitTabsEnabled,
176
+ HEADER_LINKS: config.header.links,
177
+ HEADER_LAYOUT: config.header.layout,
178
+ ENABLE_CLIENT_ROUTER: config.enableClientRouter,
179
+ API_REFERENCE_BASE_PATH: apiReferenceBasePath ?? '/api',
180
+ ENABLE_PROSE_MARKDOWN_RENDERING: config.enableProseMarkdownRendering,
181
+ ENABLE_CONTEXT_MENU: config.contextMenu, // TODO: do not duplicate this between both virtual modules
182
+ RENDER_PAGE_DESCRIPTIONS: config.renderPageDescriptions,
183
+ FONTS: getFontRoles(config.fonts),
184
+ LINK_GROUP_TITLES_TO_OVERVIEW_PAGES: config.linkGroupTitlesToOverviewPages,
185
+ } satisfies typeof StlDocsVirtualModule),
186
+
187
+ 'virtual:stl-docs/components/AiChat.tsx': `
188
+ ${
189
+ config.aiChat
190
+ ? `export { default } from ${JSON.stringify(config.aiChat.chatComponentPath)};`
191
+ : // export null when no AI chat component is provided
192
+ `export default null;`
193
+ }
194
+ export const STAINLESS_PROJECT = ${config.apiReference ? JSON.stringify(config.apiReference.stainlessProject) : 'undefined'};
195
+ `,
196
+ }),
197
+ );
198
+
124
199
  updateConfig({
200
+ experimental: {
201
+ fonts: [...flattenFonts(config.fonts), ...(astroConfig.experimental?.fonts ?? [])],
202
+ },
125
203
  vite: {
126
- ssr: {
127
- noExternal: ['@stainless-api/ui-primitives'],
128
- },
129
- optimizeDeps: { include: ['@stainless-api/ui-primitives'] },
130
204
  plugins: [
131
205
  {
132
206
  name: 'stl-docs-vite',
133
207
  resolveId(id) {
134
- if (id === virtualId) {
135
- return resolvedId;
136
- }
208
+ if (virtualModules.has(id)) return resolveVirtualModuleId(id);
137
209
  },
138
210
  load(id) {
139
- if (id === resolvedId) {
140
- return buildVirtualModuleString({
141
- TABS: config.tabs,
142
- SPLIT_TABS_ENABLED: config.splitTabsEnabled,
143
- HEADER_LINKS: config.header.links,
144
- HEADER_LAYOUT: config.header.layout,
145
- ENABLE_CLIENT_ROUTER: config.enableClientRouter,
146
- INCLUDE_AI_DROPDOWN_OPTIONS: config.includeAIDropdownOptions,
147
- });
148
- }
211
+ const bare = id.replace(/^\0/, '');
212
+ if (virtualModules.has(bare)) return virtualModules.get(bare);
149
213
  },
150
214
  },
151
215
  ],
216
+ optimizeDeps: {
217
+ include: config.aiChat
218
+ ? [
219
+ '@stainless-api/docs-ai-chat > @stainless-api/ai-chat > lucide-react',
220
+ '@stainless-api/docs-ai-chat > @stainless-api/ai-chat > motion',
221
+ '@stainless-api/docs-ai-chat > @stainless-api/ai-chat > motion > framer-motion',
222
+ '@stainless-api/docs-ai-chat > @stainless-api/ai-chat > react-markdown',
223
+ '@stainless-api/docs-ai-chat > @stainless-api/ai-chat > react-syntax-highlighter',
224
+ '@stainless-api/docs-ai-chat > @stainless-api/ai-chat > remark-gfm',
225
+ ]
226
+ : [],
227
+ },
152
228
  },
153
229
  build: {
154
230
  ...astroConfig.build,
@@ -159,7 +235,7 @@ function stainlessDocsIntegration(config: NormalizedStainlessDocsConfig): AstroI
159
235
  'astro:build:done': ({ dir }) => {
160
236
  if (redirects !== null) {
161
237
  const stainlessDir = join(dir.pathname, '_stainless');
162
- mkdirSync(stainlessDir);
238
+ mkdirSync(stainlessDir, { recursive: true });
163
239
  const outputPath = join(stainlessDir, 'redirects.json');
164
240
  writeFileSync(outputPath, JSON.stringify(redirects, null, 2), {
165
241
  encoding: 'utf-8',
@@ -170,7 +246,18 @@ function stainlessDocsIntegration(config: NormalizedStainlessDocsConfig): AstroI
170
246
  };
171
247
  }
172
248
 
173
- export function stainlessDocs(config: StainlessDocsUserConfig) {
249
+ function sharedLoggerIntegration(): AstroIntegration {
250
+ return {
251
+ name: 'stainless',
252
+ hooks: {
253
+ 'astro:config:setup': ({ logger }) => {
254
+ setSharedLogger(logger);
255
+ },
256
+ },
257
+ };
258
+ }
259
+
260
+ export function stainlessDocs(config: StainlessDocsUserConfig): StarlightPlugin[] {
174
261
  const normalizedConfigResult = parseStlDocsConfig(config);
175
262
  if (normalizedConfigResult.result === 'error') {
176
263
  // TODO: would be good to use the astro logger somehow
@@ -179,9 +266,25 @@ export function stainlessDocs(config: StainlessDocsUserConfig) {
179
266
  }
180
267
  const normalizedConfig = normalizedConfigResult.config;
181
268
 
269
+ // TODO: need to refactor this, but this allows us to get the base path for the API reference _if_ it exists
270
+ // if it doesn't exist, the value of basePath is null.
271
+ // the stl-starlight virtual module has base path, but it's not available when there's no API reference
272
+ const hasApiReference = normalizedConfig.apiReference !== null;
273
+ let apiReferenceBasePath: string | null = null;
274
+ if (hasApiReference) {
275
+ apiReferenceBasePath = normalizedConfig.apiReference?.basePath ?? '/api';
276
+ }
277
+
182
278
  return [
279
+ sharedLoggerIntegration(), // this **must** be first so it can set the shared logger used by our other integrations
183
280
  react(),
184
281
  stainlessDocsStarlightIntegration(normalizedConfig),
185
- stainlessDocsIntegration(normalizedConfig),
282
+ stainlessDocsIntegration(normalizedConfig, apiReferenceBasePath),
283
+ stainlessDocsMarkdownRenderer({
284
+ enabled: normalizedConfig.enableProseMarkdownRendering,
285
+ apiReferenceBasePath,
286
+ }),
287
+ stainlessDocsAlgoliaProseIndexing({ apiReferenceBasePath }),
288
+ stainlessDocsVectorProseIndexing(normalizedConfig, apiReferenceBasePath),
186
289
  ];
187
290
  }
@@ -1,8 +1,9 @@
1
1
  import type { StainlessStarlightUserConfig } from '../plugin/loadPluginConfig';
2
- import type { StarlightUserConfig } from '@astrojs/starlight/types';
2
+ import type { StarlightPlugin, 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
+ import { normalizeFonts, type StlDocsFontConfig } from './fonts';
6
7
 
7
8
  type StarlightConfig = Parameters<typeof starlight>[0];
8
9
 
@@ -32,20 +33,22 @@ type PassThroughStarlightConfigOptions = Pick<
32
33
  | 'lastUpdated'
33
34
  | 'pagination'
34
35
  | 'sidebar'
36
+ | 'expressiveCode'
35
37
  >;
36
38
 
37
39
  type ExperimentalStarlightCompatibilityConfig = Pick<
38
40
  StarlightConfigDefined,
39
- 'components' | 'routeMiddleware' | 'plugins'
41
+ 'components' | 'routeMiddleware' | 'plugins' | 'prerender'
40
42
  >;
41
43
 
42
- type Tabs = {
44
+ export type Tabs = {
43
45
  label: string;
44
46
  link: string;
45
47
  sidebar?: SidebarEntry[];
46
48
  /**
47
49
  * Whether to hide the tab in the tab bar.
48
- * Defaults to `false`.
50
+ *
51
+ * @default false
49
52
  */
50
53
  hidden?: boolean;
51
54
  }[];
@@ -64,10 +67,42 @@ export type StainlessDocsUserConfig = {
64
67
  layout?: HeaderLayout;
65
68
  links?: HeaderLink[];
66
69
  };
70
+ fonts?: StlDocsFontConfig;
67
71
  experimental?: {
68
72
  starlightCompat?: ExperimentalStarlightCompatibilityConfig;
69
73
  enableClientRouter?: boolean;
74
+ /**
75
+ * Disable markdown rendering for prose content. Only disable this if it is causing issues.
76
+ *
77
+ * @default false
78
+ */
79
+ disableProseMarkdownRendering?: boolean;
80
+ aiChat?: { chatComponentPath: string };
81
+ /**
82
+ * Whether to link group titles to overview pages. Note: overview pages must already be present in the sidebar for this to work.
83
+ *
84
+ * @default false
85
+ */
86
+ linkGroupTitlesToOverviewPages?: boolean;
70
87
  };
88
+ /**
89
+ * Whether to show the context menu with options like "Copy as Markdown" and "Open in ChatGPT".
90
+ *
91
+ * @default true
92
+ */
93
+ contextMenu?: boolean;
94
+
95
+ /**
96
+ * Whether to render page descriptions in prose page headers
97
+ *
98
+ * @default true
99
+ */
100
+ renderPageDescriptions?: boolean;
101
+ /**
102
+ * Stainless Docs plugins.
103
+ * Each plugin is a function that receives the normalized config and returns a Starlight plugin.
104
+ */
105
+ plugins?: ((config: Exclude<NormalizedStainlessDocsConfig, 'plugins'>) => StarlightPlugin)[];
71
106
  } & PassThroughStarlightConfigOptions;
72
107
 
73
108
  type HeaderLayout = 'default' | 'stacked';
@@ -112,6 +147,7 @@ function normalizeConfig(userConfig: StainlessDocsUserConfig) {
112
147
  layout: userConfig.header?.layout ?? 'default',
113
148
  links: userConfig.header?.links ?? [],
114
149
  },
150
+ fonts: normalizeFonts(userConfig.fonts),
115
151
  starlightPassThrough: {
116
152
  tableOfContents: userConfig.tableOfContents,
117
153
  titleDelimiter: userConfig.titleDelimiter,
@@ -119,6 +155,13 @@ function normalizeConfig(userConfig: StainlessDocsUserConfig) {
119
155
  description: userConfig.description,
120
156
  tagline: userConfig.tagline,
121
157
  logo: userConfig.logo,
158
+ favicon: userConfig.favicon,
159
+ disable404Route: userConfig.disable404Route,
160
+ editLink: userConfig.editLink,
161
+ locales: userConfig.locales,
162
+ lastUpdated: userConfig.lastUpdated,
163
+ pagination: userConfig.pagination,
164
+ prerender: userConfig.experimental?.starlightCompat?.prerender ?? true,
122
165
  },
123
166
  starlightCompat: {
124
167
  components: userConfig.experimental?.starlightCompat?.components ?? {},
@@ -128,7 +171,14 @@ function normalizeConfig(userConfig: StainlessDocsUserConfig) {
128
171
  enableClientRouter: userConfig.experimental?.enableClientRouter ?? false,
129
172
  apiReference: userConfig.apiReference ?? null,
130
173
  sidebar: userConfig.sidebar,
131
- includeAIDropdownOptions: userConfig.apiReference?.includeAIDropdownOptions ?? false,
174
+ enableProseMarkdownRendering:
175
+ userConfig.experimental?.disableProseMarkdownRendering === true ? false : true,
176
+ contextMenu: userConfig.contextMenu ?? true,
177
+ expressiveCode: userConfig.expressiveCode,
178
+ renderPageDescriptions: userConfig.renderPageDescriptions ?? true,
179
+ plugins: userConfig.plugins ?? [],
180
+ aiChat: userConfig.experimental?.aiChat,
181
+ linkGroupTitlesToOverviewPages: userConfig.experimental?.linkGroupTitlesToOverviewPages ?? false,
132
182
  };
133
183
 
134
184
  return configWithDefaults;
@@ -137,12 +187,12 @@ function normalizeConfig(userConfig: StainlessDocsUserConfig) {
137
187
  export type NormalizedStainlessDocsConfig = ReturnType<typeof normalizeConfig>;
138
188
 
139
189
  /*
140
- The goal of the code in this file is to take a user's config and normalize it.
190
+ The goal of the code in this file is to take a user's config and normalize it.
141
191
  Specifically: we want a single complete config format used throughout the internals of the plugin.
142
192
 
143
193
  We've tried to avoid any config values being optional/undefined. To accomplish this:
144
- - Any optional config values should have their defaults set here: eg. basePath defaults to /api
145
- - If a field is only used in certain contexts, we make each context a discriminated union (see SpecRetrieverConfig)
194
+ - Any optional config values should have their defaults set here: eg. basePath defaults to /api
195
+ - If a field is only used in certain contexts, we make each context a discriminated union (see SDKJSONInputs)
146
196
  - We prefer empty arrays over undefined/null
147
197
  */
148
198
  export function parseStlDocsConfig(userConfig: StainlessDocsUserConfig) {
@@ -0,0 +1,61 @@
1
+ import type { AstroIntegration } from 'astro';
2
+ import { readFile, writeFile } from 'fs/promises';
3
+ import { toMarkdown } from './toMarkdown';
4
+ import { resolveSrcFile } from '../../resolveSrcFile';
5
+ import { getSharedLogger } from '../../shared/getSharedLogger';
6
+ import { bold } from '../../shared/terminalUtils';
7
+ import { getProsePages } from '../../shared/getProsePages';
8
+
9
+ export function stainlessDocsMarkdownRenderer({
10
+ enabled,
11
+ apiReferenceBasePath,
12
+ }: {
13
+ enabled: boolean;
14
+ apiReferenceBasePath: string | null;
15
+ }): AstroIntegration {
16
+ return {
17
+ name: 'stl-docs-md',
18
+ hooks: {
19
+ 'astro:config:setup': ({ addMiddleware }) => {
20
+ if (enabled) {
21
+ addMiddleware({
22
+ entrypoint: resolveSrcFile('/stl-docs/proseMarkdown/proseMarkdownMiddleware.ts'),
23
+ order: 'post',
24
+ });
25
+ }
26
+ },
27
+ 'astro:build:done': async ({ logger: localLogger, dir }) => {
28
+ const logger = getSharedLogger({ fallback: localLogger });
29
+ if (!enabled) {
30
+ logger.info('Stainless Docs prose Markdown rendering is disabled, skipping...');
31
+ return;
32
+ }
33
+ const outputBasePath = dir.pathname;
34
+ const pagesToRender = await getProsePages({ apiReferenceBasePath, outputBasePath });
35
+
36
+ logger.info(bold(`Building ${pagesToRender.length} Markdown pages for prose content`));
37
+
38
+ let completedCount = 0;
39
+
40
+ for (const absHtmlPath of pagesToRender) {
41
+ const txt = await readFile(absHtmlPath, 'utf-8');
42
+ const md = await toMarkdown(txt);
43
+ if (md) {
44
+ const absMdPath = absHtmlPath.replace('.html', '.md');
45
+ await writeFile(absMdPath, md, 'utf-8');
46
+
47
+ completedCount++;
48
+
49
+ const relHtmlPath = absHtmlPath.replace(outputBasePath, '');
50
+ const relMdPath = absMdPath.replace(outputBasePath, '');
51
+
52
+ logger.info(`(${completedCount}/${pagesToRender.length}) ${relHtmlPath} -> ${relMdPath}`);
53
+ } else {
54
+ logger.error(`Failed to render ${absHtmlPath} as Markdown`);
55
+ process.exit(1);
56
+ }
57
+ }
58
+ },
59
+ },
60
+ };
61
+ }
@@ -0,0 +1,41 @@
1
+ import { defineMiddleware } from 'astro:middleware';
2
+ import { toMarkdown } from './toMarkdown';
3
+ import { API_REFERENCE_BASE_PATH } from 'virtual:stl-docs-virtual-module';
4
+ import path from 'path';
5
+
6
+ // this is only run in `astro dev` for rendering prose content as Markdown on the fly.
7
+ export const onRequest = defineMiddleware(async (context, next) => {
8
+ // eslint-disable-next-line turbo/no-undeclared-env-vars
9
+ if (!import.meta.env.DEV) {
10
+ return next();
11
+ }
12
+
13
+ const resolvedBasePath = path.posix.join(import.meta.env.BASE_URL ?? '', API_REFERENCE_BASE_PATH);
14
+ if (resolvedBasePath && context.url.pathname.startsWith(resolvedBasePath)) {
15
+ // handled by the API reference API route in stl-starlight plugin
16
+ return next();
17
+ }
18
+
19
+ if (!context.url.pathname.endsWith('/index.md')) {
20
+ return next();
21
+ }
22
+
23
+ const pathname = context.url.pathname.replace('index.md', '');
24
+
25
+ // We must trim the trailing slash to support astro configs with `trailingSlash: 'never'`
26
+ const cleanPathname = pathname !== '/' ? pathname.replace(/\/$/, '') : pathname;
27
+ const htmlUrl = new URL(cleanPathname, context.url);
28
+
29
+ const resp = await fetch(htmlUrl);
30
+ if (!resp.ok) {
31
+ return new Response('Failed to fetch HTML', { status: 400 });
32
+ }
33
+ const html = await resp.text();
34
+ const md = await toMarkdown(html);
35
+
36
+ if (!md) {
37
+ return new Response('Failed to render Markdown', { status: 400 });
38
+ }
39
+
40
+ return new Response(md, { status: 200 });
41
+ });