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

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 +390 -0
  2. package/eslint-suppressions.json +47 -0
  3. package/locals.d.ts +14 -0
  4. package/package.json +43 -39
  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,19 +1,20 @@
1
1
  import * as React from 'react';
2
- import { BASE_PATH, SEARCH } from 'virtual:stl-starlight-virtual-module';
3
- import { parseRoute, generateRoute } from '@stainless-api/docs-ui/src/routing';
4
- import { SearchModal } from '@stainless-api/docs-ui/src/search/index';
5
- import { ChatModal } from '@stainless-api/docs-ui/src/components/chat';
2
+ import { BASE_PATH, HIGHLIGHT_THEMES } from 'virtual:stl-starlight-virtual-module';
3
+ import { parseRoute, generateRoute } from '@stainless-api/docs-ui/routing';
4
+ import { SearchModal } from '@stainless-api/docs-ui/search';
6
5
  import * as Markdoc from '@markdoc/markdoc';
7
6
  import { createHighlighter } from 'shiki';
8
7
  import type { BundledLanguage, BundledTheme, HighlighterGeneric } from 'shiki';
9
8
 
10
9
  import {
11
10
  DocsProvider,
11
+ type MarkdownContext,
12
12
  MarkdownProvider,
13
13
  NavigationProvider,
14
14
  SearchProvider,
15
- } from '@stainless-api/docs-ui/src/contexts';
16
- import type { SearchSettings } from '@stainless-api/docs-ui/src/search/types';
15
+ } from '@stainless-api/docs-ui/contexts';
16
+ import { ComponentProvider } from '@stainless-api/docs-ui/contexts/component';
17
+ import type { SearchSettings } from '@stainless-api/docs-ui/search/types';
17
18
 
18
19
  let $$highlighter: HighlighterGeneric<BundledLanguage, BundledTheme> | null = null;
