@stainless-api/docs 0.1.0-beta.5 → 0.1.0-beta.50

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 (115) hide show
  1. package/CHANGELOG.md +381 -0
  2. package/eslint-suppressions.json +47 -0
  3. package/locals.d.ts +14 -0
  4. package/package.json +42 -38
  5. package/plugin/buildAlgoliaIndex.ts +31 -6
  6. package/plugin/cms/server.ts +98 -56
  7. package/plugin/cms/sidebar-builder.ts +7 -26
  8. package/plugin/cms/worker.ts +3 -3
  9. package/plugin/components/MethodDescription.tsx +54 -0
  10. package/plugin/components/SDKSelect.astro +7 -87
  11. package/plugin/components/SnippetCode.tsx +11 -7
  12. package/plugin/components/search/SearchAlgolia.astro +5 -33
  13. package/plugin/components/search/SearchIsland.tsx +37 -23
  14. package/plugin/generateAPIReferenceLink.ts +2 -2
  15. package/plugin/globalJs/ai-dropdown-options.ts +235 -0
  16. package/plugin/globalJs/method-descriptions.ts +33 -0
  17. package/plugin/globalJs/navigation.ts +7 -27
  18. package/plugin/helpers/getPageLoadEvent.ts +1 -1
  19. package/plugin/index.ts +54 -34
  20. package/plugin/languages.ts +2 -2
  21. package/plugin/loadPluginConfig.ts +112 -32
  22. package/plugin/middlewareBuilder/stainlessMiddleware.d.ts +1 -1
  23. package/plugin/react/Routing.tsx +176 -80
  24. package/plugin/referencePlaceholderUtils.ts +1 -1
  25. package/plugin/replaceSidebarPlaceholderMiddleware.ts +5 -1
  26. package/plugin/routes/Docs.astro +60 -85
  27. package/plugin/routes/Overview.astro +10 -16
  28. package/plugin/routes/markdown.ts +7 -7
  29. package/plugin/vendor/preview.worker.docs.js +17973 -16561
  30. package/plugin/vendor/templates/go.md +1 -1
  31. package/plugin/vendor/templates/python.md +1 -1
  32. package/resolveSrcFile.ts +10 -0
  33. package/scripts/vendor_deps.ts +1 -1
  34. package/shared/getSharedLogger.ts +15 -0
  35. package/shared/terminalUtils.ts +3 -0
  36. package/src/content.config.ts +9 -0
  37. package/stl-docs/components/AIDropdown.tsx +63 -0
  38. package/stl-docs/components/{content-panel/ContentBreadcrumbs.tsx → ContentBreadcrumbs.tsx} +2 -2
  39. package/stl-docs/components/Head.astro +16 -0
  40. package/stl-docs/components/Header.astro +6 -8
  41. package/stl-docs/components/PageTitle.astro +82 -0
  42. package/stl-docs/components/TableOfContents.astro +34 -0
  43. package/stl-docs/components/ThemeSelect.astro +118 -141
  44. package/stl-docs/components/content-panel/ContentPanel.astro +16 -46
  45. package/stl-docs/components/headers/DefaultHeader.astro +1 -1
  46. package/stl-docs/components/headers/HeaderLinks.astro +1 -1
  47. package/stl-docs/components/headers/SplashMobileMenuToggle.astro +17 -1
  48. package/stl-docs/components/headers/StackedHeader.astro +29 -24
  49. package/stl-docs/components/icons/chat-gpt.tsx +17 -0
  50. package/stl-docs/components/icons/claude.tsx +10 -0
  51. package/stl-docs/components/icons/cursor.tsx +10 -0
  52. package/stl-docs/components/icons/gemini.tsx +19 -0
  53. package/stl-docs/components/icons/markdown.tsx +10 -0
  54. package/stl-docs/components/index.ts +1 -0
  55. package/stl-docs/components/mintlify-compat/Accordion.astro +7 -38
  56. package/stl-docs/components/mintlify-compat/AccordionGroup.astro +9 -23
  57. package/stl-docs/components/mintlify-compat/Columns.astro +40 -42
  58. package/stl-docs/components/mintlify-compat/Frame.astro +16 -18
  59. package/stl-docs/components/mintlify-compat/Step.astro +30 -32
  60. package/stl-docs/components/mintlify-compat/Steps.astro +8 -10
  61. package/stl-docs/components/mintlify-compat/callouts/Callout.astro +10 -3
  62. package/stl-docs/components/mintlify-compat/callouts/Check.astro +7 -3
  63. package/stl-docs/components/mintlify-compat/callouts/Danger.astro +7 -3
  64. package/stl-docs/components/mintlify-compat/callouts/Info.astro +7 -3
  65. package/stl-docs/components/mintlify-compat/callouts/Note.astro +7 -3
  66. package/stl-docs/components/mintlify-compat/callouts/Tip.astro +7 -3
  67. package/stl-docs/components/mintlify-compat/callouts/Warning.astro +7 -3
  68. package/stl-docs/components/mintlify-compat/card.css +33 -35
  69. package/stl-docs/components/nav-tabs/NavDropdown.astro +31 -75
  70. package/stl-docs/components/nav-tabs/NavTabs.astro +78 -80
  71. package/stl-docs/components/nav-tabs/SecondaryNavTabs.astro +15 -8
  72. package/stl-docs/components/nav-tabs/buildNavLinks.ts +4 -3
  73. package/stl-docs/components/pagination/HomeLink.astro +10 -0
  74. package/stl-docs/components/pagination/Pagination.astro +174 -0
  75. package/stl-docs/components/pagination/PaginationLinkEmphasized.astro +22 -0
  76. package/stl-docs/components/pagination/PaginationLinkQuiet.astro +13 -0
  77. package/stl-docs/components/pagination/util.ts +71 -0
  78. package/stl-docs/components/scripts.ts +1 -0
  79. package/stl-docs/components/{Sidebar.astro → sidebars/BaseSidebar.astro} +2 -3
  80. package/stl-docs/components/sidebars/SDKSelectSidebar.astro +8 -0
  81. package/stl-docs/disableCalloutSyntax.ts +36 -0
  82. package/stl-docs/index.ts +98 -26
  83. package/stl-docs/loadStlDocsConfig.ts +37 -5
  84. package/stl-docs/proseMarkdown/proseMarkdownIntegration.ts +64 -0
  85. package/stl-docs/proseMarkdown/proseMarkdownMiddleware.ts +34 -0
  86. package/stl-docs/proseMarkdown/toMarkdown.ts +158 -0
  87. package/stl-docs/tabsMiddleware.ts +12 -4
  88. package/styles/code.css +104 -141
  89. package/styles/fonts.css +32 -17
  90. package/styles/links.css +11 -48
  91. package/styles/method-descriptions.css +36 -0
  92. package/styles/overrides.css +49 -57
  93. package/styles/page.css +90 -59
  94. package/styles/sdk_select.css +9 -7
  95. package/styles/search.css +58 -69
  96. package/styles/sidebar.css +211 -131
  97. package/styles/{variables.css → sl-variables.css} +3 -2
  98. package/styles/stldocs-variables.css +6 -0
  99. package/styles/toc.css +41 -34
  100. package/theme.css +12 -2
  101. package/tsconfig.json +2 -5
  102. package/virtual-module.d.ts +8 -4
  103. package/components/variables.css +0 -139
  104. package/plugin/globalJs/ai-dropdown.ts +0 -57
  105. package/stl-docs/components/APIReferenceAIDropdown.tsx +0 -86
  106. package/stl-docs/components/content-panel/ProseAIDropdown.tsx +0 -64
  107. /package/{plugin/assets → assets}/fonts/geist/OFL.txt +0 -0
  108. /package/{plugin/assets → assets}/fonts/geist/geist-italic-latin-ext.woff2 +0 -0
  109. /package/{plugin/assets → assets}/fonts/geist/geist-italic-latin.woff2 +0 -0
  110. /package/{plugin/assets → assets}/fonts/geist/geist-latin-ext.woff2 +0 -0
  111. /package/{plugin/assets → assets}/fonts/geist/geist-latin.woff2 +0 -0
  112. /package/{plugin/assets → assets}/fonts/geist/geist-mono-italic-latin-ext.woff2 +0 -0
  113. /package/{plugin/assets → assets}/fonts/geist/geist-mono-italic-latin.woff2 +0 -0
  114. /package/{plugin/assets → assets}/fonts/geist/geist-mono-latin-ext.woff2 +0 -0
  115. /package/{plugin/assets → assets}/fonts/geist/geist-mono-latin.woff2 +0 -0
