@stainless-api/docs 0.1.0-beta.15 → 0.1.0-beta.16
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 +9 -0
- package/package.json +9 -17
- package/plugin/buildAlgoliaIndex.ts +28 -3
- package/plugin/cms/server.ts +95 -52
- package/plugin/cms/sidebar-builder.ts +0 -9
- package/plugin/globalJs/ai-dropdown-options.ts +161 -0
- package/plugin/globalJs/navigation.ts +0 -22
- package/plugin/index.ts +46 -17
- package/plugin/loadPluginConfig.ts +90 -21
- package/plugin/react/Routing.tsx +13 -23
- package/plugin/routes/Docs.astro +7 -16
- package/plugin/routes/Overview.astro +7 -5
- package/plugin/vendor/preview.worker.docs.js +6357 -6132
- package/resolveSrcFile.ts +10 -0
- package/shared/getSharedLogger.ts +15 -0
- package/shared/terminalUtils.ts +3 -0
- package/stl-docs/components/AIDropdown.tsx +52 -0
- package/stl-docs/components/Head.astro +9 -0
- package/stl-docs/components/PageTitle.astro +67 -0
- package/stl-docs/components/content-panel/ContentPanel.astro +8 -35
- package/stl-docs/components/icons/chat-gpt.tsx +1 -1
- package/stl-docs/index.ts +58 -23
- package/stl-docs/loadStlDocsConfig.ts +17 -2
- package/stl-docs/proseMarkdown/proseMarkdownIntegration.ts +64 -0
- package/stl-docs/proseMarkdown/proseMarkdownMiddleware.ts +33 -0
- package/stl-docs/proseMarkdown/toMarkdown.ts +158 -0
- package/styles/page.css +31 -4
- package/virtual-module.d.ts +3 -2
- package/plugin/globalJs/ai-dropdown.ts +0 -57
- package/stl-docs/components/APIReferenceAIDropdown.tsx +0 -58
- package/stl-docs/components/content-panel/ProseAIDropdown.tsx +0 -55
- /package/stl-docs/components/{content-panel/ContentBreadcrumbs.tsx → ContentBreadcrumbs.tsx} +0 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { join } from 'path';
|
|
2
|
+
const dirName = import.meta.dirname;
|
|
3
|
+
/**
|
|
4
|
+
* Resolve a source file path relative to the stl-starlight package.
|
|
5
|
+
* @param projectPath - The relative path to the source file.
|
|
6
|
+
* @returns The absolute path to the source file.
|
|
7
|
+
*/
|
|
8
|
+
export function resolveSrcFile(...projectPath: string[]) {
|
|
9
|
+
return join(dirName, ...projectPath);
|
|
10
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { AstroIntegrationLogger } from 'astro';
|
|
2
|
+
|
|
3
|
+
let sharedLogger: AstroIntegrationLogger | null = null;
|
|
4
|
+
|
|
5
|
+
// This is probably temporary, but it's a quick way to share a logger between our many integrations
|
|
6
|
+
// we want to share a logger so they have the same "stainless" label
|
|
7
|
+
|
|
8
|
+
export function setSharedLogger(logger: AstroIntegrationLogger) {
|
|
9
|
+
sharedLogger = logger;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// a fallback is probably not required, but it's a good safeguard in case we somehow call a logger before the shared logger is set
|
|
13
|
+
export function getSharedLogger({ fallback }: { fallback: AstroIntegrationLogger }) {
|
|
14
|
+
return sharedLogger ?? fallback;
|
|
15
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { DropdownButton } from '@stainless-api/ui-primitives';
|
|
2
|
+
import { CopyIcon } from 'lucide-react';
|
|
3
|
+
import { ChatGPTIcon } from './icons/chat-gpt';
|
|
4
|
+
import { ClaudeIcon } from './icons/claude';
|
|
5
|
+
import { MarkdownIcon } from './icons/markdown';
|
|
6
|
+
import React from 'react';
|
|
7
|
+
|
|
8
|
+
import { getAIDropdownOptions, type DropdownIcon } from '../../plugin/globalJs/ai-dropdown-options';
|
|
9
|
+
|
|
10
|
+
const iconMap: { [key in DropdownIcon]: React.ReactNode } = {
|
|
11
|
+
markdown: <MarkdownIcon />,
|
|
12
|
+
copy: <CopyIcon size={16} />,
|
|
13
|
+
claude: <ClaudeIcon />,
|
|
14
|
+
chatgpt: <ChatGPTIcon />,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const { primaryAction, groups } = getAIDropdownOptions();
|
|
18
|
+
|
|
19
|
+
function ItemLabel({ label }: { label: string[] }) {
|
|
20
|
+
const isExternal = label.length > 1;
|
|
21
|
+
return (
|
|
22
|
+
<DropdownButton.MenuItemText subtle={isExternal}>
|
|
23
|
+
{label[0]}
|
|
24
|
+
{isExternal && <strong>{label[1]}</strong>}
|
|
25
|
+
</DropdownButton.MenuItemText>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function AIDropdown() {
|
|
30
|
+
return (
|
|
31
|
+
<DropdownButton data-dropdown-id="ai-dropdown-button">
|
|
32
|
+
<DropdownButton.PrimaryAction>
|
|
33
|
+
{iconMap[primaryAction.icon]}
|
|
34
|
+
<DropdownButton.PrimaryActionText>{primaryAction.label}</DropdownButton.PrimaryActionText>
|
|
35
|
+
</DropdownButton.PrimaryAction>
|
|
36
|
+
<DropdownButton.Trigger />
|
|
37
|
+
<DropdownButton.Menu>
|
|
38
|
+
{groups.map((group) => (
|
|
39
|
+
<React.Fragment key={group.reactKey}>
|
|
40
|
+
{group.options.map((option) => (
|
|
41
|
+
<DropdownButton.MenuItem value={option.id} isExternalLink key={option.id}>
|
|
42
|
+
<DropdownButton.MenuItemIcon>{iconMap[option.icon]}</DropdownButton.MenuItemIcon>
|
|
43
|
+
<ItemLabel label={option.label} />
|
|
44
|
+
</DropdownButton.MenuItem>
|
|
45
|
+
))}
|
|
46
|
+
{group.isLast ? null : <hr />}
|
|
47
|
+
</React.Fragment>
|
|
48
|
+
))}
|
|
49
|
+
</DropdownButton.Menu>
|
|
50
|
+
</DropdownButton>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { ENABLE_CONTEXT_MENU, ENABLE_PROSE_MARKDOWN_RENDERING } from 'virtual:stl-docs-virtual-module';
|
|
3
|
+
import type { StarlightRouteData } from '@astrojs/starlight/route-data';
|
|
4
|
+
import Default from '@astrojs/starlight/components/PageTitle.astro';
|
|
5
|
+
import { ContentBreadcrumbs } from './ContentBreadcrumbs';
|
|
6
|
+
import { AIDropdown } from './AIDropdown';
|
|
7
|
+
|
|
8
|
+
function sidebarHasEntry(sidebarEntry: StarlightRouteData['sidebar'], currentPath?: string) {
|
|
9
|
+
if (!currentPath) return false;
|
|
10
|
+
const normalizePath = (path: string) => {
|
|
11
|
+
return path.endsWith('/') ? path : path + '/';
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const normalizedCurrent = normalizePath(currentPath);
|
|
15
|
+
|
|
16
|
+
for (const entry of sidebarEntry) {
|
|
17
|
+
if (entry.type === 'link') {
|
|
18
|
+
const normalizedHref = normalizePath(entry.href);
|
|
19
|
+
if (normalizedHref === normalizedCurrent) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
} else if (entry.type === 'group') {
|
|
23
|
+
const hasInGroup = sidebarHasEntry(entry.entries, currentPath);
|
|
24
|
+
if (hasInGroup) return true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// @ts-expect-error - _stlStarlightPage isn't typed
|
|
32
|
+
const skipRenderingStarlightTitle = (Astro.locals._stlStarlightPage?.skipRenderingStarlightTitle ??
|
|
33
|
+
false) as boolean;
|
|
34
|
+
|
|
35
|
+
// @ts-expect-error - _stlStarlightPage isn't typed
|
|
36
|
+
const hasMarkdownRoute = (Astro.locals._stlStarlightPage?.hasMarkdownRoute ?? true) as boolean;
|
|
37
|
+
// ⬆️ hopefully this is temporary. But for now, we don't have a markdown representation of the API reference overview page
|
|
38
|
+
|
|
39
|
+
const currentPath = Astro.url.pathname;
|
|
40
|
+
|
|
41
|
+
function shouldShowAIDropdown() {
|
|
42
|
+
// Hide if there is no associated sidebar entry. This applies to pages like the 404 page.
|
|
43
|
+
const hasSidebarEntry = sidebarHasEntry(Astro.locals.starlightRoute.sidebar, currentPath);
|
|
44
|
+
if (!hasSidebarEntry) return false;
|
|
45
|
+
|
|
46
|
+
if (!hasMarkdownRoute) return false;
|
|
47
|
+
|
|
48
|
+
if (!ENABLE_PROSE_MARKDOWN_RENDERING) return false;
|
|
49
|
+
if (!ENABLE_CONTEXT_MENU) return false;
|
|
50
|
+
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const showAIDropdown = shouldShowAIDropdown();
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
{
|
|
58
|
+
skipRenderingStarlightTitle ? null : (
|
|
59
|
+
<>
|
|
60
|
+
<div class="stl-ui-not-prose stl-page-nav-container stl-prose-page-nav-container">
|
|
61
|
+
<ContentBreadcrumbs currentPath={currentPath} sidebarEntry={Astro.locals.starlightRoute.sidebar} />
|
|
62
|
+
{showAIDropdown && <AIDropdown />}
|
|
63
|
+
</div>
|
|
64
|
+
<Default />
|
|
65
|
+
</>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
@@ -1,20 +1,9 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
import { ProseAIDropdown } from './ProseAIDropdown';
|
|
4
|
-
import { INCLUDE_AI_DROPDOWN_OPTIONS } from 'virtual:stl-docs-virtual-module';
|
|
5
|
-
const currentPath = Astro.url.pathname;
|
|
2
|
+
|
|
6
3
|
---
|
|
7
4
|
|
|
8
5
|
<div class="content-panel">
|
|
9
6
|
<div class="sl-container stl-ui-prose">
|
|
10
|
-
<div class="stl-ui-not-prose stl-page-nav-container stl-prose-page-nav-container">
|
|
11
|
-
<ContentBreadcrumbs currentPath={currentPath} sidebarEntry={Astro.locals.starlightRoute.sidebar} />
|
|
12
|
-
{
|
|
13
|
-
INCLUDE_AI_DROPDOWN_OPTIONS && (
|
|
14
|
-
<ProseAIDropdown currentPath={currentPath} sidebarEntry={Astro.locals.starlightRoute.sidebar} />
|
|
15
|
-
)
|
|
16
|
-
}
|
|
17
|
-
</div>
|
|
18
7
|
<slot />
|
|
19
8
|
</div>
|
|
20
9
|
</div>
|
|
@@ -38,32 +27,16 @@ const currentPath = Astro.url.pathname;
|
|
|
38
27
|
}
|
|
39
28
|
}
|
|
40
29
|
.content-panel {
|
|
41
|
-
padding: 0
|
|
30
|
+
padding: 0 var(--sl-content-pad-x) 0 var(--sl-content-pad-x);
|
|
42
31
|
}
|
|
43
32
|
|
|
44
|
-
|
|
45
|
-
|
|
33
|
+
@media (min-width: 50rem) {
|
|
34
|
+
.content-panel {
|
|
35
|
+
padding: 0 0 0 var(--sl-content-pad-x);
|
|
36
|
+
}
|
|
46
37
|
}
|
|
47
|
-
</style>
|
|
48
38
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
display: none;
|
|
39
|
+
.stl-prose-page-nav-container {
|
|
40
|
+
padding: 1rem 0 0;
|
|
52
41
|
}
|
|
53
42
|
</style>
|
|
54
|
-
|
|
55
|
-
<script>
|
|
56
|
-
import { initDropdownButton } from '@stainless-api/ui-primitives/scripts';
|
|
57
|
-
import { getPageLoadEvent } from '../../../plugin/helpers/getPageLoadEvent';
|
|
58
|
-
import { onSelectAIOption } from '../../../plugin/globalJs/ai-dropdown.ts';
|
|
59
|
-
|
|
60
|
-
document.addEventListener(getPageLoadEvent(), () => {
|
|
61
|
-
initDropdownButton({
|
|
62
|
-
dropdownId: 'prose-ai-dropdown-button',
|
|
63
|
-
onSelect: (option: string) => onSelectAIOption(option, false),
|
|
64
|
-
onPrimaryAction: () => {
|
|
65
|
-
onSelectAIOption('claude', false);
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
</script>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export function ChatGPTIcon() {
|
|
2
2
|
return (
|
|
3
3
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
|
|
4
|
-
<g
|
|
4
|
+
<g clipPath="url(#clip0_2771_126147)">
|
|
5
5
|
<path
|
|
6
6
|
d="M14.853 6.54857C15.216 5.45907 15.091 4.26557 14.5105 3.27457C13.6375 1.75457 11.8825 0.972574 10.1685 1.34057C9.40596 0.481574 8.31046 -0.00692579 7.16196 7.42116e-05C5.40996 -0.00392579 3.85546 1.12407 3.31646 2.79107C2.19096 3.02157 1.21946 3.72607 0.650955 4.72457C-0.228545 6.24057 -0.0280446 8.15157 1.14696 9.45157C0.783955 10.5411 0.908955 11.7346 1.48946 12.7256C2.36246 14.2456 4.11746 15.0276 5.83146 14.6596C6.59346 15.5186 7.68946 16.0071 8.83796 15.9996C10.591 16.0041 12.146 14.8751 12.685 13.2066C13.8105 12.9761 14.782 12.2716 15.3505 11.2731C16.229 9.75707 16.028 7.84757 14.8535 6.54757L14.853 6.54857ZM8.83896 14.9541C8.13746 14.9551 7.45796 14.7096 6.91946 14.2601C6.94396 14.2471 6.98646 14.2236 7.01396 14.2066L10.2 12.3666C10.363 12.2741 10.463 12.1006 10.462 11.9131V7.42157L11.8085 8.19907C11.823 8.20607 11.8325 8.22007 11.8345 8.23607V11.9556C11.8325 13.6096 10.493 14.9506 8.83896 14.9541ZM2.39696 12.2026C2.04546 11.5956 1.91896 10.8841 2.03946 10.1936C2.06296 10.2076 2.10446 10.2331 2.13396 10.2501L5.31996 12.0901C5.48146 12.1846 5.68146 12.1846 5.84346 12.0901L9.73296 9.84407V11.3991C9.73396 11.4151 9.72646 11.4306 9.71396 11.4406L6.49346 13.3001C5.05896 14.1261 3.22696 13.6351 2.39746 12.2026H2.39696ZM1.55846 5.24807C1.90846 4.64007 2.46096 4.17507 3.11896 3.93357C3.11896 3.96107 3.11746 4.00957 3.11746 4.04357V7.72407C3.11646 7.91107 3.21646 8.08457 3.37896 8.17707L7.26846 10.4226L5.92196 11.2001C5.90846 11.2091 5.89146 11.2106 5.87646 11.2041L2.65546 9.34307C1.22396 8.51407 0.732955 6.68257 1.55796 5.24857L1.55846 5.24807ZM12.6215 7.82257L8.73196 5.57657L10.0785 4.79957C10.092 4.79057 10.109 4.78907 10.124 4.79557L13.345 6.65507C14.779 7.48357 15.2705 9.31807 14.442 10.7521C14.0915 11.3591 13.5395 11.8241 12.882 12.0661V8.27557C12.8835 8.08857 12.784 7.91557 12.622 7.82257H12.6215ZM13.9615 5.80557C13.938 5.79107 13.8965 5.76607 13.867 5.74907L10.681 3.90907C10.5195 3.81457 10.3195 3.81457 10.1575 3.90907L6.26796 6.15507V4.60007C6.26696 4.58407 6.27446 4.56857 6.28696 4.55857L9.50746 2.70057C10.942 1.87307 12.776 2.36557 13.603 3.80057C13.9525 4.40657 14.079 5.11607 13.9605 5.80557H13.9615ZM5.53596 8.57707L4.18896 7.79957C4.17446 7.79257 4.16496 7.77857 4.16296 7.76257V4.04307C4.16396 2.38707 5.50746 1.04507 7.16346 1.04607C7.86396 1.04607 8.54196 1.29207 9.08046 1.74007C9.05596 1.75307 9.01396 1.77657 8.98596 1.79357L5.79996 3.63357C5.63696 3.72607 5.53696 3.89907 5.53796 4.08657L5.53596 8.57607V8.57707ZM6.26746 7.00007L7.99996 5.99957L9.73246 6.99957V9.00007L7.99996 10.0001L6.26746 9.00007V7.00007Z"
|
|
7
7
|
fill="var(--stl-ui-foreground)"
|
package/stl-docs/index.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { disableCalloutSyntaxStarlightPlugin } from './disableCalloutSyntax';
|
|
|
7
7
|
import type { AstroIntegration } from 'astro';
|
|
8
8
|
|
|
9
9
|
import { normalizeRedirects, type NormalizedRedirectConfig } from './redirects';
|
|
10
|
-
import { join } from 'path';
|
|
10
|
+
import path, { join } from 'path';
|
|
11
11
|
import { mkdirSync, writeFileSync } from 'fs';
|
|
12
12
|
import {
|
|
13
13
|
parseStlDocsConfig,
|
|
@@ -18,11 +18,15 @@ import {
|
|
|
18
18
|
type StarlightSidebarConfig,
|
|
19
19
|
} from './loadStlDocsConfig';
|
|
20
20
|
import { buildVirtualModuleString } from '../shared/virtualModule';
|
|
21
|
-
|
|
21
|
+
import { resolveSrcFile } from '../resolveSrcFile';
|
|
22
22
|
import geistPath from '../plugin/assets/fonts/geist/geist-latin.woff2?url';
|
|
23
|
+
import { stainlessDocsMarkdownRenderer } from './proseMarkdown/proseMarkdownIntegration';
|
|
24
|
+
import { setSharedLogger } from '../shared/getSharedLogger';
|
|
23
25
|
|
|
24
26
|
export * from '../plugin';
|
|
25
27
|
|
|
28
|
+
const COMPONENTS_FOLDER = '/stl-docs/components';
|
|
29
|
+
|
|
26
30
|
function stainlessDocsStarlightIntegration(config: NormalizedStainlessDocsConfig) {
|
|
27
31
|
// We transform our tabs into a Starlight sidebar
|
|
28
32
|
// This gives them all the built-in features of Starlight (eg. auto-generated entries by directory)
|
|
@@ -43,26 +47,27 @@ function stainlessDocsStarlightIntegration(config: NormalizedStainlessDocsConfig
|
|
|
43
47
|
|
|
44
48
|
type ComponentOverrides = StarlightConfigDefined['components'];
|
|
45
49
|
const componentOverrides: ComponentOverrides = {
|
|
46
|
-
Sidebar: '
|
|
47
|
-
Header: '
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
Sidebar: '@stainless-api/docs/SDKSelectSidebar',
|
|
54
|
-
Search: '@stainless-api/docs/Search',
|
|
55
|
-
}),
|
|
50
|
+
Sidebar: resolveSrcFile(COMPONENTS_FOLDER, './sidebars/BaseSidebar.astro'),
|
|
51
|
+
Header: resolveSrcFile(COMPONENTS_FOLDER, './Header.astro'),
|
|
52
|
+
Head: resolveSrcFile(COMPONENTS_FOLDER, './Head.astro'),
|
|
53
|
+
ThemeSelect: resolveSrcFile(COMPONENTS_FOLDER, './ThemeSelect.astro'),
|
|
54
|
+
ContentPanel: resolveSrcFile(COMPONENTS_FOLDER, './content-panel/ContentPanel.astro'),
|
|
55
|
+
TableOfContents: resolveSrcFile(COMPONENTS_FOLDER, './TableOfContents.astro'),
|
|
56
|
+
PageTitle: resolveSrcFile(COMPONENTS_FOLDER, './PageTitle.astro'),
|
|
56
57
|
};
|
|
57
58
|
|
|
58
59
|
const plugins: StarlightPlugin[] = [
|
|
59
60
|
// Disable starlight callout syntax in favor of our own component
|
|
60
61
|
disableCalloutSyntaxStarlightPlugin,
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
if (config.apiReference !== null) {
|
|
65
|
+
plugins.push(stainlessStarlight(config.apiReference));
|
|
66
|
+
componentOverrides.Sidebar = resolveSrcFile(COMPONENTS_FOLDER, './sidebars/SDKSelectSidebar.astro');
|
|
67
|
+
componentOverrides.Search = resolveSrcFile('/plugin/components/search/Search.astro');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
plugins.push(...config.starlightCompat.plugins);
|
|
66
71
|
|
|
67
72
|
// TODO: re-add once we figure out what to do with the client router
|
|
68
73
|
// if (config.enableClientRouter) {
|
|
@@ -108,20 +113,26 @@ function stainlessDocsStarlightIntegration(config: NormalizedStainlessDocsConfig
|
|
|
108
113
|
},
|
|
109
114
|
},
|
|
110
115
|
],
|
|
111
|
-
routeMiddleware: [
|
|
112
|
-
|
|
116
|
+
routeMiddleware: [
|
|
117
|
+
...config.starlightCompat.routeMiddleware,
|
|
118
|
+
resolveSrcFile('/stl-docs/tabsMiddleware.ts'),
|
|
119
|
+
],
|
|
120
|
+
customCss: [resolveSrcFile('/theme.css'), ...config.customCss],
|
|
113
121
|
plugins,
|
|
114
122
|
});
|
|
115
123
|
}
|
|
116
124
|
|
|
117
|
-
function stainlessDocsIntegration(
|
|
125
|
+
function stainlessDocsIntegration(
|
|
126
|
+
config: NormalizedStainlessDocsConfig,
|
|
127
|
+
apiReferenceBasePath: string | null,
|
|
128
|
+
): AstroIntegration {
|
|
118
129
|
const virtualId = `virtual:stl-docs-virtual-module`;
|
|
119
130
|
// The '\0' prefix tells Vite “this is a virtual module” and prevents it from being resolved again.
|
|
120
131
|
const resolvedId = `\0${virtualId}`;
|
|
121
132
|
let redirects: NormalizedRedirectConfig | null = null;
|
|
122
133
|
|
|
123
134
|
return {
|
|
124
|
-
name: 'stl-docs-
|
|
135
|
+
name: 'stl-docs-astro',
|
|
125
136
|
hooks: {
|
|
126
137
|
'astro:config:setup': ({ updateConfig, command, config: astroConfig }) => {
|
|
127
138
|
// // we only handle redirects for builds
|
|
@@ -152,7 +163,9 @@ function stainlessDocsIntegration(config: NormalizedStainlessDocsConfig): AstroI
|
|
|
152
163
|
HEADER_LINKS: config.header.links,
|
|
153
164
|
HEADER_LAYOUT: config.header.layout,
|
|
154
165
|
ENABLE_CLIENT_ROUTER: config.enableClientRouter,
|
|
155
|
-
|
|
166
|
+
API_REFERENCE_BASE_PATH: apiReferenceBasePath,
|
|
167
|
+
ENABLE_PROSE_MARKDOWN_RENDERING: config.enableProseMarkdownRendering,
|
|
168
|
+
ENABLE_CONTEXT_MENU: config.contextMenu,
|
|
156
169
|
});
|
|
157
170
|
}
|
|
158
171
|
},
|
|
@@ -179,6 +192,17 @@ function stainlessDocsIntegration(config: NormalizedStainlessDocsConfig): AstroI
|
|
|
179
192
|
};
|
|
180
193
|
}
|
|
181
194
|
|
|
195
|
+
function sharedLoggerIntegration(): AstroIntegration {
|
|
196
|
+
return {
|
|
197
|
+
name: 'stainless',
|
|
198
|
+
hooks: {
|
|
199
|
+
'astro:config:setup': ({ logger }) => {
|
|
200
|
+
setSharedLogger(logger);
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
182
206
|
export function stainlessDocs(config: StainlessDocsUserConfig) {
|
|
183
207
|
const normalizedConfigResult = parseStlDocsConfig(config);
|
|
184
208
|
if (normalizedConfigResult.result === 'error') {
|
|
@@ -188,9 +212,20 @@ export function stainlessDocs(config: StainlessDocsUserConfig) {
|
|
|
188
212
|
}
|
|
189
213
|
const normalizedConfig = normalizedConfigResult.config;
|
|
190
214
|
|
|
215
|
+
// TODO: need to refactor this, but this allows us to get the base path for the API reference _if_ it exists
|
|
216
|
+
// if it doesn't exist, the value of basePath is null.
|
|
217
|
+
// the stl-starlight virtual module has base path, but it's not available when there's no API reference
|
|
218
|
+
const hasApiReference = normalizedConfig.apiReference !== null;
|
|
219
|
+
let apiReferenceBasePath: string | null = null;
|
|
220
|
+
if (hasApiReference) {
|
|
221
|
+
apiReferenceBasePath = normalizedConfig.apiReference?.basePath ?? '/api';
|
|
222
|
+
}
|
|
223
|
+
|
|
191
224
|
return [
|
|
225
|
+
sharedLoggerIntegration(), // this **must** be first so it can set the shared logger used by our other integrations
|
|
192
226
|
react(),
|
|
193
227
|
stainlessDocsStarlightIntegration(normalizedConfig),
|
|
194
|
-
stainlessDocsIntegration(normalizedConfig),
|
|
228
|
+
stainlessDocsIntegration(normalizedConfig, apiReferenceBasePath),
|
|
229
|
+
stainlessDocsMarkdownRenderer({ enabled: normalizedConfig.enableProseMarkdownRendering }),
|
|
195
230
|
];
|
|
196
231
|
}
|
|
@@ -45,7 +45,8 @@ type Tabs = {
|
|
|
45
45
|
sidebar?: SidebarEntry[];
|
|
46
46
|
/**
|
|
47
47
|
* Whether to hide the tab in the tab bar.
|
|
48
|
-
*
|
|
48
|
+
*
|
|
49
|
+
* Default: `false`
|
|
49
50
|
*/
|
|
50
51
|
hidden?: boolean;
|
|
51
52
|
}[];
|
|
@@ -67,7 +68,19 @@ export type StainlessDocsUserConfig = {
|
|
|
67
68
|
experimental?: {
|
|
68
69
|
starlightCompat?: ExperimentalStarlightCompatibilityConfig;
|
|
69
70
|
enableClientRouter?: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Disable markdown rendering for prose content. Only disable this if it is causing issues.
|
|
73
|
+
*
|
|
74
|
+
* Default: `false`
|
|
75
|
+
*/
|
|
76
|
+
disableProseMarkdownRendering?: boolean;
|
|
70
77
|
};
|
|
78
|
+
/**
|
|
79
|
+
* Whether to show the context menu with options like "Copy as Markdown" and "Open in ChatGPT".
|
|
80
|
+
*
|
|
81
|
+
* Default: `true`
|
|
82
|
+
*/
|
|
83
|
+
contextMenu?: boolean;
|
|
71
84
|
} & PassThroughStarlightConfigOptions;
|
|
72
85
|
|
|
73
86
|
type HeaderLayout = 'default' | 'stacked';
|
|
@@ -134,7 +147,9 @@ function normalizeConfig(userConfig: StainlessDocsUserConfig) {
|
|
|
134
147
|
enableClientRouter: userConfig.experimental?.enableClientRouter ?? false,
|
|
135
148
|
apiReference: userConfig.apiReference ?? null,
|
|
136
149
|
sidebar: userConfig.sidebar,
|
|
137
|
-
|
|
150
|
+
enableProseMarkdownRendering:
|
|
151
|
+
userConfig.experimental?.disableProseMarkdownRendering === true ? false : true,
|
|
152
|
+
contextMenu: userConfig.contextMenu ?? true,
|
|
138
153
|
};
|
|
139
154
|
|
|
140
155
|
return configWithDefaults;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { AstroIntegration } from 'astro';
|
|
2
|
+
import { readFile, writeFile } from 'fs/promises';
|
|
3
|
+
import { toMarkdown } from './toMarkdown';
|
|
4
|
+
import { resolveSrcFile } from '../../resolveSrcFile';
|
|
5
|
+
import { getSharedLogger } from '../../shared/getSharedLogger';
|
|
6
|
+
import { bold } from '../../shared/terminalUtils';
|
|
7
|
+
|
|
8
|
+
export function stainlessDocsMarkdownRenderer({ enabled }: { enabled: boolean }): AstroIntegration {
|
|
9
|
+
return {
|
|
10
|
+
name: 'stl-docs-md',
|
|
11
|
+
hooks: {
|
|
12
|
+
'astro:config:setup': ({ addMiddleware }) => {
|
|
13
|
+
if (enabled) {
|
|
14
|
+
addMiddleware({
|
|
15
|
+
entrypoint: resolveSrcFile('/stl-docs/proseMarkdown/proseMarkdownMiddleware.ts'),
|
|
16
|
+
order: 'post',
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
'astro:build:done': async ({ assets, logger: localLogger, dir }) => {
|
|
21
|
+
const logger = getSharedLogger({ fallback: localLogger });
|
|
22
|
+
if (!enabled) {
|
|
23
|
+
logger.info('Stainless Docs prose Markdown rendering is disabled, skipping...');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const starlightPagePatterns = ['/[...slug]'];
|
|
27
|
+
const pagesToRender = Array.from(assets.entries())
|
|
28
|
+
.filter(([k]) => {
|
|
29
|
+
if (starlightPagePatterns.includes(k)) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
})
|
|
34
|
+
.map(([, v]) => v)
|
|
35
|
+
.flat()
|
|
36
|
+
.map((v) => v.pathname);
|
|
37
|
+
|
|
38
|
+
logger.info(bold(`Building ${pagesToRender.length} Markdown pages for prose content`));
|
|
39
|
+
|
|
40
|
+
const outputBasePath = dir.pathname;
|
|
41
|
+
|
|
42
|
+
let completedCount = 0;
|
|
43
|
+
for (const absHtmlPath of pagesToRender) {
|
|
44
|
+
const txt = await readFile(absHtmlPath, 'utf-8');
|
|
45
|
+
const md = await toMarkdown(txt);
|
|
46
|
+
if (md) {
|
|
47
|
+
const absMdPath = absHtmlPath.replace('.html', '.md');
|
|
48
|
+
await writeFile(absMdPath, md, 'utf-8');
|
|
49
|
+
|
|
50
|
+
completedCount++;
|
|
51
|
+
|
|
52
|
+
const relHtmlPath = absHtmlPath.replace(outputBasePath, '');
|
|
53
|
+
const relMdPath = absMdPath.replace(outputBasePath, '');
|
|
54
|
+
|
|
55
|
+
logger.info(`(${completedCount}/${pagesToRender.length}) ${relHtmlPath} -> ${relMdPath}`);
|
|
56
|
+
} else {
|
|
57
|
+
logger.error(`Failed to render ${absHtmlPath} as Markdown`);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { defineMiddleware } from 'astro:middleware';
|
|
2
|
+
import { toMarkdown } from './toMarkdown';
|
|
3
|
+
import { API_REFERENCE_BASE_PATH } from 'virtual:stl-docs-virtual-module';
|
|
4
|
+
|
|
5
|
+
// this is only run in `astro dev` for rendering prose content as Markdown on the fly.
|
|
6
|
+
export const onRequest = defineMiddleware(async (context, next) => {
|
|
7
|
+
if (!import.meta.env.DEV) {
|
|
8
|
+
return next();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (API_REFERENCE_BASE_PATH && context.url.pathname.startsWith(API_REFERENCE_BASE_PATH)) {
|
|
12
|
+
// handled by the API reference API route in stl-starlight plugin
|
|
13
|
+
return next();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (!context.url.pathname.endsWith('/index.md')) {
|
|
17
|
+
return next();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const htmlUrl = new URL(context.url.pathname.replace('index.md', ''), context.url);
|
|
21
|
+
const resp = await fetch(htmlUrl);
|
|
22
|
+
if (!resp.ok) {
|
|
23
|
+
return new Response('Failed to fetch HTML', { status: 400 });
|
|
24
|
+
}
|
|
25
|
+
const html = await resp.text();
|
|
26
|
+
const md = await toMarkdown(html);
|
|
27
|
+
|
|
28
|
+
if (!md) {
|
|
29
|
+
return new Response('Failed to render Markdown', { status: 400 });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return new Response(md, { status: 200 });
|
|
33
|
+
});
|