19
20
  async function getHighlighter() {
@@ -27,7 +28,7 @@ async function getHighlighter() {
27
28
  return $$highlighter;
28
29
  }
29
30
 
30
- async function createMarkdownRenderer() {
31
+ async function createMarkdownRenderer(): Promise<MarkdownContext> {
31
32
  const highlighter = await getHighlighter();
32
33
  const markdocConfig: Markdoc.Config = {
33
34
  nodes: {
@@ -37,7 +38,10 @@ async function createMarkdownRenderer() {
37
38
  transform(node, config) {
38
39
  const attributes = node.transformAttributes(config);
39
40
  const lang = node.attributes.language === 'node' ? 'typescript' : node.attributes.language;
40
- const code = highlighter.codeToTokens(node.attributes.content, { lang, theme: 'github-dark' });
41
+ const code = highlighter.codeToTokens(node.attributes.content, {
42
+ lang,
43
+ theme: 'github-dark',
44
+ });
41
45
 
42
46
  return new Markdoc.Tag(
43
47
  'pre',
@@ -66,16 +70,25 @@ async function createMarkdownRenderer() {
66
70
  },
67
71
  };
68
72
 
69
- return (content: string) => {
70
- const ast = Markdoc.parse(content);
71
- const transformed = Markdoc.transform(ast, markdocConfig);
72
- return Markdoc.renderers.html(transformed);
73
+ return {
74
+ render: (content: string) => {
75
+ const ast = Markdoc.parse(content);
76
+ const transformed = Markdoc.transform(ast, markdocConfig);
77
+ return Markdoc.renderers.html(transformed);
78
+ },
79
+ highlight: (content: string, language: string) => {
80
+ return highlighter.codeToHtml(content, {
81
+ lang: language ?? 'javascript',
82
+ themes: HIGHLIGHT_THEMES || {},
83
+ });
84
+ },
73
85
  };
74
86
  }
75
87
 
76
88
  export function DocsSearch({ settings, currentPath }: { settings: SearchSettings; currentPath: string }) {
77
- const renderMarkdown = React.use(createMarkdownRenderer());
89
+ const markdownRenderer = React.use(createMarkdownRenderer());
78
90
  const { stainlessPath, language } = parseRoute(BASE_PATH, currentPath);
91
+ // eslint-disable-next-line turbo/no-undeclared-env-vars
79
92
  const pageFind = import.meta.env.DEV ? undefined : '/pagefind/pagefind.js';
80
93
 
81
94
  function handleSelect(path: string) {
@@ -85,16 +98,17 @@ export function DocsSearch({ settings, currentPath }: { settings: SearchSettings
85
98
 
86
99
  return (
87
100
  <DocsProvider spec={null} language={language}>
88
- <NavigationProvider basePath="/" selectedPath={stainlessPath}>
89
- <MarkdownProvider render={renderMarkdown}>
90
- <SearchProvider onSelect={handleSelect} pageFind={pageFind} settings={settings}>
91
- <div className="stldocs-root">
92
- <SearchModal id="stldocs-search" />
93
- {SEARCH?.enableAISearch === true && <ChatModal id="stldocs-chat" />}
94
- </div>
95
- </SearchProvider>
96
- </MarkdownProvider>
97
- </NavigationProvider>
101
+ <ComponentProvider>
102
+ <NavigationProvider basePath="/" selectedPath={stainlessPath}>
103
+ <MarkdownProvider {...markdownRenderer}>
104
+ <SearchProvider onSelect={handleSelect} pageFind={pageFind} settings={settings}>
105
+ <div className="stldocs-root">
106
+ <SearchModal id="stldocs-search" />
107
+ </div>
108
+ </SearchProvider>
109
+ </MarkdownProvider>
110
+ </NavigationProvider>
111
+ </ComponentProvider>
98
112
  </DocsProvider>
99
113
  );
100
114
  }
@@ -1,8 +1,8 @@
1
1
  // This is probably temporary, but it fills in functionality needed for Mintlify imports
2
2
 
3
3
  import type { StarlightRouteData } from '@astrojs/starlight/route-data';
4
- import type * as SDKJSON from '~/lib/json-spec-v2/types';
5
- import { walkTree } from '@stainless-api/docs-ui/src/routing';
4
+ import type * as SDKJSON from '@stainless/sdk-json';
5
+ import { walkTree } from '@stainless-api/docs-ui/routing';
6
6
 
7
7
  const INTERNAL_REFERENCE_ENTRY_MARKER = 'STL_STARLIGHT_API_REFERENCE_METHOD_LINK_PLACEHOLDER';
8
8
 
@@ -0,0 +1,235 @@
1
+ import { initDropdownButton } from '@stainless-api/ui-primitives/scripts';
2
+ import { getPageLoadEvent } from '../helpers/getPageLoadEvent';
3
+
4
+ export type DropdownIcon = 'markdown' | 'copy' | 'claude' | 'chatgpt' | 'gemini' | 'cursor';
5
+
6
+ interface DropdownOptionInputProps {
7
+ onClick: () => void;
8
+ icon: DropdownIcon;
9
+ primaryAction?: boolean;
10
+ clientHidden?: boolean;
11
+ external: boolean;
12
+ }
13
+
14
+ function option(label: string[] | string, props: DropdownOptionInputProps) {
15
+ const labelArr = typeof label === 'string' ? [label] : label;
16
+ return {
17
+ ...props,
18
+ label: labelArr,
19
+ primaryAction: props.primaryAction ?? false,
20
+ clientHidden: props.clientHidden ?? false,
21
+ id: labelArr.join('').toLowerCase().replace(/ /g, '-'),
22
+ };
23
+ }
24
+
25
+ type DropdownOption = ReturnType<typeof option>;
26
+
27
+ function getMarkdownUrl(type: 'relative' | 'absolute') {
28
+ const currentUrl = new URL(window.location.href);
29
+ const hasTrailingSlash = currentUrl.pathname.endsWith('/');
30
+
31
+ const markdownUrl = [
32
+ type === 'absolute' ? currentUrl.origin : '',
33
+ currentUrl.pathname,
34
+ hasTrailingSlash ? '' : '/',
35
+ 'index.md',
36
+ ].join('');
37
+
38
+ return markdownUrl;
39
+ }
40
+
41
+ function getURLEncodedPrompt() {
42
+ const mdUrl = getMarkdownUrl('absolute');
43
+ const aiPrompt = encodeURIComponent(
44
+ `Load the contents of ${mdUrl} into this chat's context so we can discuss it.`,
45
+ );
46
+ return aiPrompt;
47
+ }
48
+
49
+ function openDeepLink({ deepLinkUrl, fallbackUrl }: { deepLinkUrl: string; fallbackUrl: string }) {
50
+ if (navigator.userAgent.includes('Safari') && !navigator.userAgent.includes('Chrom')) {
51
+ // safari doesn't let us detect if the deep link worked
52
+ window.open(fallbackUrl, '_blank');
53
+ return;
54
+ }
55
+
56
+ const controller = new AbortController();
57
+ let frame: HTMLIFrameElement | null;
58
+
59
+ // We are using a native deep link with a fallback web url.
60
+ if (navigator.userAgent.includes('Chrom')) {
61
+ // In Chrome, load it in a hidden frame, this shows the "Do you want to open ...?" prompt, but unlike
62
+ // top level navigation this preserves our userActivation, so we can open the fallback if it fails.
63
+ frame = Object.assign(document.createElement('iframe'), { src: deepLinkUrl });
64
+ document.head.append(frame);
65
+ } else {
66
+ // In Firefox do the opposite.
67
+ location.href = deepLinkUrl;
68
+ }
69
+
70
+ // The popup (in non-Safari browsers) fires a `blur` event.
71
+ window.addEventListener(
72
+ 'blur',
73
+ () => {
74
+ controller.abort();
75
+ },
76
+ controller,
77
+ );
78
+
79
+ // If it's been 300ms with no popup, open the fallback web url.
80
+ const timeout = setTimeout(() => {
81
+ window.open(fallbackUrl, '_blank');
82
+ }, 300);
83
+
84
+ controller.signal.addEventListener('abort', () => {
85
+ clearTimeout(timeout);
86
+ frame?.remove();
87
+ });
88
+ }
89
+
90
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Browser_detection_using_the_user_agent#mobile_tablet_or_desktop
91
+ const hasMobileUserAgent = navigator.userAgent.includes('Mobi');
92
+
93
+ // 2d array of dropdown options
94
+ // each sub-array is a group, separated by a horizontal rule in the UI
95
+ const aiDropdownOptions: DropdownOption[][] = [
96
+ [
97
+ option(['Open in ', 'Claude'], {
98
+ onClick: () => {
99
+ window.open(`https://claude.ai/new?q=${getURLEncodedPrompt()}`, '_blank');
100
+ },
101
+ icon: 'claude',
102
+ primaryAction: false,
103
+ external: true,
104
+ }),
105
+ option(['Open in ', 'ChatGPT'], {
106
+ onClick: () => {
107
+ window.open(`https://chatgpt.com/?hints=search&prompt=${getURLEncodedPrompt()}`, '_blank');
108
+ },
109
+ icon: 'chatgpt',
110
+ primaryAction: false,
111
+ external: true,
112
+ }),
113
+ option(['Open in ', 'Cursor'], {
114
+ onClick: () => {
115
+ const aiPrompt = getURLEncodedPrompt();
116
+ openDeepLink({
117
+ deepLinkUrl: `cursor://anysphere.cursor-deeplink/prompt?text=${aiPrompt}`,
118
+ fallbackUrl: `https://cursor.com/link/prompt?text=${aiPrompt}`,
119
+ });
120
+ },
121
+ clientHidden: hasMobileUserAgent,
122
+ icon: 'cursor',
123
+ primaryAction: false,
124
+ external: true,
125
+ }),
126
+ ],
127
+ [
128
+ option('Copy Markdown', {
129
+ onClick: () => {
130
+ // Source: https://wolfgangrittner.dev/how-to-use-clipboard-api-in-firefox/
131
+ const markdownUrl = getMarkdownUrl('relative');
132
+ // ClipboardItem doesn't exist in every browser
133
+ // eslint-disable-next-line no-constant-binary-expression
134
+ if (typeof ClipboardItem && navigator.clipboard.write) {
135
+ // NOTE: Safari locks down the clipboard API to only work when triggered
136
+ // by a direct user interaction. You can't use it async in a promise.
137
+ // But! You can wrap the promise in a ClipboardItem, and give that to
138
+ // the clipboard API.
139
+ // Found this on https://developer.apple.com/forums/thread/691873
140
+ const text = new ClipboardItem({
141
+ 'text/plain': fetch(markdownUrl)
142
+ .then((response) => response.text())
143
+ .then((text) => new Blob([text], { type: 'text/plain' })),
144
+ });
145
+ navigator.clipboard.write([text]);
146
+ } else {
147
+ // NOTE: Firefox has support for ClipboardItem and navigator.clipboard.write,
148
+ // but those are behind `dom.events.asyncClipboard.clipboardItem` preference.
149
+ // Good news is that other than Safari, Firefox does not care about
150
+ // Clipboard API being used async in a Promise.
151
+ fetch(markdownUrl)
152
+ .then((response) => response.text())
153
+ .then((text) => navigator.clipboard.writeText(text));
154
+ }
155
+ },
156
+ icon: 'copy',
157
+ primaryAction: true,
158
+ external: false,
159
+ }),
160
+ option('View as Markdown', {
161
+ onClick: () => {
162
+ window.open(getMarkdownUrl('absolute'), '_blank');
163
+ },
164
+ icon: 'markdown',
165
+ primaryAction: false,
166
+ external: true,
167
+ }),
168
+ ],
169
+ ];
170
+
171
+ // TODO: Add support for more LLMs
172
+ // {
173
+ // label: ['Open in ', 'Gemini'],
174
+ // onClick: () => {
175
+ // openInLLM('https://gemini.google.com?prompt_action=prefill&prompt_text=');
176
+ // },
177
+ // icon: 'gemini',
178
+ // primaryAction: false,
179
+ // },
180
+
181
+ export function getAIDropdownOptions() {
182
+ const renderedOptions = aiDropdownOptions.map((group, index) => {
183
+ return {
184
+ options: group,
185
+ isLast: index === aiDropdownOptions.length - 1,
186
+ reactKey: index,
187
+ };
188
+ });
189
+
190
+ const allOptions = renderedOptions.flatMap((group) => group.options);
191
+ const primaryAction = allOptions.find((o) => o.primaryAction) ?? allOptions[0]!;
192
+
193
+ return {
194
+ primaryAction,
195
+ groups: renderedOptions,
196
+ };
197
+ }
198
+
199
+ export function wireAIDropdown() {
200
+ const { primaryAction, groups } = getAIDropdownOptions();
201
+ const flatOptions = groups.flatMap((group) => group.options);
202
+ function triggerOption(id: string) {
203
+ const option = flatOptions.find((option) => option.id === id);
204
+ if (!option) return;
205
+ option.onClick();
206
+ }
207
+
208
+ document.addEventListener(getPageLoadEvent(), () => {
209
+ // we hide the Cursor option on non-desktop devices
210
+ for (const option of flatOptions) {
211
+ if (option.clientHidden === true) {
212
+ const el = document.querySelector(
213
+ `[data-dropdown-id="ai-dropdown-button"] [data-value="${option.id}"]`,
214
+ );
215
+ if (el) {
216
+ el.remove();
217
+ }
218
+ }
219
+ }
220
+
221
+ const dropdowns = document.querySelectorAll('[data-dropdown-id="ai-dropdown-button"]');
222
+
223
+ dropdowns.forEach((dropdown) => {
224
+ initDropdownButton({
225
+ dropdown: dropdown,
226
+ onSelect: (value) => {
227
+ triggerOption(value);
228
+ },
229
+ onPrimaryAction: () => {
230
+ triggerOption(primaryAction.id);
231
+ },
232
+ });
233
+ });
234
+ });
235
+ }
@@ -0,0 +1,33 @@
1
+ document.addEventListener('DOMContentLoaded', function () {
2
+ const COLLAPSED_HEIGHT = 170;
3
+
4
+ const container = document.querySelector<HTMLElement>('[data-stldocs-property-group="method-description"]');
5
+
6
+ if (!container) return;
7
+
8
+ const toggle = document?.querySelector<HTMLButtonElement>('[data-method-description-toggle]');
9
+ if (!toggle) return;
10
+
11
+ // If content isn't tall enough, don't show the button
12
+ if (container.scrollHeight <= COLLAPSED_HEIGHT + 1) {
13
+ toggle.hidden = true;
14
+ container.dataset.collapsed = 'false';
15
+ return;
16
+ }
17
+
18
+ // Only show button if content is taller than collapsed max height
19
+ if (container.scrollHeight > COLLAPSED_HEIGHT + 1) {
20
+ toggle.hidden = false;
21
+ } else {
22
+ // Not tall enough to need collapsing — show full content and hide button
23
+ container.dataset.collapsed = 'false';
24
+ toggle.hidden = true;
25
+ return;
26
+ }
27
+
28
+ toggle.addEventListener('click', function () {
29
+ const isCollapsed = container.dataset.collapsed !== 'false';
30
+ container.dataset.collapsed = isCollapsed ? 'false' : 'true';
31
+ toggle.textContent = isCollapsed ? 'Show less' : 'Show more';
32
+ });
33
+ });
@@ -1,12 +1,10 @@
1
- import { parseRoute, scrollToPath } from '@stainless-api/docs-ui/src/routing';
1
+ import { parseRoute, scrollToPath } from '@stainless-api/docs-ui/routing';
2
2
  import { BASE_PATH } from 'virtual:stl-starlight-virtual-module';
3
3
  import { updateSelectedLanguage } from '../languages';
4
4
  import { navigate } from 'astro:transitions/client';
5
5
  import { getPageLoadEvent } from '../helpers/getPageLoadEvent.ts';
6
6
 
7
- import { initDropdown } from '@stainless-api/docs-ui/src/components/scripts/dropdown';
8
- import { initDropdownButton } from '@stainless-api/ui-primitives/scripts';
9
- import { copyCurrentPageAsMarkdown, onSelectAIOption } from './ai-dropdown.ts';
7
+ import { initDropdown } from '@stainless-api/docs/components/scripts';
10
8
 
11
9
  history.scrollRestoration = 'auto';
12
10
 
@@ -24,10 +22,13 @@ window.addEventListener('popstate', (ev: PopStateEvent) => {
24
22
  });
25
23
 
26
24
  document.addEventListener(getPageLoadEvent(), () => {
25
+ const rootElement = document.getElementById('stldocs-snippet-select');
26
+ if (!rootElement) return;
27
+
27
28
  initDropdown({
28
- dropdownId: 'stldocs-snippet-select',
29
+ root: rootElement,
29
30
  onSelect: (value) => {
30
- const originalLanguage = document.getElementById('stldocs-snippet-select')?.dataset.currentValue;
31
+ const originalLanguage = rootElement?.dataset.currentValue;
31
32
  navigate(updateSelectedLanguage(BASE_PATH, originalLanguage, value));
32
33
  },
33
34
  });
@@ -36,27 +37,6 @@ document.addEventListener(getPageLoadEvent(), () => {
36
37
  if (path) setTimeout(() => scrollToPath(path.slice(1)), 10);
37
38
  });
38
39
 
39
- document.addEventListener(getPageLoadEvent(), () => {
40
- console.log('Initializing AI Dropdown');
41
- initDropdownButton({
42
- dropdownId: 'ai-dropdown-button',
43
- onSelect: onSelectAIOption,
44
- onPrimaryAction: (el) => {
45
- copyCurrentPageAsMarkdown();
46
- const innerText = el.querySelector('[data-part="primary-action-text"]');
47
- if (!innerText) return;
48
-
49
- const originalInnerHtml = innerText.innerHTML;
50
- innerText.innerHTML = 'Copied!';
51
- el.classList.add('disabled');
52
- setTimeout(() => {
53
- innerText.innerHTML = originalInnerHtml;
54
- el.classList.remove('disabled');
55
- }, 1000);
56
- },
57
- });
58
- });
59
-
60
40
  document.addEventListener('click', (event) => {
61
41
  const toggle = (event.target as HTMLElement).closest(
62
42
  '[data-stldocs-property-toggle-expanded] > .stldocs-expand-toggle-content',
@@ -1,4 +1,4 @@
1
- // import { ENABLE_CLIENT_ROUTER } from 'virtual:stl-stl-starlight-virtual-module';
1
+ // import { ENABLE_CLIENT_ROUTER } from 'virtual:stl-docs-virtual-module';
2
2
 
3
3
  export function getPageLoadEvent() {
4
4
  // if (ENABLE_CLIENT_ROUTER) {
package/plugin/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import react from '@astrojs/react';
2
2
  import type { StarlightPlugin } from '@astrojs/starlight/types';
3
- import type { AstroIntegration } from 'astro';
3
+ import type { AstroIntegration, AstroIntegrationLogger } from 'astro';
4
4
  import { config } from 'dotenv';
5
5
  import getPort from 'get-port';
6
6
  import { startDevServer } from './cms/server';
@@ -21,13 +21,18 @@ import {
21
21
  type SpecRetrieverConfig,
22
22
  } from './loadPluginConfig';
23
23
  import { buildVirtualModuleString } from '../shared/virtualModule';
24
+ import type * as StlStarlightVirtualModule from 'virtual:stl-starlight-virtual-module';
24
25
  import path from 'path';
25
26
  import fs from 'fs';
27
+ import { getSharedLogger } from '../shared/getSharedLogger';
28
+ import { resolveSrcFile } from '../resolveSrcFile';
26
29
 
27
30
  export { generateAPILink } from './generateAPIReferenceLink';
28
31
  export type { ReferenceSidebarConfigItem };
29
32
 
30
- config();
33
+ config({
34
+ quiet: true,
35
+ });
31
36
 
32
37
  let sidebarIdCounter = 0;
33
38
 
@@ -108,6 +113,7 @@ function tmpGetCMSServerConfig(specRetrieverConfig: SpecRetrieverConfig) {
108
113
 
109
114
  async function stlStarlightAstroIntegration(
110
115
  pluginConfig: NormalizedStainlessStarlightConfig,
116
+ stlStarlightPluginLogger: AstroIntegrationLogger,
111
117
  ): Promise<AstroIntegration> {
112
118
  const virtualId = `virtual:stl-starlight-virtual-module`;
113
119
  // The '\0' prefix tells Vite “this is a virtual module” and prevents it from being resolved again.
@@ -117,11 +123,12 @@ async function stlStarlightAstroIntegration(
117
123
 
118
124
  const { apiKey, version, devPaths } = tmpGetCMSServerConfig(pluginConfig.specRetrieverConfig);
119
125
 
120
- const cmsServer = await startDevServer({
126
+ const cmsServer = startDevServer({
121
127
  port: CMS_PORT,
122
- apiKey,
128
+ apiKey: apiKey.value,
123
129
  version,
124
130
  devPaths,
131
+ logger: stlStarlightPluginLogger,
125
132
  getGeneratedSidebarConfig: (id: number) => {
126
133
  const config = sidebarConfigs.get(id);
127
134
  if (!config) {
@@ -134,43 +141,45 @@ async function stlStarlightAstroIntegration(
134
141
  return {
135
142
  name: 'stl-starlight-astro',
136
143
  hooks: {
137
- 'astro:config:setup': async ({ injectRoute, updateConfig, logger, command, config: astroConfig }) => {
144
+ 'astro:config:setup': async ({
145
+ injectRoute,
146
+ updateConfig,
147
+ logger: localLogger,
148
+ command,
149
+ config: astroConfig,
150
+ }) => {
151
+ const logger = getSharedLogger({ fallback: localLogger });
138
152
  const projectDir = astroConfig.root.pathname;
139
153
 
140
154
  const middlewareFile = path.join(projectDir, 'middleware.stainless.ts');
141
155
 
142
156
  let vmMiddlewareExport = 'export const MIDDLEWARE = {};';
143
157
  if (fs.existsSync(middlewareFile)) {
144
- logger.info(`Loading middleware from ${middlewareFile}`);
158
+ logger.debug(`Loading middleware from ${middlewareFile}`);
145
159
  vmMiddlewareExport = `export { default as MIDDLEWARE } from '${middlewareFile}';`;
146
160
  }
147
161
 
148
162
  injectRoute({
149
- pattern: `${pluginConfig.basePath}/[...slug].md`,
150
- entrypoint: '@stainless-api/docs/MarkdownRoute',
163
+ pattern: `${pluginConfig.basePath}/[...slug]/index.md`,
164
+ entrypoint: resolveSrcFile('/plugin/routes/markdown.ts'),
151
165
  prerender: command === 'build',
152
166
  });
153
167
 
154
- const astroFile = command === 'build' ? 'DocsStaticRoute' : 'DocsRoute';
168
+ const astroFile = command === 'build' ? 'DocsStatic' : 'Docs';
155
169
  injectRoute({
156
170
  pattern: `${pluginConfig.basePath}/[...slug]`,
157
- // in prod I think this points to @stainless-starlight/components/docs.astro
158
- entrypoint: `@stainless-api/docs/${astroFile}`,
171
+ entrypoint: resolveSrcFile(`/plugin/routes/${astroFile}.astro`),
159
172
  prerender: command === 'build',
160
173
  });
161
174
 
162
175
  injectRoute({
163
176
  pattern: pluginConfig.basePath,
164
- entrypoint: '@stainless-api/docs/OverviewRoute',
177
+ entrypoint: resolveSrcFile('/plugin/routes/Overview.astro'),
165
178
  prerender: command === 'build',
166
179
  });
167
180
 
168
181
  updateConfig({
169
182
  vite: {
170
- ssr: {
171
- noExternal: ['@stainless-api/ui-primitives'],
172
- },
173
- optimizeDeps: { include: ['@stainless-api/ui-primitives'] },
174
183
  plugins: [
175
184
  {
176
185
  name: 'stl-starlight-vite',
@@ -211,9 +220,11 @@ async function stlStarlightAstroIntegration(
211
220
  HIGHLIGHT_THEMES: pluginConfig.highlighting.themes,
212
221
  CONTENT_PANEL_LAYOUT: pluginConfig.contentPanel.layout,
213
222
  EXPERIMENTAL_COLLAPSIBLE_SNIPPETS: pluginConfig.experimentalCollapsibleSnippets,
223
+ EXPERIMENTAL_COLLAPSIBLE_METHOD_DESCRIPTIONS:
224
+ pluginConfig.experimentalCollapsibleMethodDescriptions,
214
225
  PROPERTY_SETTINGS: pluginConfig.propertySettings,
215
- SEARCH: pluginConfig.search,
216
- }),
226
+ ENABLE_CONTEXT_MENU: pluginConfig.contextMenu,
227
+ } satisfies Omit<typeof StlStarlightVirtualModule, 'MIDDLEWARE'>),
217
228
  vmMiddlewareExport,
218
229
  ].join('\n');
219
230
  }
@@ -241,15 +252,20 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
241
252
  command,
242
253
  config: starlightConfig,
243
254
  astroConfig,
244
- logger,
255
+ logger: localLogger,
245
256
  }) => {
246
257
  if (command !== 'build' && command !== 'dev') {
247
258
  return;
248
259
  }
249
260
 
261
+ const logger = getSharedLogger({ fallback: localLogger });
262
+
250
263
  const configParseResult = parseStarlightPluginConfig(someUserConfig, command);
251
264
  if (configParseResult.result === 'error') {
252
- logger.error(configParseResult.message);
265
+ const errorLines = configParseResult.message.split('\n');
266
+ for (const line of errorLines) {
267
+ logger.error(line);
268
+ }
253
269
  process.exit(1);
254
270
  }
255
271
  const config = configParseResult.config;
@@ -260,17 +276,30 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
260
276
  addIntegration(react());
261
277
  }
262
278
 
279
+ if ('apiKey' in config.specRetrieverConfig) {
280
+ if (!config.specRetrieverConfig.apiKey) {
281
+ logger.info(`Stainless credentials not loaded`);
282
+ } else if (config.specRetrieverConfig.apiKey.source === 'explicit-config') {
283
+ logger.info(`Stainless credentials loaded from user config`);
284
+ } else if (config.specRetrieverConfig.apiKey.source === 'environment-variable') {
285
+ logger.info('Stainless credentials loaded from `STAINLESS_API_KEY` environment variable');
286
+ } else if (config.specRetrieverConfig.apiKey.source === 'cli') {
287
+ logger.info('Stainless credentials loaded from `stl` CLI');
288
+ }
289
+ }
290
+
263
291
  if (
264
292
  command === 'build' &&
265
293
  config.specRetrieverConfig.kind === 'local_spec_server_with_remote_files'
266
294
  ) {
267
295
  await buildAlgoliaIndex({
268
296
  version: config.specRetrieverConfig.version,
269
- apiKey: config.specRetrieverConfig.apiKey,
297
+ apiKey: config.specRetrieverConfig.apiKey.value,
298
+ logger,
270
299
  });
271
300
  }
272
301
 
273
- addIntegration(await stlStarlightAstroIntegration(config));
302
+ addIntegration(await stlStarlightAstroIntegration(config, logger));
274
303
 
275
304
  if (starlightConfig.sidebar) {
276
305
  // for pagination (https://starlight.astro.build/reference/configuration/#pagination) to work correctly
@@ -281,21 +310,12 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
281
310
  }
282
311
  }
283
312
 
284
- const currentExpressiveCode =
285
- typeof starlightConfig.expressiveCode === 'object' ? starlightConfig.expressiveCode : {};
286
- updateConfig({
287
- expressiveCode: {
288
- ...currentExpressiveCode,
289
- themes: [...(currentExpressiveCode.themes || []), 'github-light', 'github-dark'],
290
- },
291
- });
292
-
293
313
  updateConfig({
294
314
  sidebar: starlightConfig.sidebar,
295
315
  });
296
316
 
297
317
  addRouteMiddleware({
298
- entrypoint: '@stainless-api/docs/replaceSidebarPlaceholderMiddleware',
318
+ entrypoint: resolveSrcFile('/plugin/replaceSidebarPlaceholderMiddleware.ts'),
299
319
  order: 'post',
300
320
  });
301
321
  },
@@ -304,5 +324,5 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
304
324
  }
305
325
 
306
326
  // Additional exports we want for Stainless <-> docs integration.
307
- export { parseStainlessPath } from '@stainless-api/docs-ui/src/routing';
308
- export { renderMarkdown } from '@stainless-api/docs-ui/src/markdown';
327
+ export { parseStainlessPath } from '@stainless-api/docs-ui/routing';
328
+ export { renderMarkdown } from '@stainless-api/docs-ui/markdown';
@@ -1,4 +1,4 @@
1
- import type { DocsLanguage } from '@stainless-api/docs-ui/src/routing';
1
+ import type { DocsLanguage } from '@stainless-api/docs-ui/routing';
2
2
  import KotlinIcon from './assets/languages/kotlin.svg';
3
3
  import RubyIcon from './assets/languages/ruby.svg';
4
4
  import TerraformIcon from './assets/languages/terraform.svg';
@@ -44,7 +44,7 @@ export function applyLanguageToLinks(basePath?: string, defaultLanguage?: string
44
44
  `[data-stldocs-overview],[data-stldocs-method],a.nav-link[href^='${basePath}']`,
45
45
  );
46
46
 
47
- for (var link of links) {
47
+ for (const link of links) {
48
48
  const href = link.getAttribute('href');
49
49
  const prefix = generatePrefix(basePath, language);
50
50
  if (href?.startsWith(basePath) && !href?.startsWith(prefix)) {