@stainless-api/docs 0.1.0-beta.88 → 0.1.0-beta.89

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 CHANGED
@@ -1,5 +1,21 @@
1
1
  # @stainless-api/docs
2
2
 
3
+ ## 0.1.0-beta.89
4
+
5
+ ### Minor Changes
6
+
7
+ - 4b2bd1c: Adds Webhook events overview page
8
+
9
+ ### Patch Changes
10
+
11
+ - 7bda782: fix bug when detecting current sidebar item
12
+ - de83f02: fix api reference base path
13
+ - 31457f3: support for setting custom fonts
14
+ - Updated dependencies [31457f3]
15
+ - @stainless-api/ui-primitives@0.1.0-beta.47
16
+ - @stainless-api/docs-search@0.1.0-beta.20
17
+ - @stainless-api/docs-ui@0.1.0-beta.67
18
+
3
19
  ## 0.1.0-beta.88
4
20
 
5
21
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stainless-api/docs",
3
- "version": "0.1.0-beta.88",
3
+ "version": "0.1.0-beta.89",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -14,7 +14,6 @@
14
14
  "./plugin/languages": "./plugin/languages.ts",
15
15
  "./stainless-docs/mintlify-compat": "./stl-docs/components/mintlify-compat/index.ts",
16
16
  "./mintlify-compat.css": "./styles/mintlify-compat.css",
17
- "./font-imports": "./styles/fonts.css",
18
17
  "./components": "./stl-docs/components/index.ts",
19
18
  "./components/scripts": "./stl-docs/components/scripts.ts",
20
19
  "./docs-config": "./stl-docs/loadStlDocsConfig.ts",
@@ -57,9 +56,9 @@
57
56
  "vite-plugin-prebundle-workers": "^0.2.0",
58
57
  "web-worker": "^1.5.0",
59
58
  "yaml": "^2.8.2",
60
- "@stainless-api/docs-search": "0.1.0-beta.19",
61
- "@stainless-api/docs-ui": "0.1.0-beta.66",
62
- "@stainless-api/ui-primitives": "0.1.0-beta.46"
59
+ "@stainless-api/docs-search": "0.1.0-beta.20",
60
+ "@stainless-api/docs-ui": "0.1.0-beta.67",
61
+ "@stainless-api/ui-primitives": "0.1.0-beta.47"
63
62
  },
64
63
  "devDependencies": {
65
64
  "@astrojs/check": "^0.9.6",
@@ -1,14 +1,19 @@
1
1
  ---
2
2
  import { parseRoute } from '@stainless-api/docs-ui/routing';
3
- import { BASE_PATH, DEFAULT_LANGUAGE, EXCLUDE_LANGUAGES } from 'virtual:stl-starlight-virtual-module';
3
+ import {
4
+ RESOLVED_API_REFERENCE_PATH,
5
+ DEFAULT_LANGUAGE,
6
+ EXCLUDE_LANGUAGES,
7
+ } from 'virtual:stl-starlight-virtual-module';
4
8
  import { Languages } from '../languages';
5
9
  import { SDKSelectReactComponent } from '../react/Routing';
6
10
  import { getSDKJSONInSSR } from '../specs/fetchSpecSSR';
7
11
  import { getDocsLanguages } from '../helpers/getDocsLanguages';
12
+ import { API_REFERENCE_BASE_PATH } from 'virtual:stl-docs-virtual-module';
8
13
 
9
14
  const slug = `/${Astro.locals.starlightRoute.id}`;
10
15
 
11
- const basePath = BASE_PATH;
16
+ const basePath = API_REFERENCE_BASE_PATH;
12
17
  const defaultLanguage = DEFAULT_LANGUAGE;
13
18
  const { language, stainlessPath } = parseRoute(basePath, slug);
14
19
 
@@ -27,7 +32,8 @@ const options = getDocsLanguages(spec, EXCLUDE_LANGUAGES).map((value) => ({
27
32
  selected: data.language === value,
28
33
  }));
29
34
 
30
- const readmeSlug = language === 'http' ? BASE_PATH : `${BASE_PATH}/${language}`;
35
+ const readmeSlug =
36
+ language === 'http' ? RESOLVED_API_REFERENCE_PATH : `${RESOLVED_API_REFERENCE_PATH}/${language}`;
31
37
  ---
32
38
 
33
39
  <span
@@ -52,7 +58,7 @@ const readmeSlug = language === 'http' ? BASE_PATH : `${BASE_PATH}/${language}`;
52
58
  import { navigate } from 'astro:transitions/client';
53
59
  import { updateSelectedLanguage } from '../languages';
54
60
  import { initDropdown } from '@stainless-api/docs/components/scripts';
55
- import { BASE_PATH } from 'virtual:stl-starlight-virtual-module';
61
+ import { RESOLVED_API_REFERENCE_PATH } from 'virtual:stl-starlight-virtual-module';
56
62
  import { getPageLoadEvent } from '../helpers/getPageLoadEvent';
57
63
 
58
64
  document.addEventListener(getPageLoadEvent(), () => {
@@ -62,7 +68,7 @@ const readmeSlug = language === 'http' ? BASE_PATH : `${BASE_PATH}/${language}`;
62
68
  root: sdkSelect,
63
69
  onSelect: (value) => {
64
70
  const originalLanguage = sdkSelect.dataset.currentValue;
65
- navigate(updateSelectedLanguage(BASE_PATH, originalLanguage, value));
71
+ navigate(updateSelectedLanguage(RESOLVED_API_REFERENCE_PATH, originalLanguage, value));
66
72
  },
67
73
  });
68
74
  });
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { BASE_PATH, HIGHLIGHT_THEMES } from 'virtual:stl-starlight-virtual-module';
2
+ import { RESOLVED_API_REFERENCE_PATH, HIGHLIGHT_THEMES } from 'virtual:stl-starlight-virtual-module';
3
3
  import { parseRoute, generateRoute } from '@stainless-api/docs-ui/routing';