@@ -1,9 +1,12 @@
1
1
  import path from 'path';
2
+ import { homedir } from 'os';
3
+ import { existsSync, readFileSync } from 'fs';
2
4
 
3
5
  import type { CreateShikiHighlighterOptions } from '@astrojs/markdown-remark';
4
- import type { DocsLanguage } from '@stainless-api/docs-ui/src/routing';
5
- import type { PropertySettingsType } from '@stainless-api/docs-ui/src/contexts';
6
+ import type { DocsLanguage } from '@stainless-api/docs-ui/routing';
7
+ import type { PropertySettingsType } from '@stainless-api/docs-ui/contexts';
6
8
  import type { InputFilePaths } from '../plugin/cms/server';
9
+ import { bold } from '../shared/terminalUtils';
7
10
 
8
11
  export type AstroCommand = 'dev' | 'build' | 'preview' | 'sync';
9
12
 
@@ -18,7 +21,7 @@ export type VersionUserConfig = {
18
21
  type BreadcrumbUserConfig = {
19
22
  /**
20
23
  * Include the current page in the breadcrumb list.
21
- * Defaults to `false`.
24
+ * Default: `false`
22
25
  */
23
26
  includeCurrentPage?: boolean;
24
27
  };
@@ -26,7 +29,12 @@ type BreadcrumbUserConfig = {
26
29
  export type StainlessStarlightUserConfig = {
27
30
  /**
28
31
  * Optional api key for Stainless API.
29
- * If not provided, it will look for the STAINLESS_API_KEY environment variable.
32
+ * If not provided, we will handle Stainless auth via the `stl` CLI or look for the STAINLESS_API_KEY environment variable.
33
+ * Precedence:
34
+ * 1. Explicity `apiKey` option provided
35
+ * 2. `STAINLESS_API_KEY` environment variable
36
+ * 3. Login status from the `stl` CLI
37
+ * 4. Error (no auth found)
30
38
  */
31
39
  apiKey?: string;
32
40
 
@@ -42,8 +50,8 @@ export type StainlessStarlightUserConfig = {
42
50
 
43
51
  /**
44
52
  * Optional mount point for API reference docs.
45
- * Defaults to `/api`.
46
53
  * Example: `/my-api` → docs available at `/my-api/…`.
54
+ * @default `/api`
47
55
  */
48
56
  basePath?: string;
49
57
 
@@ -55,8 +63,8 @@ export type StainlessStarlightUserConfig = {
55
63
 
56
64
  /**
57
65
  * Optional language to treat as the default when the user hasn't selected one.
58
- * Defaults to `"http"` when none is provided.
59
- * Example: `"python"`
66
+ * Example: `'python'`
67
+ * @default 'http'
60
68
  */
61
69
  defaultLanguage?: DocsLanguage;
62
70
 
@@ -90,7 +98,7 @@ export type StainlessStarlightUserConfig = {
90
98
  contentPanel?: {
91
99
  /**
92
100
  * Optional layout for the content panel.
93
- * Defaults to `"double-pane"`.
101
+ * @default 'double-pane'
94
102
  */
95
103
  layout?: ContentLayout;
96
104
  };
@@ -100,23 +108,28 @@ export type StainlessStarlightUserConfig = {
100
108
  */
101
109
  propertySettings?: PropertySettingsType;
102
110
 
103
- /**
104
- * Options to control the documentation site's search functionality
105
- */
106
- search?: {
107
- /**
108
- * When set to `true`, the enableAISearch` setting turns on support for
109
- * LLM-based conversations with the API documentation
110
- */
111
- enableAISearch?: boolean;
112
- };
113
-
114
111
  /**
115
112
  * Enable experimental collapsible code snippets. Snippets will be collapsed by default for
116
113
  * single-pane and mobile layouts.
117
- * Defaults to `false`.
114
+ *
115
+ * @default false
118
116
  */
119
117
  experimentalCollapsibleSnippets?: boolean;
118
+
119
+ /**
120
+ * Enable experimental collapsible method descriptions. Method descriptions will be
121
+ * collapsed if their content exceeds a certain length.
122
+ *
123
+ * @default false
124
+ */
125
+ experimentalCollapsibleMethodDescriptions?: boolean;
126
+
127
+ /**
128
+ * Whether to show the context menu with options like "Copy as Markdown" and "Open in ChatGPT".
129
+ *
130
+ * @default true
131
+ */
132
+ contextMenu?: boolean;
120
133
  };
121
134
 
122
135
  export type ExternalSpecServerUserConfig = Omit<StainlessStarlightUserConfig, 'stainlessProject'> & {
@@ -133,15 +146,29 @@ function getLocalFilePaths(command: AstroCommand): InputFilePaths | null {
133
146
  if (command !== 'dev') {
134
147
  return null;
135
148
  }
136
- if (!process.env.OPENAPI_PATH || !process.env.STAINLESS_SPEC_PATH) {
149
+
150
+ // eslint-disable-next-line turbo/no-undeclared-env-vars
151
+ const oasPath = process.env.OPENAPI_PATH;
152
+ // eslint-disable-next-line turbo/no-undeclared-env-vars
153
+ const configPath = process.env.STAINLESS_CONFIG_PATH;
154
+
155
+ if (!oasPath || !configPath) {
137
156
  return null;
138
157
  }
158
+
139
159
  return {
140
- oasPath: resolvePath(process.env.OPENAPI_PATH),
141
- configPath: resolvePath(process.env.STAINLESS_SPEC_PATH),
160
+ oasPath: resolvePath(oasPath),
161
+ configPath: resolvePath(configPath),
142
162
  };
143
163
  }
144
164
 
165
+ export type ApiKeySource = 'explicit-config' | 'environment-variable' | 'cli';
166
+
167
+ export type LoadedApiKey = {
168
+ value: string;
169
+ source: ApiKeySource;
170
+ };
171
+
145
172
  export type SpecRetrieverConfig =
146
173
  | {
147
174
  kind: 'external_spec_server';
@@ -152,16 +179,63 @@ export type SpecRetrieverConfig =
152
179
  kind: 'local_spec_server_with_files';
153
180
  stainlessProject: string;
154
181
  devPaths: InputFilePaths;
155
- apiKey: string | null;
182
+ apiKey: LoadedApiKey | null;
156
183
  version: VersionUserConfig;
157
184
  }
158
185
  | {
159
186
  kind: 'local_spec_server_with_remote_files';
160
187
  stainlessProject: string;
161
- apiKey: string;
188
+ apiKey: LoadedApiKey;
162
189
  version: VersionUserConfig;
163
190
  };
164
191
 
192
+ function parseAuthJson(authJsonStr: string) {
193
+ let json: unknown;
194
+ try {
195
+ json = JSON.parse(authJsonStr);
196
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
197
+ } catch (_error) {
198
+ return null;
199
+ }
200
+
201
+ if (typeof json !== 'object' || json === null) {
202
+ return null;
203
+ }
204
+ if (!('access_token' in json)) {
205
+ return null;
206
+ }
207
+ const accessToken = json['access_token'];
208
+ if (typeof accessToken !== 'string') {
209
+ return null;
210
+ }
211
+ return accessToken;
212
+ }
213
+
214
+ function loadApiKey(configValue: string | undefined): LoadedApiKey | null {
215
+ if (typeof configValue === 'string') {
216
+ return { value: configValue, source: 'explicit-config' };
217
+ }
218
+ if (process.env.STAINLESS_API_KEY) {
219
+ return { value: process.env.STAINLESS_API_KEY, source: 'environment-variable' };
220
+ }
221
+
222
+ const homeDirPath = homedir();
223
+
224
+ const authJsonPath = path.join(homeDirPath, '.config', 'stainless', 'auth.json');
225
+
226
+ if (!existsSync(authJsonPath)) {
227
+ return null;
228
+ }
229
+
230
+ const authJsonStr = readFileSync(authJsonPath, 'utf-8');
231
+ const accessToken = parseAuthJson(authJsonStr);
232
+ if (!accessToken) {
233
+ return null;
234
+ }
235
+
236
+ return { value: accessToken, source: 'cli' };
237
+ }
238
+
165
239
  function normalizeConfig(partial: SomeStainlessStarlightUserConfig, command: AstroCommand) {
166
240
  const configWithDefaults = {
167
241
  basePath: partial.basePath ?? '/api',
@@ -181,15 +255,14 @@ function normalizeConfig(partial: SomeStainlessStarlightUserConfig, command: Ast
181
255
  layout: partial.contentPanel?.layout ?? 'double-pane',
182
256
  },
183
257
  experimentalCollapsibleSnippets: partial.experimentalCollapsibleSnippets ?? false,
258
+ experimentalCollapsibleMethodDescriptions: partial.experimentalCollapsibleMethodDescriptions ?? false,
184
259
  propertySettings: {
185
260
  types: partial.propertySettings?.types ?? 'rich',
186
261
  collapseDescription: partial.propertySettings?.collapseDescription ?? true,
187
262
  expandDepth: partial.propertySettings?.expandDepth ?? 0,
188
263
  includeModelProperties: partial.propertySettings?.includeModelProperties ?? true,
189
264
  },
190
- search: {
191
- enableAISearch: partial.search?.enableAISearch ?? false,
192
- },
265
+ contextMenu: partial.contextMenu ?? true,
193
266
  };
194
267
 
195
268
  function getSpecRetrieverConfig(): SpecRetrieverConfig {
@@ -205,7 +278,7 @@ function normalizeConfig(partial: SomeStainlessStarlightUserConfig, command: Ast
205
278
  throw new Error('You must provide a stainlessProject when using Stainless Starlight');
206
279
  }
207
280
 
208
- const apiKey = partial.apiKey ?? process.env.STAINLESS_API_KEY ?? null;
281
+ const apiKey = loadApiKey(partial.apiKey);
209
282
 
210
283
  const version = {
211
284
  stainlessProject: partial.stainlessProject,
@@ -226,7 +299,14 @@ function normalizeConfig(partial: SomeStainlessStarlightUserConfig, command: Ast
226
299
 
227
300
  if (!apiKey) {
228
301
  throw new Error(
229
- 'Please provide a Stainless API key via the STAINLESS_API_KEY environment variable or the apiKey option in the Stainless Starlight config.',
302
+ [
303
+ bold(
304
+ 'No Stainless credentials found. Please choose one of the following options to authenticate with Stainless:',
305
+ ),
306
+ '- Run `stl auth login` to authenticate via the Stainless CLI',
307
+ '- Provide a Stainless API key via the `STAINLESS_API_KEY` environment variable (eg. in a .env file)',
308
+ '- Set the `apiKey` option in the Stainless Docs config',
309
+ ].join('\n'),
230
310
  );
231
311
  }
232
312
 
@@ -249,11 +329,11 @@ function normalizeConfig(partial: SomeStainlessStarlightUserConfig, command: Ast
249
329
  export type NormalizedStainlessStarlightConfig = ReturnType<typeof normalizeConfig>;
250
330
 
251
331
  /*
252
- The goal of the code in this file is to take a user's config and normalize it.
332
+ The goal of the code in this file is to take a user's config and normalize it.
253
333
  Specifically: we want a single complete config format used throughout the internals of the plugin.
254
334
 
255
335
  We've tried to avoid any config values being optional/undefined. To accomplish this:
256
- - Any optional config values should have their defaults set here: eg. basePath defaults to /api
336
+ - Any optional config values should have their defaults set here: eg. basePath defaults to /api
257
337
  - If a field is only used in certain contexts, we make each context a discriminated union (see SpecRetrieverConfig)
258
338
  - We prefer empty arrays over undefined/null
259
339
  */
@@ -1,4 +1,4 @@
1
- import type { TransformRequestSnippetFn } from '@stainless-api/docs-ui/src/components/sdk';
1
+ import type { TransformRequestSnippetFn } from '@stainless-api/docs-ui/components/sdk';
2
2
 
3
3
  export type StlStarlightMiddleware = {
4
4
  transformRequestSnippet?: TransformRequestSnippetFn;
@@ -1,22 +1,25 @@
1
1
  import * as React from 'react';
2
- import { marked } from 'marked';
3
- import hljs from 'highlight.js';
4
- import { createMarkdownProcessor, type MarkdownProcessor } from '@astrojs/markdown-remark';
2
+ import { marked, Tokens } from 'marked';
3
+
4
+ import {
5
+ createMarkdownProcessor,
6
+ CreateShikiHighlighterOptions,
7
+ type MarkdownProcessor,
8
+ } from '@astrojs/markdown-remark';
5
9
  import remarkGfmAlerts from 'remark-github-alerts';
6
10
 
7
11
  import type { MarkdownHeading } from 'astro';
8
12
  import type { StarlightRouteData } from '@astrojs/starlight/route-data';
9
- import type * as SDKJSON from '~/lib/json-spec-v2/types';
10
- import type { DocsLanguage } from '@stainless-api/docs-ui/src/routing';
13
+ import type * as SDKJSON from '@stainless/sdk-json';
14
+ import { LanguageNames, SupportedLanguageSyntaxes, type DocsLanguage } from '@stainless-api/docs-ui/routing';
11
15
 
12
16
  import {
13
17
  generateRouteList,
14
18
  generateRoute,
15
19
  parseStainlessPath,
16
20
  walkTree,
17
- SupportedLanguageSyntaxes,
18
21
  getLanguageSnippet,
19
- } from '@stainless-api/docs-ui/src/routing';
22
+ } from '@stainless-api/docs-ui/routing';
20
23
 
21
24
  import {
22
25
  DocsProvider,
@@ -24,23 +27,22 @@ import {
24
27
  NavigationProvider,
25
28
  useSpec,
26
29
  type ContentPanelLayout,
27
- } from '@stainless-api/docs-ui/src/contexts';
30
+ } from '@stainless-api/docs-ui/contexts';
28
31
 
29
- import { flatResources, getResourceFromSpec } from '@stainless-api/docs-ui/src/utils';
32
+ import { flatResources, getResourceFromSpec } from '@stainless-api/docs-ui/utils';
30
33
 
31
34
  import {
32
35
  SDKMethod,
33
36
  SDKResource,
34
37
  type SDKRequestTitleProps,
35
38
  SDKBreadcrumbs,
36
- Dropdown,
37
- DropdownTrigger,
38
- DropdownMenu,
39
- DropdownItem,
40
39
  SDKIcon,
41
40
  SDKOverview,
42
41
  SDKLanguageBlock,
43
- } from '@stainless-api/docs-ui/src/components';
42
+ } from '@stainless-api/docs-ui/components';
43
+
44
+ import { Dropdown } from '@stainless-api/docs/components';
45
+
44
46
  import {
45
47
  BASE_PATH,
46
48
  EXCLUDE_LANGUAGES,
@@ -48,14 +50,17 @@ import {
48
50
  HIGHLIGHT_THEMES,
49
51
  BREADCRUMB_CONFIG,
50
52
  PROPERTY_SETTINGS,
53
+ ENABLE_CONTEXT_MENU,
54
+ EXPERIMENTAL_COLLAPSIBLE_METHOD_DESCRIPTIONS,
51
55
  } from 'virtual:stl-starlight-virtual-module';
52
- import style from '@stainless-api/docs-ui/src/style';
53
- import { createHighlighter, type BundledLanguage, type BundledTheme, type HighlighterGeneric } from 'shiki';
56
+ import style from '@stainless-api/docs-ui/style';
57
+ import { BundledTheme, createHighlighter, HighlighterGeneric, type BundledLanguage } from 'shiki';
54
58
  import { SnippetCode, SnippetContainer, SnippetRequestContainer } from '../components/SnippetCode';
55
- import clsx from 'clsx';
56
59
  import type { StlStarlightMiddleware } from '../middlewareBuilder/stainlessMiddleware';
57
- import { ComponentProvider } from '@stainless-api/docs-ui/src/contexts/component';
58
- import { APIReferenceAIDropdown } from '../../stl-docs/components/APIReferenceAIDropdown';
60
+ import { ComponentProvider } from '@stainless-api/docs-ui/contexts/component';
61
+ import { AIDropdown } from '../../stl-docs/components/AIDropdown';
62
+ import { ChevronsUpDownIcon } from 'lucide-react';
63
+ import { MethodDescription } from '../components/MethodDescription';
59
64
 
60
65
  export function generateDocsRoutes(spec: SDKJSON.Spec) {
61
66
  const paths = generateRouteList({
@@ -120,7 +125,6 @@ export function buildSidebar(
120
125
 
121
126
  const meths: SidebarEntry[] = Object.values(resource.methods ?? [])
122
127
  .filter((method) => spec.decls?.[language]?.[method.stainlessPath])
123
- .toSorted((first, second) => first.name.localeCompare(second.name))
124
128
  .map((method) => ({
125
129
  type: 'link',
126
130
  isCurrent: current === method.stainlessPath,
@@ -152,19 +156,58 @@ export function buildPageNavigation(resource: SDKJSON.Resource, depth: number =
152
156
  return [...output, ...subs];
153
157
  }
154
158
 
155
- function renderMarkdown(content: string) {
156
- return marked.parse(content, { gfm: true }) as string;
159
+ async function renderMarkdown(content: string) {
160
+ const highlighter = await astroHighlight();
161
+
162
+ const renderer = {
163
+ code({ text, lang }: Tokens.Code) {
164
+ return shikiHighlight({
165
+ highlighter,
166
+ content: text,
167
+ language: lang,
168
+ });
169
+ },
170
+ };
171
+
172
+ marked.use({ renderer });
173
+ return marked.parse(content, {
174
+ gfm: true,
175
+ }) as string;
157
176
  }
158
177
 
159
- async function highlight(content: string, language?: string) {
160
- if (language === 'json') return hljs.highlight(content, { language }).value;
161
- const highlighter = await astroHighlight();
178
+ function shikiHighlight({
179
+ highlighter,
180
+ content,
181
+ language,
182
+ themes,
183
+ }: {
184
+ highlighter: HighlighterGeneric<BundledLanguage, BundledTheme>;
185
+ content: string;
186
+ language?: string;
187
+ themes?: CreateShikiHighlighterOptions['themes'] | Record<string, 'stainless-docs-json'>;
188
+ }) {
189
+ let _themes = themes;
190
+ if (!themes && language === 'json') {
191
+ _themes = {
192
+ light: 'stainless-docs-json',
193
+ dark: 'stainless-docs-json',
194
+ };
195
+ }
196
+
197
+ if (!_themes) {
198
+ _themes = HIGHLIGHT_THEMES;
199
+ }
162
200
  return highlighter.codeToHtml(content, {
163
201
  lang: language ?? 'javascript',
164
- themes: HIGHLIGHT_THEMES || {},
202
+ themes: _themes || {},
165
203
  });
166
204
  }
167
205
 
206
+ async function highlight(content: string, language?: string) {
207
+ const highlighter = await astroHighlight();
208
+ return shikiHighlight({ highlighter, content, language });
209
+ }
210
+
168
211
  export function SDKSelectReactComponent({
169
212
  selected,
170
213
  languages,
@@ -178,30 +221,36 @@ export function SDKSelectReactComponent({
178
221
  }) {
179
222
  return (
180
223
  <Dropdown id={id} data-current-value={selected} className={className}>
181
- <DropdownTrigger
182
- className="dropdown-toggle stldocs-button-tertiary"
183
- type="button"
184
- id="stldocs-snippet-title-button"
185
- aria-expanded="false"
186
- withChevron
187
- >
188
- <SDKIcon language={getLanguageSnippet(selected)} size={16} />
189
- <span className={clsx('stl-snippet-dropdown-button-text', selected)}>{selected}</span>
190
- </DropdownTrigger>
191
- <DropdownMenu
224
+ <Dropdown.Trigger>
225
+ <Dropdown.TriggerSelectedItem>
226
+ <Dropdown.Icon>
227
+ <SDKIcon language={getLanguageSnippet(selected)} />
228
+ </Dropdown.Icon>
229
+ <span className="stl-snippet-dropdown-button-text">{LanguageNames[selected]}</span>
230
+ </Dropdown.TriggerSelectedItem>
231
+ <Dropdown.TriggerIcon>
232
+ <ChevronsUpDownIcon size={16} />
233
+ </Dropdown.TriggerIcon>
234
+ </Dropdown.Trigger>
235
+ <Dropdown.Menu
192
236
  className="dropdown-menu stl-sdk-select-dropdown-menu"
193
- position="below"
194
- aria-labelledby="stldocs-snippet-title-button"
237
+ aria-labelledby="stl-docs-snippet-title-button"
195
238
  >
196
239
  {languages.map((item) => (
197
- <DropdownItem key={item} value={item} selected={item === selected}>
198
- <div>
240
+ <Dropdown.MenuItem key={item} value={item} isSelected={item === selected}>
241
+ <Dropdown.Icon>
199
242
  <SDKIcon language={getLanguageSnippet(item)} size={16} />
200
- <span className={clsx('stl-snippet-dropdown-button-text', item)}>{item}</span>
201
- </div>
202
- </DropdownItem>
243
+ </Dropdown.Icon>
244
+ <Dropdown.MenuItemText>{LanguageNames[item]}</Dropdown.MenuItemText>
245
+ <Dropdown.MenuItemTemplate>
246
+ <Dropdown.Icon>
247
+ <SDKIcon language={getLanguageSnippet(item)} size={16} />
248
+ </Dropdown.Icon>
249
+ <span className="stl-snippet-dropdown-button-text">{LanguageNames[item]}</span>
250
+ </Dropdown.MenuItemTemplate>
251
+ </Dropdown.MenuItem>
203
252
  ))}
204
- </DropdownMenu>
253
+ </Dropdown.Menu>
205
254
  </Dropdown>
206
255
  );
207
256
  }
@@ -302,7 +351,10 @@ export function RenderSpec({
302
351
  const parsed = parseStainlessPath(path);
303
352
  const resource = getResourceFromSpec(path, spec);
304
353
 
305
- if (!resource || !parsed) return null;
354
+ if (!resource || !parsed) {
355
+ console.warn(`Could not find resource or parsed path for '${path}'`);
356
+ return null;
357
+ }
306
358
 
307
359
  return (
308
360
  <DocsProvider
@@ -319,11 +371,12 @@ export function RenderSpec({
319
371
  SnippetCode,
320
372
  SnippetContainer,
321
373
  SnippetRequestContainer,
374
+ ...(EXPERIMENTAL_COLLAPSIBLE_METHOD_DESCRIPTIONS ? { MethodDescription } : {}),
322
375
  }}
323
376
  >
324
377
  <NavigationProvider basePath={BASE_PATH} selectedPath={path}>
325
378
  <MarkdownProvider render={renderMarkdown} highlight={highlight}>
326
- {kind === 'http_method' ? (
379
+ {
327
380
  <div className="stldocs-root stl-ui-not-prose">
328
381
  <div className="stl-page-nav-container">
329
382
  <SDKBreadcrumbs
@@ -332,27 +385,18 @@ export function RenderSpec({
332
385
  basePath={BASE_PATH}
333
386
  config={BREADCRUMB_CONFIG}
334
387
  />
335
- <APIReferenceAIDropdown />
388
+ {ENABLE_CONTEXT_MENU && <AIDropdown />}
336
389
  </div>
337
- <SDKMethod
338
- method={resource.methods[parsed.method]}
339
- transformRequestSnippet={transformRequestSnippet}
340
- />
341
- </div>
342
- ) : (
343
- <div className="stldocs-root stl-ui-not-prose">
344
- <div className="stl-page-nav-container">
345
- <SDKBreadcrumbs
346
- spec={spec as SDKJSON.Spec}
347
- currentPath={currentPath}
348
- basePath={BASE_PATH}
349
- config={BREADCRUMB_CONFIG}
390
+ {kind === 'http_method' ? (
391
+ <SDKMethod
392
+ method={resource.methods[parsed.method!]!}
393
+ transformRequestSnippet={transformRequestSnippet}
350
394
  />
351
- <APIReferenceAIDropdown />
352
- </div>
353
- <SDKOverview resource={resource} />
395
+ ) : (
396
+ <SDKOverview resource={resource} />
397
+ )}
354
398
  </div>
355
- )}
399
+ }
356
400
  </MarkdownProvider>
357
401
  </NavigationProvider>
358
402
  </ComponentProvider>
@@ -366,10 +410,14 @@ export function RenderMethod({ path }: { path: string }) {
366
410
 
367
411
  const parsed = parseStainlessPath(path);
368
412
  const resource = getResourceFromSpec(path, spec);
369
- if (!resource || !parsed) return null;
370
413
 
371
- const meth = resource.methods[parsed.method];
372
- return <SDKMethod method={meth} />;
414
+ if (!resource || !parsed) {
415
+ console.warn(`Could not find resource or parsed path for '${path}'`);
416
+ return null;
417
+ }
418
+
419
+ const method = resource.methods[parsed.method!]!;
420
+ return <SDKMethod method={method} />;
373
421
  }
374
422
 
375
423
  export async function getReadmeContent(spec: SDKJSON.Spec, language: DocsLanguage) {
@@ -389,6 +437,66 @@ export async function getReadmeContent(spec: SDKJSON.Spec, language: DocsLanguag
389
437
  return spec.readme[language];
390
438
  }
391
439
 
440
+ let astroShikiHighlighter:
441
+ | HighlighterGeneric<BundledLanguage, BundledTheme>
442
+ | Promise<HighlighterGeneric<BundledLanguage, BundledTheme>>
443
+ | null = null;
444
+
445
+ async function astroHighlight() {
446
+ if (astroShikiHighlighter) {
447
+ return astroShikiHighlighter;
448
+ }
449
+
450
+ astroShikiHighlighter = createHighlighter({
451
+ themes: [
452
+ 'github-light',
453
+ 'github-dark',
454
+ {
455
+ name: 'stainless-docs-json',
456
+ colors: {
457
+ 'editor.background': 'var(--stl-color-background)',
458
+ 'editor.foreground': 'var(--stl-color-foreground)',
459
+ },
460
+
461
+ tokenColors: [
462
+ {
463
+ scope: ['comment', 'punctuation.definition.comment'],
464
+ settings: { foreground: 'var(--stl-color-foreground-muted)' },
465
+ },
466
+ // numbers, booleans, null
467
+ {
468
+ scope: ['constant.numeric', 'constant.language'],
469
+ settings: { foreground: 'var(--stl-color-orange-foreground)' },
470
+ },
471
+ // strings
472
+ {
473
+ scope: ['string', 'string.quoted', 'string.template'],
474
+ settings: { foreground: 'var(--stl-color-green-foreground)' },
475
+ },
476
+ // Keys, brackets
477
+ {
478
+ scope: ['support.type', 'meta'],
479
+ settings: { foreground: 'var(--stl-color-foreground)' },
480
+ },
481
+ // brackets
482
+ {
483
+ scope: ['meta'],
484
+ settings: { foreground: 'var(--stl-color-foreground-muted)' },
485
+ },
486
+ // built-in types
487
+ {
488
+ scope: ['support.type.builtin'],
489
+ settings: { foreground: 'var(--stl-color-purple-foreground)' },
490
+ },
491
+ ],
492
+ },
493
+ ],
494
+ langs: SupportedLanguageSyntaxes,
495
+ });
496
+
497
+ return astroShikiHighlighter;
498
+ }
499
+
392
500
  // Astro's markdown processor is a singleton
393
501
  // Need to cache it instead of instanting per request
394
502
  let astroMarkdownProcessor: MarkdownProcessor;
@@ -406,18 +514,6 @@ async function astroMarkdown() {
406
514
  return astroMarkdownProcessor;
407
515
  }
408
516
 
409
- let astroShikiHighlighter: HighlighterGeneric<BundledLanguage, BundledTheme>;
410
- async function astroHighlight() {
411
- if (!astroShikiHighlighter) {
412
- astroShikiHighlighter = await createHighlighter({
413
- themes: ['github-light', 'github-dark'],
414
- langs: SupportedLanguageSyntaxes,
415
- });
416
- }
417
-
418
- return astroShikiHighlighter;
419
- }
420
-
421
517
  export async function astroMarkdownRender(content: string) {
422
518
  const md = await astroMarkdown();
423
519
  const output = await md.render(content);
@@ -58,7 +58,7 @@ function recursiveGetPlaceholderItems(
58
58
  items: PlaceholderItemResult[],
59
59
  ): PlaceholderItemResult[] {
60
60
  for (let i = 0; i < sidebar.length; i++) {
61
- const entry = sidebar[i];
61
+ const entry = sidebar[i]!;
62
62
  if ('attrs' in entry && entry.attrs?.about === INTERNAL_REFERENCE_ENTRY_MARKER) {
63
63
  items.push({
64
64
  index: i,
@@ -5,7 +5,7 @@ import { BASE_PATH } from 'virtual:stl-starlight-virtual-module';
5
5
  import { getAPIReferencePlaceholderItems } from './referencePlaceholderUtils';
6
6
  import { getMethodFromSDKJSON, recursiveReplacePlaceholderItems } from './generateAPIReferenceLink';
7
7
  import { forceGenerateRoute } from './cms/sidebar-builder';
8
- import { parseRoute } from '@stainless-api/docs-ui/src/routing';
8
+ import { parseRoute } from '@stainless-api/docs-ui/routing';
9
9
 
10
10
  // this fn is loaded in the plugin via addRouteMiddleware
11
11
 
@@ -15,6 +15,10 @@ export const onRequest = defineRouteMiddleware(async (context) => {
15
15
 
16
16
  const slug = `/${context.locals.starlightRoute.id}`; // same as .slug but not deprecated
17
17
 
18
+ context.locals.starlightRoute._stlStarlight = {
19
+ basePath: BASE_PATH,
20
+ };
21
+
18
22
  const apiReferencePlaceholderItems = getAPIReferencePlaceholderItems(context.locals.starlightRoute.sidebar);
19
23
 
20
24
  const spec = await cmsClient.getSpec();