@stainless-api/docs 0.1.0-beta.3 → 0.1.0-beta.31
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.
- package/CHANGELOG.md +222 -0
- package/components/variables.css +1 -27
- package/eslint-suppressions.json +47 -0
- package/locals.d.ts +14 -0
- package/package.json +30 -27
- package/plugin/buildAlgoliaIndex.ts +29 -4
- package/plugin/cms/server.ts +97 -54
- package/plugin/cms/sidebar-builder.ts +6 -25
- package/plugin/cms/worker.ts +2 -2
- package/plugin/components/SnippetCode.tsx +7 -4
- package/plugin/components/search/SearchAlgolia.astro +0 -7
- package/plugin/components/search/SearchIsland.tsx +30 -17
- package/plugin/generateAPIReferenceLink.ts +1 -1
- package/plugin/globalJs/ai-dropdown-options.ts +233 -0
- package/plugin/globalJs/navigation.ts +0 -23
- package/plugin/helpers/getPageLoadEvent.ts +1 -1
- package/plugin/index.ts +48 -18
- package/plugin/languages.ts +1 -1
- package/plugin/loadPluginConfig.ts +100 -13
- package/plugin/react/Routing.tsx +30 -33
- package/plugin/referencePlaceholderUtils.ts +1 -1
- package/plugin/replaceSidebarPlaceholderMiddleware.ts +4 -0
- package/plugin/routes/Docs.astro +59 -85
- package/plugin/routes/Overview.astro +9 -15
- package/plugin/routes/markdown.ts +3 -3
- package/plugin/vendor/preview.worker.docs.js +7566 -6784
- package/resolveSrcFile.ts +10 -0
- package/shared/getSharedLogger.ts +15 -0
- package/shared/terminalUtils.ts +3 -0
- package/src/content.config.ts +9 -0
- package/stl-docs/components/AIDropdown.tsx +63 -0
- package/stl-docs/components/Head.astro +16 -0
- package/stl-docs/components/Header.astro +3 -2
- package/stl-docs/components/PageTitle.astro +64 -0
- package/stl-docs/components/TableOfContents.astro +34 -0
- package/stl-docs/components/ThemeSelect.astro +4 -2
- package/stl-docs/components/content-panel/ContentPanel.astro +9 -39
- package/stl-docs/components/headers/DefaultHeader.astro +1 -1
- package/stl-docs/components/headers/HeaderLinks.astro +1 -1
- package/stl-docs/components/headers/StackedHeader.astro +29 -24
- package/stl-docs/components/icons/chat-gpt.tsx +17 -0
- package/stl-docs/components/icons/claude.tsx +10 -0
- package/stl-docs/components/icons/cursor.tsx +46 -0
- package/stl-docs/components/icons/gemini.tsx +30 -0
- package/stl-docs/components/icons/markdown.tsx +10 -0
- package/stl-docs/components/index.ts +2 -0
- package/stl-docs/components/mintlify-compat/Accordion.astro +7 -38
- package/stl-docs/components/mintlify-compat/AccordionGroup.astro +9 -23
- package/stl-docs/components/mintlify-compat/Columns.astro +40 -42
- package/stl-docs/components/mintlify-compat/Frame.astro +16 -18
- package/stl-docs/components/mintlify-compat/Step.astro +30 -32
- package/stl-docs/components/mintlify-compat/Steps.astro +8 -10
- package/stl-docs/components/mintlify-compat/callouts/Callout.astro +10 -3
- package/stl-docs/components/mintlify-compat/callouts/Check.astro +7 -3
- package/stl-docs/components/mintlify-compat/callouts/Danger.astro +7 -3
- package/stl-docs/components/mintlify-compat/callouts/Info.astro +7 -3
- package/stl-docs/components/mintlify-compat/callouts/Note.astro +7 -3
- package/stl-docs/components/mintlify-compat/callouts/Tip.astro +7 -3
- package/stl-docs/components/mintlify-compat/callouts/Warning.astro +7 -3
- package/stl-docs/components/mintlify-compat/card.css +33 -35
- package/stl-docs/components/nav-tabs/NavDropdown.astro +1 -1
- package/stl-docs/components/nav-tabs/SecondaryNavTabs.astro +15 -7
- package/stl-docs/components/nav-tabs/buildNavLinks.ts +4 -3
- package/stl-docs/components/pagination/HomeLink.astro +10 -0
- package/stl-docs/components/pagination/Pagination.astro +173 -0
- package/stl-docs/components/pagination/PaginationLinkEmphasized.astro +22 -0
- package/stl-docs/components/pagination/PaginationLinkQuiet.astro +13 -0
- package/stl-docs/components/pagination/util.ts +71 -0
- package/stl-docs/components/{Sidebar.astro → sidebars/BaseSidebar.astro} +2 -3
- package/stl-docs/components/sidebars/SDKSelectSidebar.astro +8 -0
- package/stl-docs/disableCalloutSyntax.ts +36 -0
- package/stl-docs/index.ts +76 -26
- package/stl-docs/loadStlDocsConfig.ts +25 -3
- package/stl-docs/proseMarkdown/proseMarkdownIntegration.ts +64 -0
- package/stl-docs/proseMarkdown/proseMarkdownMiddleware.ts +34 -0
- package/stl-docs/proseMarkdown/toMarkdown.ts +158 -0
- package/stl-docs/tabsMiddleware.ts +12 -4
- package/styles/code.css +115 -127
- package/styles/fonts.css +32 -17
- package/styles/links.css +10 -49
- package/styles/overrides.css +54 -57
- package/styles/page.css +89 -59
- package/styles/sdk_select.css +6 -7
- package/styles/search.css +65 -67
- package/styles/sidebar.css +199 -128
- package/styles/toc.css +37 -33
- package/theme.css +9 -1
- package/tsconfig.json +2 -5
- package/virtual-module.d.ts +5 -1
- package/plugin/globalJs/ai-dropdown.ts +0 -57
- package/stl-docs/components/APIReferenceAIDropdown.tsx +0 -86
- package/stl-docs/components/content-panel/ProseAIDropdown.tsx +0 -64
- /package/{plugin/assets → assets}/fonts/geist/OFL.txt +0 -0
- /package/{plugin/assets → assets}/fonts/geist/geist-italic-latin-ext.woff2 +0 -0
- /package/{plugin/assets → assets}/fonts/geist/geist-italic-latin.woff2 +0 -0
- /package/{plugin/assets → assets}/fonts/geist/geist-latin-ext.woff2 +0 -0
- /package/{plugin/assets → assets}/fonts/geist/geist-latin.woff2 +0 -0
- /package/{plugin/assets → assets}/fonts/geist/geist-mono-italic-latin-ext.woff2 +0 -0
- /package/{plugin/assets → assets}/fonts/geist/geist-mono-italic-latin.woff2 +0 -0
- /package/{plugin/assets → assets}/fonts/geist/geist-mono-latin-ext.woff2 +0 -0
- /package/{plugin/assets → assets}/fonts/geist/geist-mono-latin.woff2 +0 -0
- /package/stl-docs/components/{content-panel/ContentBreadcrumbs.tsx → ContentBreadcrumbs.tsx} +0 -0
|
@@ -0,0 +1,233 @@
|
|
|
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(`[data-value="${option.id}"]`);
|
|
213
|
+
if (el) {
|
|
214
|
+
el.remove();
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const dropdowns = document.querySelectorAll('[data-dropdown-id]');
|
|
220
|
+
|
|
221
|
+
dropdowns.forEach((dropdown) => {
|
|
222
|
+
initDropdownButton({
|
|
223
|
+
dropdown: dropdown,
|
|
224
|
+
onSelect: (value) => {
|
|
225
|
+
triggerOption(value);
|
|
226
|
+
},
|
|
227
|
+
onPrimaryAction: () => {
|
|
228
|
+
triggerOption(primaryAction.id);
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
}
|
|
@@ -5,8 +5,6 @@ import { navigate } from 'astro:transitions/client';
|
|
|
5
5
|
import { getPageLoadEvent } from '../helpers/getPageLoadEvent.ts';
|
|
6
6
|
|
|
7
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';
|
|
10
8
|
|
|
11
9
|
history.scrollRestoration = 'auto';
|
|
12
10
|
|
|
@@ -36,27 +34,6 @@ document.addEventListener(getPageLoadEvent(), () => {
|
|
|
36
34
|
if (path) setTimeout(() => scrollToPath(path.slice(1)), 10);
|
|
37
35
|
});
|
|
38
36
|
|
|
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
37
|
document.addEventListener('click', (event) => {
|
|
61
38
|
const toggle = (event.target as HTMLElement).closest(
|
|
62
39
|
'[data-stldocs-property-toggle-expanded] > .stldocs-expand-toggle-content',
|
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';
|
|
@@ -23,11 +23,15 @@ import {
|
|
|
23
23
|
import { buildVirtualModuleString } from '../shared/virtualModule';
|
|
24
24
|
import path from 'path';
|
|
25
25
|
import fs from 'fs';
|
|
26
|
+
import { getSharedLogger } from '../shared/getSharedLogger';
|
|
27
|
+
import { resolveSrcFile } from '../resolveSrcFile';
|
|
26
28
|
|
|
27
29
|
export { generateAPILink } from './generateAPIReferenceLink';
|
|
28
30
|
export type { ReferenceSidebarConfigItem };
|
|
29
31
|
|
|
30
|
-
config(
|
|
32
|
+
config({
|
|
33
|
+
quiet: true,
|
|
34
|
+
});
|
|
31
35
|
|
|
32
36
|
let sidebarIdCounter = 0;
|
|
33
37
|
|
|
@@ -108,6 +112,7 @@ function tmpGetCMSServerConfig(specRetrieverConfig: SpecRetrieverConfig) {
|
|
|
108
112
|
|
|
109
113
|
async function stlStarlightAstroIntegration(
|
|
110
114
|
pluginConfig: NormalizedStainlessStarlightConfig,
|
|
115
|
+
stlStarlightPluginLogger: AstroIntegrationLogger,
|
|
111
116
|
): Promise<AstroIntegration> {
|
|
112
117
|
const virtualId = `virtual:stl-starlight-virtual-module`;
|
|
113
118
|
// The '\0' prefix tells Vite “this is a virtual module” and prevents it from being resolved again.
|
|
@@ -117,11 +122,12 @@ async function stlStarlightAstroIntegration(
|
|
|
117
122
|
|
|
118
123
|
const { apiKey, version, devPaths } = tmpGetCMSServerConfig(pluginConfig.specRetrieverConfig);
|
|
119
124
|
|
|
120
|
-
const cmsServer =
|
|
125
|
+
const cmsServer = startDevServer({
|
|
121
126
|
port: CMS_PORT,
|
|
122
|
-
apiKey,
|
|
127
|
+
apiKey: apiKey.value,
|
|
123
128
|
version,
|
|
124
129
|
devPaths,
|
|
130
|
+
logger: stlStarlightPluginLogger,
|
|
125
131
|
getGeneratedSidebarConfig: (id: number) => {
|
|
126
132
|
const config = sidebarConfigs.get(id);
|
|
127
133
|
if (!config) {
|
|
@@ -134,34 +140,40 @@ async function stlStarlightAstroIntegration(
|
|
|
134
140
|
return {
|
|
135
141
|
name: 'stl-starlight-astro',
|
|
136
142
|
hooks: {
|
|
137
|
-
'astro:config:setup': async ({
|
|
143
|
+
'astro:config:setup': async ({
|
|
144
|
+
injectRoute,
|
|
145
|
+
updateConfig,
|
|
146
|
+
logger: localLogger,
|
|
147
|
+
command,
|
|
148
|
+
config: astroConfig,
|
|
149
|
+
}) => {
|
|
150
|
+
const logger = getSharedLogger({ fallback: localLogger });
|
|
138
151
|
const projectDir = astroConfig.root.pathname;
|
|
139
152
|
|
|
140
153
|
const middlewareFile = path.join(projectDir, 'middleware.stainless.ts');
|
|
141
154
|
|
|
142
155
|
let vmMiddlewareExport = 'export const MIDDLEWARE = {};';
|
|
143
156
|
if (fs.existsSync(middlewareFile)) {
|
|
144
|
-
logger.
|
|
157
|
+
logger.debug(`Loading middleware from ${middlewareFile}`);
|
|
145
158
|
vmMiddlewareExport = `export { default as MIDDLEWARE } from '${middlewareFile}';`;
|
|
146
159
|
}
|
|
147
160
|
|
|
148
161
|
injectRoute({
|
|
149
|
-
pattern: `${pluginConfig.basePath}/[...slug].md`,
|
|
150
|
-
entrypoint: '
|
|
162
|
+
pattern: `${pluginConfig.basePath}/[...slug]/index.md`,
|
|
163
|
+
entrypoint: resolveSrcFile('/plugin/routes/markdown.ts'),
|
|
151
164
|
prerender: command === 'build',
|
|
152
165
|
});
|
|
153
166
|
|
|
154
|
-
const astroFile = command === 'build' ? '
|
|
167
|
+
const astroFile = command === 'build' ? 'DocsStatic' : 'Docs';
|
|
155
168
|
injectRoute({
|
|
156
169
|
pattern: `${pluginConfig.basePath}/[...slug]`,
|
|
157
|
-
|
|
158
|
-
entrypoint: `@stainless-api/docs/${astroFile}`,
|
|
170
|
+
entrypoint: resolveSrcFile(`/plugin/routes/${astroFile}.astro`),
|
|
159
171
|
prerender: command === 'build',
|
|
160
172
|
});
|
|
161
173
|
|
|
162
174
|
injectRoute({
|
|
163
175
|
pattern: pluginConfig.basePath,
|
|
164
|
-
entrypoint: '
|
|
176
|
+
entrypoint: resolveSrcFile('/plugin/routes/Overview.astro'),
|
|
165
177
|
prerender: command === 'build',
|
|
166
178
|
});
|
|
167
179
|
|
|
@@ -170,7 +182,6 @@ async function stlStarlightAstroIntegration(
|
|
|
170
182
|
ssr: {
|
|
171
183
|
noExternal: ['@stainless-api/ui-primitives'],
|
|
172
184
|
},
|
|
173
|
-
optimizeDeps: { include: ['@stainless-api/ui-primitives'] },
|
|
174
185
|
plugins: [
|
|
175
186
|
{
|
|
176
187
|
name: 'stl-starlight-vite',
|
|
@@ -213,6 +224,7 @@ async function stlStarlightAstroIntegration(
|
|
|
213
224
|
EXPERIMENTAL_COLLAPSIBLE_SNIPPETS: pluginConfig.experimentalCollapsibleSnippets,
|
|
214
225
|
PROPERTY_SETTINGS: pluginConfig.propertySettings,
|
|
215
226
|
SEARCH: pluginConfig.search,
|
|
227
|
+
ENABLE_CONTEXT_MENU: pluginConfig.contextMenu,
|
|
216
228
|
}),
|
|
217
229
|
vmMiddlewareExport,
|
|
218
230
|
].join('\n');
|
|
@@ -241,15 +253,20 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
|
|
|
241
253
|
command,
|
|
242
254
|
config: starlightConfig,
|
|
243
255
|
astroConfig,
|
|
244
|
-
logger,
|
|
256
|
+
logger: localLogger,
|
|
245
257
|
}) => {
|
|
246
258
|
if (command !== 'build' && command !== 'dev') {
|
|
247
259
|
return;
|
|
248
260
|
}
|
|
249
261
|
|
|
262
|
+
const logger = getSharedLogger({ fallback: localLogger });
|
|
263
|
+
|
|
250
264
|
const configParseResult = parseStarlightPluginConfig(someUserConfig, command);
|
|
251
265
|
if (configParseResult.result === 'error') {
|
|
252
|
-
|
|
266
|
+
const errorLines = configParseResult.message.split('\n');
|
|
267
|
+
for (const line of errorLines) {
|
|
268
|
+
logger.error(line);
|
|
269
|
+
}
|
|
253
270
|
process.exit(1);
|
|
254
271
|
}
|
|
255
272
|
const config = configParseResult.config;
|
|
@@ -260,17 +277,30 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
|
|
|
260
277
|
addIntegration(react());
|
|
261
278
|
}
|
|
262
279
|
|
|
280
|
+
if ('apiKey' in config.specRetrieverConfig) {
|
|
281
|
+
if (!config.specRetrieverConfig.apiKey) {
|
|
282
|
+
logger.info(`Stainless credentials not loaded`);
|
|
283
|
+
} else if (config.specRetrieverConfig.apiKey.source === 'explicit-config') {
|
|
284
|
+
logger.info(`Stainless credentials loaded from user config`);
|
|
285
|
+
} else if (config.specRetrieverConfig.apiKey.source === 'environment-variable') {
|
|
286
|
+
logger.info('Stainless credentials loaded from `STAINLESS_API_KEY` environment variable');
|
|
287
|
+
} else if (config.specRetrieverConfig.apiKey.source === 'cli') {
|
|
288
|
+
logger.info('Stainless credentials loaded from `stl` CLI');
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
263
292
|
if (
|
|
264
293
|
command === 'build' &&
|
|
265
294
|
config.specRetrieverConfig.kind === 'local_spec_server_with_remote_files'
|
|
266
295
|
) {
|
|
267
296
|
await buildAlgoliaIndex({
|
|
268
297
|
version: config.specRetrieverConfig.version,
|
|
269
|
-
apiKey: config.specRetrieverConfig.apiKey,
|
|
298
|
+
apiKey: config.specRetrieverConfig.apiKey.value,
|
|
299
|
+
logger,
|
|
270
300
|
});
|
|
271
301
|
}
|
|
272
302
|
|
|
273
|
-
addIntegration(await stlStarlightAstroIntegration(config));
|
|
303
|
+
addIntegration(await stlStarlightAstroIntegration(config, logger));
|
|
274
304
|
|
|
275
305
|
if (starlightConfig.sidebar) {
|
|
276
306
|
// for pagination (https://starlight.astro.build/reference/configuration/#pagination) to work correctly
|
|
@@ -295,7 +325,7 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
|
|
|
295
325
|
});
|
|
296
326
|
|
|
297
327
|
addRouteMiddleware({
|
|
298
|
-
entrypoint: '
|
|
328
|
+
entrypoint: resolveSrcFile('/plugin/replaceSidebarPlaceholderMiddleware.ts'),
|
|
299
329
|
order: 'post',
|
|
300
330
|
});
|
|
301
331
|
},
|
package/plugin/languages.ts
CHANGED
|
@@ -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 (
|
|
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)) {
|
|
@@ -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
6
|
import type { DocsLanguage } from '@stainless-api/docs-ui/src/routing';
|
|
5
7
|
import type { PropertySettingsType } from '@stainless-api/docs-ui/src/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
|
-
*
|
|
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,
|
|
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,7 +50,7 @@ export type StainlessStarlightUserConfig = {
|
|
|
42
50
|
|
|
43
51
|
/**
|
|
44
52
|
* Optional mount point for API reference docs.
|
|
45
|
-
*
|
|
53
|
+
* Default: `/api`
|
|
46
54
|
* Example: `/my-api` → docs available at `/my-api/…`.
|
|
47
55
|
*/
|
|
48
56
|
basePath?: string;
|
|
@@ -55,7 +63,7 @@ export type StainlessStarlightUserConfig = {
|
|
|
55
63
|
|
|
56
64
|
/**
|
|
57
65
|
* Optional language to treat as the default when the user hasn't selected one.
|
|
58
|
-
*
|
|
66
|
+
* Default: `"http"`
|
|
59
67
|
* Example: `"python"`
|
|
60
68
|
*/
|
|
61
69
|
defaultLanguage?: DocsLanguage;
|
|
@@ -90,7 +98,7 @@ export type StainlessStarlightUserConfig = {
|
|
|
90
98
|
contentPanel?: {
|
|
91
99
|
/**
|
|
92
100
|
* Optional layout for the content panel.
|
|
93
|
-
*
|
|
101
|
+
* Default: `"double-pane"`
|
|
94
102
|
*/
|
|
95
103
|
layout?: ContentLayout;
|
|
96
104
|
};
|
|
@@ -107,6 +115,8 @@ export type StainlessStarlightUserConfig = {
|
|
|
107
115
|
/**
|
|
108
116
|
* When set to `true`, the enableAISearch` setting turns on support for
|
|
109
117
|
* LLM-based conversations with the API documentation
|
|
118
|
+
*
|
|
119
|
+
* Default: `false`
|
|
110
120
|
*/
|
|
111
121
|
enableAISearch?: boolean;
|
|
112
122
|
};
|
|
@@ -114,9 +124,17 @@ export type StainlessStarlightUserConfig = {
|
|
|
114
124
|
/**
|
|
115
125
|
* Enable experimental collapsible code snippets. Snippets will be collapsed by default for
|
|
116
126
|
* single-pane and mobile layouts.
|
|
117
|
-
*
|
|
127
|
+
*
|
|
128
|
+
* Default: `false`
|
|
118
129
|
*/
|
|
119
130
|
experimentalCollapsibleSnippets?: boolean;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Whether to show the context menu with options like "Copy as Markdown" and "Open in ChatGPT".
|
|
134
|
+
*
|
|
135
|
+
* Default: `true`
|
|
136
|
+
*/
|
|
137
|
+
contextMenu?: boolean;
|
|
120
138
|
};
|
|
121
139
|
|
|
122
140
|
export type ExternalSpecServerUserConfig = Omit<StainlessStarlightUserConfig, 'stainlessProject'> & {
|
|
@@ -133,15 +151,29 @@ function getLocalFilePaths(command: AstroCommand): InputFilePaths | null {
|
|
|
133
151
|
if (command !== 'dev') {
|
|
134
152
|
return null;
|
|
135
153
|
}
|
|
136
|
-
|
|
154
|
+
|
|
155
|
+
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
156
|
+
const oasPath = process.env.OPENAPI_PATH;
|
|
157
|
+
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
158
|
+
const configPath = process.env.STAINLESS_CONFIG_PATH;
|
|
159
|
+
|
|
160
|
+
if (!oasPath || !configPath) {
|
|
137
161
|
return null;
|
|
138
162
|
}
|
|
163
|
+
|
|
139
164
|
return {
|
|
140
|
-
oasPath: resolvePath(
|
|
141
|
-
configPath: resolvePath(
|
|
165
|
+
oasPath: resolvePath(oasPath),
|
|
166
|
+
configPath: resolvePath(configPath),
|
|
142
167
|
};
|
|
143
168
|
}
|
|
144
169
|
|
|
170
|
+
export type ApiKeySource = 'explicit-config' | 'environment-variable' | 'cli';
|
|
171
|
+
|
|
172
|
+
export type LoadedApiKey = {
|
|
173
|
+
value: string;
|
|
174
|
+
source: ApiKeySource;
|
|
175
|
+
};
|
|
176
|
+
|
|
145
177
|
export type SpecRetrieverConfig =
|
|
146
178
|
| {
|
|
147
179
|
kind: 'external_spec_server';
|
|
@@ -152,16 +184,63 @@ export type SpecRetrieverConfig =
|
|
|
152
184
|
kind: 'local_spec_server_with_files';
|
|
153
185
|
stainlessProject: string;
|
|
154
186
|
devPaths: InputFilePaths;
|
|
155
|
-
apiKey:
|
|
187
|
+
apiKey: LoadedApiKey | null;
|
|
156
188
|
version: VersionUserConfig;
|
|
157
189
|
}
|
|
158
190
|
| {
|
|
159
191
|
kind: 'local_spec_server_with_remote_files';
|
|
160
192
|
stainlessProject: string;
|
|
161
|
-
apiKey:
|
|
193
|
+
apiKey: LoadedApiKey;
|
|
162
194
|
version: VersionUserConfig;
|
|
163
195
|
};
|
|
164
196
|
|
|
197
|
+
function parseAuthJson(authJsonStr: string) {
|
|
198
|
+
let json: unknown;
|
|
199
|
+
try {
|
|
200
|
+
json = JSON.parse(authJsonStr);
|
|
201
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
202
|
+
} catch (_error) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (typeof json !== 'object' || json === null) {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
if (!('access_token' in json)) {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
const accessToken = json['access_token'];
|
|
213
|
+
if (typeof accessToken !== 'string') {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
return accessToken;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function loadApiKey(configValue: string | undefined): LoadedApiKey | null {
|
|
220
|
+
if (typeof configValue === 'string') {
|
|
221
|
+
return { value: configValue, source: 'explicit-config' };
|
|
222
|
+
}
|
|
223
|
+
if (process.env.STAINLESS_API_KEY) {
|
|
224
|
+
return { value: process.env.STAINLESS_API_KEY, source: 'environment-variable' };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const homeDirPath = homedir();
|
|
228
|
+
|
|
229
|
+
const authJsonPath = path.join(homeDirPath, '.config', 'stainless', 'auth.json');
|
|
230
|
+
|
|
231
|
+
if (!existsSync(authJsonPath)) {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const authJsonStr = readFileSync(authJsonPath, 'utf-8');
|
|
236
|
+
const accessToken = parseAuthJson(authJsonStr);
|
|
237
|
+
if (!accessToken) {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return { value: accessToken, source: 'cli' };
|
|
242
|
+
}
|
|
243
|
+
|
|
165
244
|
function normalizeConfig(partial: SomeStainlessStarlightUserConfig, command: AstroCommand) {
|
|
166
245
|
const configWithDefaults = {
|
|
167
246
|
basePath: partial.basePath ?? '/api',
|
|
@@ -190,6 +269,7 @@ function normalizeConfig(partial: SomeStainlessStarlightUserConfig, command: Ast
|
|
|
190
269
|
search: {
|
|
191
270
|
enableAISearch: partial.search?.enableAISearch ?? false,
|
|
192
271
|
},
|
|
272
|
+
contextMenu: partial.contextMenu ?? true,
|
|
193
273
|
};
|
|
194
274
|
|
|
195
275
|
function getSpecRetrieverConfig(): SpecRetrieverConfig {
|
|
@@ -205,7 +285,7 @@ function normalizeConfig(partial: SomeStainlessStarlightUserConfig, command: Ast
|
|
|
205
285
|
throw new Error('You must provide a stainlessProject when using Stainless Starlight');
|
|
206
286
|
}
|
|
207
287
|
|
|
208
|
-
const apiKey = partial.apiKey
|
|
288
|
+
const apiKey = loadApiKey(partial.apiKey);
|
|
209
289
|
|
|
210
290
|
const version = {
|
|
211
291
|
stainlessProject: partial.stainlessProject,
|
|
@@ -226,7 +306,14 @@ function normalizeConfig(partial: SomeStainlessStarlightUserConfig, command: Ast
|
|
|
226
306
|
|
|
227
307
|
if (!apiKey) {
|
|
228
308
|
throw new Error(
|
|
229
|
-
|
|
309
|
+
[
|
|
310
|
+
bold(
|
|
311
|
+
'No Stainless credentials found. Please choose one of the following options to authenticate with Stainless:',
|
|
312
|
+
),
|
|
313
|
+
'- Run `stl auth login` to authenticate via the Stainless CLI',
|
|
314
|
+
'- Provide a Stainless API key via the `STAINLESS_API_KEY` environment variable (eg. in a .env file)',
|
|
315
|
+
'- Set the `apiKey` option in the Stainless Docs config',
|
|
316
|
+
].join('\n'),
|
|
230
317
|
);
|
|
231
318
|
}
|
|
232
319
|
|