4
4
  import { SearchModal } from '@stainless-api/docs-search';
5
5
  import * as Markdoc from '@markdoc/markdoc';
@@ -63,7 +63,7 @@ async function createMarkdownRenderer(): Promise<MarkdownContext> {
63
63
  transform(node, config) {
64
64
  const children = node.transformChildren(config);
65
65
  const attrs = node.transformAttributes(config);
66
- const href = attrs['href'].replace('docs://BASE_PATH', BASE_PATH);
66
+ const href = attrs['href'].replace('docs://BASE_PATH', RESOLVED_API_REFERENCE_PATH);
67
67
  return new Markdoc.Tag(this.render, { ...attrs, href }, children);
68
68
  },
69
69
  },
@@ -87,7 +87,7 @@ async function createMarkdownRenderer(): Promise<MarkdownContext> {
87
87
 
88
88
  export function DocsSearch({ settings, currentPath }: { settings: SearchSettings; currentPath: string }) {
89
89
  const markdownRenderer = React.use(createMarkdownRenderer());
90
- const { stainlessPath, language } = parseRoute(BASE_PATH, currentPath);
90
+ const { stainlessPath, language } = parseRoute(RESOLVED_API_REFERENCE_PATH, currentPath);
91
91
  // eslint-disable-next-line turbo/no-undeclared-env-vars
92
92
  const pageFind = import.meta.env.DEV
93
93
  ? undefined
@@ -96,7 +96,7 @@ export function DocsSearch({ settings, currentPath }: { settings: SearchSettings
96
96
  function handleSelect(selectedPath: string) {
97
97
  const url = selectedPath.startsWith('/')
98
98
  ? selectedPath
99
- : generateRoute(BASE_PATH, language, selectedPath);
99
+ : generateRoute(RESOLVED_API_REFERENCE_PATH, language, selectedPath);
100
100
  if (url) window.location.href = url;
101
101
  }
102
102
 
@@ -1,4 +1,7 @@
1
- import { BASE_PATH, EXPERIMENTAL_COLLAPSIBLE_SNIPPETS } from 'virtual:stl-starlight-virtual-module';
1
+ import {
2
+ RESOLVED_API_REFERENCE_PATH,
3
+ EXPERIMENTAL_COLLAPSIBLE_SNIPPETS,
4
+ } from 'virtual:stl-starlight-virtual-module';
2
5
  import { getPageLoadEvent } from '../helpers/getPageLoadEvent';
3
6
  import { updateSelectedLanguage } from '../languages';
4
7
  import { navigate } from 'astro/virtual-modules/transitions-router.js';
@@ -90,7 +93,7 @@ function loadPlayground(playButton: HTMLElement) {
90
93
  container,
91
94
  onLanguageSelect: (value) => {
92
95
  const originalLanguage = document.getElementById('stldocs-snippet-select')?.dataset.currentValue;
93
- const path: string = updateSelectedLanguage(BASE_PATH, originalLanguage, value);
96
+ const path: string = updateSelectedLanguage(RESOLVED_API_REFERENCE_PATH, originalLanguage, value);
94
97
  navigate(path.replace(/(\?.+)?($|#)/, (_, str, end) => (str ? str + '&play' : '?play') + end));
95
98
  },
96
99
  ...playgroundData,
@@ -1,5 +1,5 @@
1
1
  import { parseRoute, scrollToPath } from '@stainless-api/docs-ui/routing';
2
- import { BASE_PATH } from 'virtual:stl-starlight-virtual-module';
2
+ import { RESOLVED_API_REFERENCE_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';
@@ -11,7 +11,7 @@ history.scrollRestoration = 'auto';
11
11
  function getStainlessPathForLocation() {
12
12
  return document.location.hash
13
13
  ? decodeURI(document.location.hash)
14
- : parseRoute(BASE_PATH, document.location.href)?.stainlessPath;
14
+ : parseRoute(RESOLVED_API_REFERENCE_PATH, document.location.href)?.stainlessPath;
15
15
  }
16
16
 
17
17
  window.addEventListener('popstate', (ev: PopStateEvent) => {
@@ -29,7 +29,7 @@ document.addEventListener(getPageLoadEvent(), () => {
29
29
  root: rootElement,
30
30
  onSelect: (value) => {
31
31
  const originalLanguage = rootElement?.dataset.currentValue;
32
- navigate(updateSelectedLanguage(BASE_PATH, originalLanguage, value));
32
+ navigate(updateSelectedLanguage(RESOLVED_API_REFERENCE_PATH, originalLanguage, value));
33
33
  },
34
34
  });
35
35
 
package/plugin/index.ts CHANGED
@@ -300,7 +300,7 @@ async function stlStarlightAstroIntegration(
300
300
  if (id === resolvedId) {
301
301
  return [
302
302
  buildVirtualModuleString({
303
- BASE_PATH: path.posix.join(astroConfig.base, pluginConfig.basePath),
303
+ RESOLVED_API_REFERENCE_PATH: path.posix.join(astroConfig.base, pluginConfig.basePath),
304
304
  EXCLUDE_LANGUAGES: pluginConfig.excludeLanguages,
305
305
  DEFAULT_LANGUAGE: pluginConfig.defaultLanguage,
306
306
  BREADCRUMB_CONFIG: pluginConfig.breadcrumbs,
@@ -43,7 +43,7 @@ import {
43
43
  import { Dropdown } from '@stainless-api/docs/components';
44
44
 
45
45
  import {
46
- BASE_PATH,
46
+ RESOLVED_API_REFERENCE_PATH,
47
47
  EXCLUDE_LANGUAGES,
48
48
  EXPAND_RESOURCES,
49
49
  HIGHLIGHT_THEMES,
@@ -282,7 +282,7 @@ export function RenderLibraries({ metadata }: { metadata: SpecMetadata }) {
282
282
  language={language}
283
283
  version={data.version || ''}
284
284
  install={data.install || ''}
285
- links={{ repo: data.repo_url || '#', docs: `${BASE_PATH}/${language}` }}
285
+ links={{ repo: data.repo_url || '#', docs: `${RESOLVED_API_REFERENCE_PATH}/${language}` }}
286
286
  />
287
287
  ))}
288
288
  </ComponentProvider>
@@ -295,7 +295,7 @@ export function RenderSpecOverview({ spec, language }: { spec: SDKJSON.Spec; lan
295
295
  return (
296
296
  <DocsProvider spec={spec} language={language ?? 'node'}>
297
297
  <ComponentProvider components={componentOverrides}>
298
- <NavigationProvider basePath={BASE_PATH}>
298
+ <NavigationProvider basePath={RESOLVED_API_REFERENCE_PATH}>
299
299
  <MarkdownProvider render={renderMarkdown} highlight={highlight}>
300
300
  <div className={style.Overview}>
301
301
  {resources
@@ -351,7 +351,7 @@ export function RenderSpec({
351
351
  }}
352
352
  >
353
353
  <ComponentProvider components={componentOverrides}>
354
- <NavigationProvider basePath={BASE_PATH} selectedPath={path}>
354
+ <NavigationProvider basePath={RESOLVED_API_REFERENCE_PATH} selectedPath={path}>
355
355
  <MarkdownProvider render={renderMarkdown} highlight={highlight}>
356
356
  {
357
357
  <div className="stldocs-root stl-ui-not-prose">
@@ -359,7 +359,7 @@ export function RenderSpec({
359
359
  <SDKBreadcrumbs
360
360
  spec={spec as SDKJSON.Spec}
361
361
  currentPath={currentPath}
362
- basePath={BASE_PATH}
362
+ basePath={RESOLVED_API_REFERENCE_PATH}
363
363
  config={BREADCRUMB_CONFIG}
364
364
  />
365
365
  {ENABLE_CONTEXT_MENU && <AIDropdown />}
@@ -1,6 +1,6 @@
1
1
  import { defineRouteMiddleware, StarlightRouteData } from '@astrojs/starlight/route-data';
2
2
 
3
- import { BASE_PATH } from 'virtual:stl-starlight-virtual-module';
3
+ import { RESOLVED_API_REFERENCE_PATH } from 'virtual:stl-starlight-virtual-module';
4
4
  import { getAPIReferencePlaceholderItems } from './referencePlaceholderUtils';
5
5
  import { parseRoute } from '@stainless-api/docs-ui/routing';
6
6
  import path from 'path';
@@ -10,14 +10,17 @@ import { sidebars } from 'virtual:stl-starlight-reference-sidebars';
10
10
 
11
11
  type SidebarEntry = StarlightRouteData['sidebar'][number];
12
12
 
13
+ const removeTrailingSlash = (value: string) => (value.endsWith('/') ? value.slice(0, -1) : value);
14
+
13
15
  function markCurrentItems(sidebar: SidebarEntry[], currentSlug: string) {
14
16
  // IMPORTANT: we need to clone the sidebar to avoid mutating the original sidebar
15
17
  const mutableSidebarInstance = structuredClone(sidebar);
18
+ const normalizedCurrentSlug = removeTrailingSlash(currentSlug);
16
19
 
17
20
  function recursiveMarkCurrent(entries: SidebarEntry[]) {
18
21
  for (const entry of entries) {
19
22
  if (entry.type === 'link') {
20
- entry.isCurrent = entry.href === currentSlug;
23
+ entry.isCurrent = removeTrailingSlash(entry.href) === normalizedCurrentSlug;
21
24
  if (entry.isCurrent) {
22
25
  return;
23
26
  }
@@ -38,12 +41,12 @@ export const onRequest = defineRouteMiddleware(async (context) => {
38
41
  const slug = path.posix.join(import.meta.env.BASE_URL ?? '', `/${context.locals.starlightRoute.id}`); // same as .slug but not deprecated
39
42
 
40
43
  context.locals.starlightRoute._stlStarlight = {
41
- basePath: BASE_PATH,
44
+ basePath: RESOLVED_API_REFERENCE_PATH,
42
45
  };
43
46
 
44
47
  const apiReferencePlaceholderItems = getAPIReferencePlaceholderItems(context.locals.starlightRoute.sidebar);
45
48
 
46
- const { language } = parseRoute(BASE_PATH, slug);
49
+ const { language } = parseRoute(RESOLVED_API_REFERENCE_PATH, slug);
47
50
 
48
51
  for (const item of apiReferencePlaceholderItems) {
49
52
  const entries = sidebars.find((sb) => sb.id === item.sidebarId && sb.language === language)?.entries;
@@ -166,10 +166,14 @@ export class SidebarConfigItemsBuilder {
166
166
  return null;
167
167
  }
168
168
 
169
+ private isWebhookResource(resource: SDKJSON.Resource): boolean {
170
+ return resource.stainlessPath === '(resource) webhooks';
171
+ }
172
+
169
173
  private toResourceOverviewPage(entry: SDKJSON.Resource): UserSidebarResourceOverviewPage {
170
174
  return {
171
175
  kind: 'resource_overview_page',
172
- label: 'Overview',
176
+ label: this.isWebhookResource(entry) ? 'Events' : 'Overview',
173
177
  key: entry.configRef,
174
178
  badge: undefined,
175
179
  metadata: {
@@ -199,7 +203,8 @@ export class SidebarConfigItemsBuilder {
199
203
 
200
204
  private generateResourceGroup(resource: SDKJSON.Resource, collapsed: boolean): ReferenceSidebarGroup {
201
205
  const entries: ReferenceSidebarConfigItem[] = [];
202
- if (!this.options?.excludeResourceOverviewPages) {
206
+ // even if we aren't generating resource overview pages, we want to generate them for webhooks
207
+ if (!this.options?.excludeResourceOverviewPages || this.isWebhookResource(resource)) {
203
208
  entries.push(this.toResourceOverviewPage(resource));
204
209
  }
205
210
  const methods = Object.values(resource.methods ?? {});
@@ -5,7 +5,7 @@ import { SpecWithAuth } from './generateSpec';
5
5
 
6
6
  let cachedSpecWithAuth: SpecWithAuth | null = null;
7
7
 
8
- export async function getSpecWithAuthInSSR() {
8
+ async function getSpecWithAuthInSSR() {
9
9
  if (cachedSpecWithAuth) {
10
10
  return cachedSpecWithAuth;
11
11
  }
@@ -1,15 +1,15 @@
1
1
  ---
2
+ import { Font } from 'astro:assets';
3
+ import { FONTS } from 'virtual:stl-docs-virtual-module';
2
4
  import Default from '@astrojs/starlight/components/Head.astro';
3
5
  import path from 'path';
4
- // TODO: for users who are overriding the font stack in their own styles, how can we know that and
5
- // preload their font instead of ours?
6
- import geistPath from '../../assets/fonts/geist/geist-latin.woff2';
7
6
 
8
7
  const mdPath = path.posix.join(Astro.url.pathname, 'index.md');
9
8
  ---
10
9
 
11
10
  <Default />
12
- <link rel="preload" as="font" type="font/woff2" crossorigin="anonymous" href={geistPath} />
11
+
12
+ {Object.values(FONTS).map((font) => <Font cssVariable={font.cssVariable} preload={font.preload} />)}
13
13
  <link rel="alternate" type="text/markdown" href={mdPath} />
14
14
 
15
15
  <script>
@@ -3,26 +3,42 @@ import { SidebarEntry } from '../pagination/util';
3
3
  import { ReactNode } from 'react';
4
4
  import { Badge, getHttpMethod } from '@stainless-api/ui-primitives';
5
5
  import { FunctionIcon } from '@stainless-api/ui-primitives/icons';
6
+ import { BracesIcon } from 'lucide-react';
7
+
8
+ function getIcon(entry: SidebarEntry): ReactNode | undefined {
9
+ if (entry.type !== 'link') {
10
+ return undefined;
11
+ }
12
+ const methodAttr = entry.attrs['data-stldocs-method'];
13
+ const httpMethod = getHttpMethod(methodAttr);
14
+ if (httpMethod) {
15
+ return <Badge.HTTP method={httpMethod} iconOnly size="sm" />;
16
+ }
17
+
18
+ // special handling for the webhooks resource overview page
19
+ if (entry.attrs['data-stldocs-overview'] === 'webhooks') {
20
+ return (
21
+ <Badge size="sm" icon={<BracesIcon />} intent="info">
22
+ {''}
23
+ </Badge>
24
+ );
25
+ }
26
+
27
+ // Support empty string as method to show generic "Function" badge
28
+ else if (methodAttr === '') {
29
+ return (
30
+ <Badge size="sm" icon={<FunctionIcon />} intent="info">
31
+ {''}
32
+ </Badge>
33
+ );
34
+ }
35
+ return undefined;
36
+ }
6
37
 
7
38
  export function convertAstroSidebarToStl(entries: SidebarEntry[]): StlSidebarEntry[] {
8
39
  return entries.map((entry): StlSidebarEntry => {
9
40
  if (entry.type === 'link') {
10
- let icon: ReactNode | undefined;
11
-
12
- const methodAttr = entry.attrs['data-stldocs-method'];
13
- const httpMethod = getHttpMethod(methodAttr);
14
- if (httpMethod) {
15
- icon = <Badge.HTTP method={httpMethod} iconOnly size="sm" />;
16
- }
17
- // Support empty string as method to show generic "Function" badge
18
- else if (methodAttr === '') {
19
- icon = (
20
- <Badge size="sm" icon={<FunctionIcon />} intent="info">
21
- {''}
22
- </Badge>
23
- );
24
- }
25
-
41
+ const icon = getIcon(entry);
26
42
  return {
27
43
  type: 'link',
28
44
  attrs: entry.attrs,
@@ -0,0 +1,172 @@
1
+ import type { AstroConfig } from 'astro';
2
+ import type { Defined } from './loadStlDocsConfig';
3
+ import { fontProviders } from 'astro/config';
4
+ import type { FontPreloadFilter } from 'astro:assets';
5
+
6
+ type AstroFontConfigEntry = Defined<AstroConfig['experimental']['fonts']>[number];
7
+
8
+ // Apply Omit to each member of the union while preserving union structure
9
+ export type StlDocsFontConfigEntry = (AstroFontConfigEntry extends infer T
10
+ ? T extends unknown
11
+ ? Omit<T, 'cssVariable'>
12
+ : never
13
+ : never) & { preload?: FontPreloadFilter };
14
+
15
+ export type StlDocsFontConfig = {
16
+ primary?: StlDocsFontConfigEntry;
17
+ heading?: StlDocsFontConfigEntry;
18
+ mono?: StlDocsFontConfigEntry;
19
+ };
20
+ const latinFeatureSettings = "'ss01' on, 'ss03' on, 'ss04' on, 'ss06' on, 'ss08' on";
21
+ /* prettier-ignore */
22
+ const latinUnicodeRange: readonly string[] = ['U+0000-00FF', 'U+0131', 'U+0152-0153', 'U+02BB-02BC', 'U+02C6', 'U+02DA', 'U+02DC', 'U+0304', 'U+0308', 'U+0329', 'U+2000-206F', 'U+20AC', 'U+2122', 'U+2191', 'U+2193', 'U+2212', 'U+2215', 'U+FEFF', 'U+FFFD'];
23
+ // /* prettier-ignore */
24
+ // const latinExtUnicodeRange: readonly string[] = ['U+0100-02BA', 'U+02BD-02C5', 'U+02C7-02CC', 'U+02CE-02D7', 'U+02DD-02FF', 'U+0304', 'U+0308', 'U+0329', 'U+1D00-1DBF', 'U+1E00-1E9F', 'U+1EF2-1EFF', 'U+2020', 'U+20A0-20AB', 'U+20AD-20C0', 'U+2113', 'U+2C60-2C7F', 'U+A720-A7FF'];
25
+
26
+ export function getFontRoles(fonts: StlDocsFontConfig | undefined) {
27
+ if (!fonts) {
28
+ return {};
29
+ }
30
+ const fontConfigs: {
31
+ primary?: { cssVariable: string; preload?: FontPreloadFilter };
32
+ heading?: { cssVariable: string; preload?: FontPreloadFilter };
33
+ mono?: { cssVariable: string; preload?: FontPreloadFilter };
34
+ } = {};
35
+ if (fonts.primary) {
36
+ fontConfigs['primary'] = {
37
+ cssVariable: '--stl-typography-font' as const,
38
+ preload: fonts.primary.preload ?? [{ style: 'normal' }],
39
+ };
40
+ }
41
+ if (fonts.heading) {
42
+ fontConfigs['heading'] = {
43
+ cssVariable: '--stl-typography-font-heading' as const,
44
+ preload: fonts.heading.preload ?? [{ style: 'normal' }],
45
+ };
46
+ }
47
+ if (fonts.mono) {
48
+ fontConfigs['mono'] = {
49
+ cssVariable: '--stl-typography-font-mono' as const,
50
+ preload: fonts.mono.preload ?? [{ style: 'normal' }],
51
+ };
52
+ }
53
+ return fontConfigs;
54
+ }
55
+
56
+ export function normalizeFonts(fonts: StlDocsFontConfig | undefined): StlDocsFontConfig {
57
+ const defaultPrimary: StlDocsFontConfigEntry = {
58
+ provider: fontProviders.local(),
59
+ name: 'Geist',
60
+ display: 'swap',
61
+ preload: [
62
+ {
63
+ weight: '100 900',
64
+ style: 'normal',
65
+ },
66
+ ],
67
+ options: {
68
+ variants: [
69
+ {
70
+ weight: '100 900',
71
+ style: 'normal',
72
+ src: [new URL('../assets/fonts/geist/geist-latin.woff2', import.meta.url)],
73
+ unicodeRange: latinUnicodeRange,
74
+ featureSettings: latinFeatureSettings,
75
+ },
76
+ {
77
+ weight: '100 900',
78
+ style: 'italic',
79
+ src: [new URL('../assets/fonts/geist/geist-italic-latin.woff2', import.meta.url)],
80
+ unicodeRange: latinUnicodeRange,
81
+ featureSettings: latinFeatureSettings,
82
+ },
83
+ // {
84
+ // weight: '100 900',
85
+ // style: 'normal',
86
+ // src: [new URL('../assets/fonts/geist/geist-latin-ext.woff2', import.meta.url)],
87
+ // unicodeRange: latinExtUnicodeRange,
88
+ // featureSettings: latinFeatureSettings,
89
+ // },
90
+ // {
91
+ // weight: '100 900',
92
+ // style: 'italic',
93
+ // src: [new URL('../assets/fonts/geist/geist-italic-latin-ext.woff2', import.meta.url)],
94
+ // unicodeRange: latinExtUnicodeRange,
95
+ // featureSettings: latinFeatureSettings,
96
+ // },
97
+ ],
98
+ },
99
+ };
100
+ const defaultMono: StlDocsFontConfigEntry = {
101
+ provider: fontProviders.local(),
102
+ name: 'Geist Mono',
103
+ display: 'swap',
104
+ preload: [
105
+ {
106
+ weight: '100 900',
107
+ style: 'normal',
108
+ },
109
+ ],
110
+ options: {
111
+ variants: [
112
+ {
113
+ weight: '100 900',
114
+ style: 'normal',
115
+ src: [new URL('../assets/fonts/geist/geist-mono-latin.woff2', import.meta.url)],
116
+ unicodeRange: latinUnicodeRange,
117
+ },
118
+ {
119
+ weight: '100 900',
120
+ style: 'italic',
121
+ src: [new URL('../assets/fonts/geist/geist-mono-italic-latin.woff2', import.meta.url)],
122
+ unicodeRange: latinUnicodeRange,
123
+ },
124
+ // {
125
+ // weight: '100 900',
126
+ // style: 'normal',
127
+ // src: [new URL('../assets/fonts/geist/geist-mono-latin-ext.woff2', import.meta.url)],
128
+ // unicodeRange: latinExtUnicodeRange,
129
+ // },
130
+ // {
131
+ // weight: '100 900',
132
+ // style: 'italic',
133
+ // src: [new URL('../assets/fonts/geist/geist-mono-italic-latin-ext.woff2', import.meta.url)],
134
+ // unicodeRange: latinExtUnicodeRange,
135
+ // },
136
+ ],
137
+ },
138
+ };
139
+
140
+ return {
141
+ primary: fonts?.primary ?? defaultPrimary,
142
+ heading: fonts?.heading ?? undefined,
143
+ mono: fonts?.mono ?? defaultMono,
144
+ };
145
+ }
146
+
147
+ // Normalize fonts for the Astro config
148
+ export function flattenFonts(fonts: StlDocsFontConfig | undefined): AstroFontConfigEntry[] {
149
+ if (!fonts) {
150
+ return [];
151
+ }
152
+ const fontConfigs: AstroFontConfigEntry[] = [];
153
+ if (fonts.primary) {
154
+ fontConfigs.push({
155
+ ...fonts.primary,
156
+ cssVariable: '--stl-typography-font' as const,
157
+ } as AstroFontConfigEntry);
158
+ }
159
+ if (fonts.heading) {
160
+ fontConfigs.push({
161
+ ...fonts.heading,
162
+ cssVariable: '--stl-typography-font-heading' as const,
163
+ } as AstroFontConfigEntry);
164
+ }
165
+ if (fonts.mono) {
166
+ fontConfigs.push({
167
+ ...fonts.mono,
168
+ cssVariable: '--stl-typography-font-mono' as const,
169
+ } as AstroFontConfigEntry);
170
+ }
171
+ return fontConfigs;
172
+ }
package/stl-docs/index.ts CHANGED
@@ -6,7 +6,7 @@ import { disableCalloutSyntaxStarlightPlugin } from './disableCalloutSyntax';
6
6
  import type { AstroIntegration } from 'astro';
7
7
 
8
8
  import { normalizeRedirects, type NormalizedRedirectConfig } from './redirects';
9
- import path, { join } from 'path';
9
+ import { join } from 'path';
10
10
  import { mkdirSync, writeFileSync } from 'fs';
11
11
  import {
12
12
  parseStlDocsConfig,
@@ -23,6 +23,7 @@ import { stainlessDocsMarkdownRenderer } from './proseMarkdown/proseMarkdownInte
23
23
  import { setSharedLogger } from '../shared/getSharedLogger';
24
24
  import { stainlessDocsAlgoliaProseIndexing, stainlessDocsVectorProseIndexing } from './proseSearchIndexing';
25
25
  import { stainlessStarlight } from '../plugin';
26
+ import { getFontRoles, flattenFonts } from './fonts';
26
27
 
27
28
  export * from '../plugin';
28
29
 
@@ -167,8 +168,6 @@ function stainlessDocsIntegration(
167
168
  redirects = normalizeRedirects(astroConfig.redirects);
168
169
  }
169
170
 
170
- const fullApiReferenceBasePath = path.posix.join(astroConfig.base, apiReferenceBasePath ?? '/api');
171
-
172
171
  const virtualModules = new Map(
173
172
  Object.entries({
174
173
  'virtual:stl-docs-virtual-module': buildVirtualModuleString({
@@ -177,10 +176,11 @@ function stainlessDocsIntegration(
177
176
  HEADER_LINKS: config.header.links,
178
177
  HEADER_LAYOUT: config.header.layout,
179
178
  ENABLE_CLIENT_ROUTER: config.enableClientRouter,
180
- API_REFERENCE_BASE_PATH: fullApiReferenceBasePath,
179
+ API_REFERENCE_BASE_PATH: apiReferenceBasePath ?? '/api',
181
180
  ENABLE_PROSE_MARKDOWN_RENDERING: config.enableProseMarkdownRendering,
182
181
  ENABLE_CONTEXT_MENU: config.contextMenu, // TODO: do not duplicate this between both virtual modules
183
182
  RENDER_PAGE_DESCRIPTIONS: config.renderPageDescriptions,
183
+ FONTS: getFontRoles(config.fonts),
184
184
  } satisfies typeof StlDocsVirtualModule),
185
185
 
186
186
  'virtual:stl-docs/components/AiChat.tsx': `
@@ -196,6 +196,9 @@ function stainlessDocsIntegration(
196
196
  );
197
197
 
198
198
  updateConfig({
199
+ experimental: {
200
+ fonts: flattenFonts(config.fonts),
201
+ },
199
202
  vite: {
200
203
  plugins: [
201
204
  {
@@ -3,6 +3,7 @@ import type { StarlightPlugin, StarlightUserConfig } from '@astrojs/starlight/ty
3
3
  import type { ButtonVariant } from '@stainless-api/ui-primitives';
4
4
  import type { AnchorHTMLAttributes } from 'react';
5
5
  import type starlight from '@astrojs/starlight';
6
+ import { normalizeFonts, type StlDocsFontConfig } from './fonts';
6
7
 
7
8
  type StarlightConfig = Parameters<typeof starlight>[0];
8
9
 
@@ -66,6 +67,7 @@ export type StainlessDocsUserConfig = {
66
67
  layout?: HeaderLayout;
67
68
  links?: HeaderLink[];
68
69
  };
70
+ fonts?: StlDocsFontConfig;
69
71
  experimental?: {
70
72
  starlightCompat?: ExperimentalStarlightCompatibilityConfig;
71
73
  enableClientRouter?: boolean;
@@ -139,6 +141,7 @@ function normalizeConfig(userConfig: StainlessDocsUserConfig) {
139
141
  layout: userConfig.header?.layout ?? 'default',
140
142
  links: userConfig.header?.links ?? [],
141
143
  },
144
+ fonts: normalizeFonts(userConfig.fonts),
142
145
  starlightPassThrough: {
143
146
  tableOfContents: userConfig.tableOfContents,
144
147
  titleDelimiter: userConfig.titleDelimiter,
@@ -1,6 +1,7 @@
1
1
  import { defineMiddleware } from 'astro:middleware';
2
2
  import { toMarkdown } from './toMarkdown';
3
3
  import { API_REFERENCE_BASE_PATH } from 'virtual:stl-docs-virtual-module';
4
+ import path from 'path';
4
5
 
5
6
  // this is only run in `astro dev` for rendering prose content as Markdown on the fly.
6
7
  export const onRequest = defineMiddleware(async (context, next) => {
@@ -9,7 +10,8 @@ export const onRequest = defineMiddleware(async (context, next) => {
9
10
  return next();
10
11
  }
11
12
 
12
- if (API_REFERENCE_BASE_PATH && context.url.pathname.startsWith(API_REFERENCE_BASE_PATH)) {
13
+ const resolvedBasePath = path.posix.join(import.meta.env.BASE_URL ?? '', API_REFERENCE_BASE_PATH);
14
+ if (resolvedBasePath && context.url.pathname.startsWith(resolvedBasePath)) {
13
15
  // handled by the API reference API route in stl-starlight plugin
14
16
  return next();
15
17
  }
package/theme.css CHANGED
@@ -1,7 +1,6 @@
1
1
  @layer starlight.base, starlight.reset, starlight.core, starlight.content, starlight.components, starlight.utils;
2
2
  @layer starlight;
3
3
 
4
- @import './styles/fonts.css';
5
4
  @import './styles/links.css';
6
5
  @import './styles/sidebar.css';
7
6
  @import './styles/search.css';
@@ -4,7 +4,7 @@ declare module 'virtual:stl-starlight-virtual-module' {
4
4
  import type { StlStarlightMiddleware } from '@stainless-api/docs/plugin/MiddlewareTypes';
5
5
  import type { DocsLanguage } from '@stainless-api/docs-ui/routing';
6
6
 
7
- export const BASE_PATH: string;
7
+ export const RESOLVED_API_REFERENCE_PATH: string;
8
8
  export const EXCLUDE_LANGUAGES: DocsLanguage[];
9
9
  export const DEFAULT_LANGUAGE: string;
10
10
  export const BREADCRUMB_CONFIG: {
@@ -26,6 +26,7 @@ declare module 'virtual:stl-starlight-virtual-module' {
26
26
  declare module 'virtual:stl-docs-virtual-module' {
27
27
  import type { ButtonVariant } from '@stainless-api/ui-primitives';
28
28
  import type { AnchorHTMLAttributes } from 'react';
29
+ import { FontPreloadFilter } from 'astro:assets';
29
30
 
30
31
  type Tab = {
31
32
  label: string;
@@ -33,6 +34,11 @@ declare module 'virtual:stl-docs-virtual-module' {
33
34
  hidden?: boolean;
34
35
  };
35
36
 
37
+ type FontConfig = {
38
+ cssVariable: string;
39
+ preload?: FontPreloadFilter;
40
+ };
41
+
36
42
  export const HEADER_LINKS: {
37
43
  label: string;
38
44
  link: string;
@@ -43,10 +49,15 @@ declare module 'virtual:stl-docs-virtual-module' {
43
49
  export const SPLIT_TABS_ENABLED: boolean;
44
50
  export const HEADER_LAYOUT: 'default' | 'stacked';
45
51
  export const ENABLE_CLIENT_ROUTER: boolean;
46
- export const API_REFERENCE_BASE_PATH: string | null;
52
+ export const API_REFERENCE_BASE_PATH: string;
47
53
  export const ENABLE_PROSE_MARKDOWN_RENDERING: boolean;
48
54
  export const ENABLE_CONTEXT_MENU: boolean;
49
55
  export const RENDER_PAGE_DESCRIPTIONS: boolean;
56
+ export const FONTS: {
57
+ primary?: FontConfig;
58
+ heading?: FontConfig;
59
+ mono?: FontConfig;
60
+ };
50
61
  }
51
62
 
52
63
  declare module 'virtual:stl-docs/components/AiChat.tsx' {
package/styles/fonts.css DELETED
@@ -1,83 +0,0 @@
1
- /* Geist */
2
- @font-face {
3
- font-family: 'Geist';
4
- font-style: normal;
5
- font-display: swap;
6
- font-weight: 100 900;
7
- src: url(../assets/fonts/geist/geist-latin.woff2) format('woff2-variations');
8
- unicode-range:
9
- U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
10
- U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
11
- }
12
- @font-face {
13
- font-family: 'Geist';
14
- font-style: italic;
15
- font-display: swap;
16
- font-weight: 100 900;
17
- src: url(../assets/fonts/geist/geist-italic-latin.woff2) format('woff2-variations');
18
- unicode-range:
19
- U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
20
- U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
21
- }
22
- @font-face {
23
- font-family: 'Geist';
24
- font-style: normal;
25
- font-display: swap;
26
- font-weight: 100 900;
27
- src: url(../assets/fonts/geist/geist-latin-ext.woff2) format('woff2-variations');
28
- unicode-range:
29
- U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF,
30
- U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
31
- }
32
- @font-face {
33
- font-family: 'Geist';
34
- font-style: italic;
35
- font-display: swap;
36
- font-weight: 100 900;
37
- src: url(../assets/fonts/geist/geist-italic-latin-ext.woff2) format('woff2-variations');
38
- unicode-range:
39
- U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF,
40
- U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
41
- }
42
-
43
- /* Geist Mono */
44
- @font-face {
45
- font-family: 'Geist Mono';
46
- font-style: normal;
47
- font-display: swap;
48
- font-weight: 100 900;
49
- src: url(../assets/fonts/geist/geist-mono-latin.woff2) format('woff2-variations');
50
- unicode-range:
51
- U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
52
- U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
53
- }
54
- @font-face {
55
- font-family: 'Geist Mono';
56
- font-style: italic;
57
- font-display: swap;
58
- font-weight: 100 900;
59
- src: url(../assets/fonts/geist/geist-mono-italic-latin.woff2) format('woff2-variations');
60
- unicode-range:
61
- U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
62
- U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
63
- }
64
- @font-face {
65
- font-family: 'Geist Mono';
66
- font-style: normal;
67
- font-display: swap;
68
- font-weight: 100 900;
69
- src: url(../assets/fonts/geist/geist-mono-latin-ext.woff2) format('woff2-variations');
70
- unicode-range:
71
- U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF,
72
- U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
73
- }
74
- @font-face {
75
- font-family: 'Geist Mono';
76
- font-style: italic;
77
- font-display: swap;
78
- font-weight: 100 900;
79
- src: url(../assets/fonts/geist/geist-mono-italic-latin-ext.woff2) format('woff2-variations');
80
- unicode-range:
81
- U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF,
82
- U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
83
- }