@stainless-api/docs 0.1.0-beta.133 → 0.1.0-beta.135

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,17 @@
1
1
  # @stainless-api/docs
2
2
 
3
+ ## 0.1.0-beta.135
4
+
5
+ ### Patch Changes
6
+
7
+ - dea0395: fix backwards llmsTxt.disabled parsing
8
+
9
+ ## 0.1.0-beta.134
10
+
11
+ ### Minor Changes
12
+
13
+ - eb35b5c: Integrate @stainless-api/docs-og-image package into core
14
+
3
15
  ## 0.1.0-beta.133
4
16
 
5
17
  ### Patch Changes
@@ -12,26 +12,9 @@
12
12
  "count": 2
13
13
  }
14
14
  },
15
- "plugin/globalJs/copy.ts": {
16
- "@typescript-eslint/no-explicit-any": {
17
- "count": 4
18
- },
19
- "@typescript-eslint/no-floating-promises": {
20
- "count": 1
21
- },
22
- "@typescript-eslint/no-unsafe-assignment": {
23
- "count": 2
24
- },
25
- "@typescript-eslint/no-unsafe-member-access": {
26
- "count": 3
27
- }
28
- },
29
15
  "plugin/index.ts": {
30
16
  "@typescript-eslint/no-explicit-any": {
31
17
  "count": 1
32
- },
33
- "@typescript-eslint/no-unsafe-assignment": {
34
- "count": 1
35
18
  }
36
19
  },
37
20
  "plugin/languages.ts": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stainless-api/docs",
3
- "version": "0.1.0-beta.133",
3
+ "version": "0.1.0-beta.135",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -25,7 +25,10 @@
25
25
  "./components/ContentBreadcrumbs": "./stl-docs/components/ContentBreadcrumbs.tsx",
26
26
  "./components/AIDropdown": "./stl-docs/components/AIDropdown.tsx",
27
27
  "./components/Footer": "./stl-docs/components/Footer.astro",
28
- "./components/Pagination": "./stl-docs/components/pagination/Pagination.astro"
28
+ "./components/Pagination": "./stl-docs/components/pagination/Pagination.astro",
29
+ "./og-image/components/OpenGraphImage": "./stl-docs/og-image/components/OpenGraphImage.tsx",
30
+ "./og-image/components/OpenGraphFunctionSignature": "./stl-docs/og-image/components/OpenGraphFunctionSignature.tsx",
31
+ "./schema-extension": "./stl-docs/schema-extension.ts"
29
32
  },
30
33
  "keywords": [],
31
34
  "author": "",
@@ -35,11 +38,17 @@
35
38
  },
36
39
  "peerDependencies": {
37
40
  "@astrojs/starlight": ">=0.38.0",
41
+ "@takumi-rs/image-response": ">=0.66.6",
38
42
  "astro": ">=6.0.0",
39
43
  "react": ">=19.0.0",
40
44
  "react-dom": ">=19.0.0",
41
45
  "vite": ">=7.3.1"
42
46
  },
47
+ "peerDependenciesMeta": {
48
+ "@takumi-rs/image-response": {
49
+ "optional": true
50
+ }
51
+ },
43
52
  "dependencies": {
44
53
  "@astrojs/markdown-remark": "^7.1.0",
45
54
  "@astrojs/react": "^5.0.3",
@@ -62,7 +71,6 @@
62
71
  "remend": "^1.3.0",
63
72
  "shiki": "^4.0.2",
64
73
  "unified": "^11.0.5",
65
- "vite-plugin-prebundle-workers": "^0.2.0",
66
74
  "web-worker": "^1.5.0",
67
75
  "@stainless-api/docs-search": "0.1.0-beta.48",
68
76
  "@stainless-api/docs-ui": "0.1.0-beta.95",
@@ -70,16 +78,17 @@
70
78
  },
71
79
  "devDependencies": {
72
80
  "@astrojs/check": "^0.9.8",
81
+ "@takumi-rs/image-response": "^0.73.1",
73
82
  "@types/node": "24.12.2",
74
83
  "@types/react": "19.2.14",
75
84
  "@types/react-dom": "^19.2.3",
76
85
  "@types/react-syntax-highlighter": "^15.5.13",
77
86
  "astro": "^6.1.5",
78
- "react": "^19.2.4",
79
- "react-dom": "^19.2.4",
87
+ "react": "^19.2.5",
88
+ "react-dom": "^19.2.5",
80
89
  "typescript": "6.0.2",
81
90
  "vite": "^7.3.2",
82
- "vitest": "^4.1.3",
91
+ "vitest": "^4.1.4",
83
92
  "zod": "^4.3.6",
84
93
  "@stainless/eslint-config": "0.1.0-beta.2",
85
94
  "@stainless/sdk-json": "^0.1.0-beta.10"
@@ -8,7 +8,6 @@ import style from '@stainless-api/docs-ui/style';
8
8
  import * as cheerio from 'cheerio/slim';
9
9
  import {
10
10
  EXPERIMENTAL_COLLAPSIBLE_SNIPPETS,
11
- EXPERIMENTAL_PLAYGROUNDS,
12
11
  EXPERIMENTAL_REQUEST_BUILDER,
13
12
  } from 'virtual:stl-starlight-virtual-module';
14
13
  import clsx from 'clsx';
@@ -18,26 +17,6 @@ import React from 'react';
18
17
  import { RequestBuilder } from './RequestBuilder';
19
18
  import { Method } from '@stainless/sdk-json';
20
19
 
21
- function PlaygroundIcon() {
22
- return (
23
- <svg
24
- xmlns="http://www.w3.org/2000/svg"
25
- width={16}
26
- height={16}
27
- viewBox="0 0 24 24"
28
- fill="none"
29
- stroke="currentColor"
30
- strokeWidth={2}
31
- strokeLinecap="round"
32
- strokeLinejoin="round"
33
- className={'lucide ' + style.Icon}
34
- aria-hidden="true"
35
- >
36
- <path d="m 1,2 h 1 a 4,4 0 0 1 4,4 v 1 m 5,15 H 10 A 4,4 0 0 1 6,18 V 6 a 4,4 0 0 1 4,-4 h 1 M 1,22 H 2 A 4,4 0 0 0 6,18 V 17 M 14.029059,8.147837 A 1.2853426,1.2853426 0 0 1 15.978924,7.0437277 L 22.40178,10.8959 a 1.2853426,1.2853426 0 0 1 0,2.208219 l -6.422856,3.852172 a 1.2853426,1.2853426 0 0 1 -1.949865,-1.105395 z" />
37
- </svg>
38
- );
39
- }
40
-
41
20
  /*
42
21
  * This may be replaced by additional data from the sdk.
43
22
  * Without information from the sdk, we use simple heuristics per language.
@@ -172,21 +151,11 @@ export function SnippetContainer({ children, signature, method }: SnippetContain
172
151
  );
173
152
  }
174
153
 
175
- export function SnippetButtons({ content }: { content: string }) {
176
- void content;
177
- const language = useLanguage();
154
+ export function SnippetButtons() {
178
155
  return (
179
- <>
180
- <Button variant="outline" data-stldocs-snippet-copy>
181
- <CopyIcon size={16} className={style.Icon} />
182
- </Button>
183
- {EXPERIMENTAL_PLAYGROUNDS &&
184
- (language === 'python' || language === 'typescript' || language === 'http') && (
185
- <Button data-stldocs-snippet-play variant="muted" border title="Play">
186
- <PlaygroundIcon />
187
- </Button>
188
- )}
189
- </>
156
+ <Button variant="outline" data-stldocs-snippet-copy>
157
+ <CopyIcon size={16} className={style.Icon} />
158
+ </Button>
190
159
  );
191
160
  }
192
161
 
@@ -1,10 +1,4 @@
1
- import {
2
- RESOLVED_API_REFERENCE_PATH,
3
- EXPERIMENTAL_COLLAPSIBLE_SNIPPETS,
4
- } from 'virtual:stl-starlight-virtual-module';
5
- import { getPageLoadEvent } from '../helpers/getPageLoadEvent';
6
- import { updateSelectedLanguage } from '../languages';
7
- import { navigate } from 'astro/virtual-modules/transitions-router.js';
1
+ import { EXPERIMENTAL_COLLAPSIBLE_SNIPPETS } from 'virtual:stl-starlight-virtual-module';
8
2
  const copyIcon = `<rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>`;
9
3
  const circleAlertIcon = `<circle cx="12" cy="12" r="10"/><line x1="12" x2="12" y1="8" y2="12"/><line x1="12" x2="12.01" y1="16" y2="16"/>`;
10
4
  const checkIcon = `<path d="M20 6 9 17l-5-5"/>`;
@@ -25,13 +19,6 @@ function getContent(button: HTMLElement, full: boolean) {
25
19
  return contentCopy.textContent;
26
20
  }
27
21
 
28
- const preloadPlayground = (event: Event) => {
29
- const playButton = (event.target as HTMLElement).closest('[data-stldocs-snippet-play]') as HTMLElement;
30
- if (playButton) {
31
- loadPlayground(playButton);
32
- }
33
- };
34
- addEventListener('mouseover', preloadPlayground);
35
22
  addEventListener('click', (event) => {
36
23
  if (!(event.target instanceof Element)) return;
37
24
  const copyButton = event.target.closest('[data-stldocs-snippet-copy]');
@@ -54,72 +41,4 @@ addEventListener('click', (event) => {
54
41
  }).toString();
55
42
  });
56
43
  }
57
-
58
- const playButton = event.target.closest('[data-stldocs-snippet-play]');
59
- if (playButton instanceof HTMLElement) {
60
- showPlayground(playButton).catch(() => {
61
- console.error('Failed to load playground');
62
- });
63
- }
64
- });
65
-
66
- async function showPlayground(playButton: HTMLElement) {
67
- if (playButton.getAttribute('aria-disabled') === 'true') return;
68
- const iconElement = playButton.querySelector('.stldocs-icon') as SVGElement;
69
- try {
70
- // use aria-disabled, not disabled, to avoid losing focus
71
- playButton.setAttribute('aria-disabled', 'true');
72
- playButton.setAttribute('aria-label', 'Loading playground...');
73
- playButton.classList.add('stl-ui-button--loading');
74
- const showPlayground = loadPlayground(playButton);
75
- await showPlayground();
76
- } catch (e) {
77
- console.error(e);
78
- iconElement.innerHTML = circleAlertIcon;
79
- }
80
- playButton.removeAttribute('aria-disabled');
81
- playButton.removeAttribute('aria-label');
82
- playButton.classList.remove('stl-ui-button--loading');
83
- }
84
-
85
- function loadPlayground(playButton: HTMLElement) {
86
- (playButton as any).__playgroundLoadPromise ??= (async () => {
87
- const container = playButton.closest('.stldocs-snippet') as HTMLElement;
88
- const language = (container.querySelector('.stl-sdk-select') as HTMLElement).dataset.currentValue;
89
- const code = getContent(playButton, true);
90
- // eslint-disable-next-line turbo/no-undeclared-env-vars
91
- if (import.meta.env.DEV) {
92
- const id = '/@id/astro:scripts/before-hydration.js';
93
- await import(/* @vite-ignore */ id).catch(console.warn);
94
- }
95
- const { createPlayground } = await import('virtual:stl-playground/create');
96
- const { default: playgroundData } = await import('virtual:stl-playground/data');
97
- return createPlayground({
98
- lang: language as any,
99
- doc: (language === 'python' ? 'from rich import print\n' : '') + code.trimEnd(),
100
- container,
101
- onLanguageSelect: (value) => {
102
- const originalLanguage = container.querySelector<HTMLElement>('[data-stldocs-snippet-select]')
103
- ?.dataset.currentValue;
104
- const path: string = updateSelectedLanguage(RESOLVED_API_REFERENCE_PATH, originalLanguage, value);
105
- navigate(path.replace(/(\?.+)?($|#)/, (_, str, end) => (str ? str + '&play' : '?play') + end));
106
- },
107
- ...playgroundData,
108
- });
109
- })();
110
- return async () => {
111
- const promise = (playButton as any).__playgroundLoadPromise;
112
- (playButton as any).__playgroundLoadPromise = null;
113
- await ((await promise) as () => Promise<void>)();
114
- };
115
- }
116
- document.addEventListener(getPageLoadEvent(), () => {
117
- if (new URL(location.href).searchParams.has('play')) {
118
- document.querySelectorAll('[data-stldocs-snippet-play]').forEach((e) => {
119
- if (e instanceof HTMLElement)
120
- showPlayground(e).catch(() => {
121
- console.error('Failed to load playground');
122
- });
123
- });
124
- }
125
44
  });
package/plugin/index.ts CHANGED
@@ -26,7 +26,6 @@ import { getSharedLogger } from '../shared/getSharedLogger';
26
26
  import { resolveSrcFile } from '../resolveSrcFile';
27
27
  import { mkdir, writeFile } from 'fs/promises';
28
28
  import { fileURLToPath } from 'url';
29
- import prebundleWorkers from 'vite-plugin-prebundle-workers';
30
29
  import { SpecLoader, startSpecLoader } from './specs';
31
30
 
32
31
  import type * as ReferenceSidebarsVirtualModule from 'virtual:stl-starlight-reference-sidebars';
@@ -93,8 +92,6 @@ function stlStarlightAstroIntegration(pluginConfig: NormalizedStainlessStarlight
93
92
  const virtualId = `virtual:stl-starlight-virtual-module`;
94
93
  // The '\0' prefix tells Vite “this is a virtual module” and prevents it from being resolved again.
95
94
  const resolvedId = `\0${virtualId}`;
96
- let playgroundsBase: string | undefined;
97
- let buildPlaygrounds: (args: unknown) => Promise<void> | undefined;
98
95
  let astroBase = '/';
99
96
 
100
97
  let specLoader: SpecLoader | undefined;
@@ -104,39 +101,6 @@ function stlStarlightAstroIntegration(pluginConfig: NormalizedStainlessStarlight
104
101
  return result.specComposite;
105
102
  }
106
103
 
107
- let building: Promise<void> | undefined;
108
- let reportError: ((message: string) => void) | null = null;
109
- let collectedErrors: string[] | null = null;
110
-
111
- function startPlaygroundsBuild(playgroundsCachePath: string) {
112
- if (!pluginConfig.experimentalPlaygrounds) return;
113
- if (building) return building;
114
- return (building = (async () => {
115
- const specComposite = await resolveSpecs();
116
-
117
- if (specComposite.listUniqueSpecs().length > 1) {
118
- throw new Error('Multiple specs found. This is not supported for Playgrounds.');
119
- }
120
-
121
- const spec = specComposite.listUniqueSpecs()[0]!.data.sdkJson;
122
- const auth = specComposite.listUniqueSpecs()[0]!.data.auth;
123
-
124
- const langs = specComposite.getLanguages();
125
-
126
- return buildPlaygrounds?.({
127
- spec,
128
- langs,
129
- auth,
130
- playPath: playgroundsCachePath,
131
- reportError(message: string) {
132
- (reportError ?? console.error)(message);
133
- },
134
- })?.catch(() => {
135
- console.error('Failed to build playgrounds');
136
- });
137
- })());
138
- }
139
-
140
104
  return {
141
105
  name: 'stl-starlight-astro',
142
106
  hooks: {
@@ -154,12 +118,6 @@ function stlStarlightAstroIntegration(pluginConfig: NormalizedStainlessStarlight
154
118
 
155
119
  specLoader = await startSpecLoader(pluginConfig, logger, createCodegenDir());
156
120
 
157
- reportError = (message: string) => logger.error(message);
158
-
159
- if (pluginConfig.experimentalPlaygrounds) {
160
- playgroundsBase = pluginConfig.experimentalPlaygrounds.playgroundsBase;
161
- }
162
-
163
121
  const middlewareFileBase = path.join(projectDir, 'middleware.stainless');
164
122
  const middlewareFile = ['.tsx', '.ts']
165
123
  .map((ext) => middlewareFileBase + ext)
@@ -255,55 +213,11 @@ function stlStarlightAstroIntegration(pluginConfig: NormalizedStainlessStarlight
255
213
  ...specLoader.vitePlugins,
256
214
  {
257
215
  name: 'stl-starlight-vite',
258
- buildStart() {
259
- building = undefined;
260
- },
261
- async configureServer(server) {
262
- if (playgroundsBase) {
263
- buildPlaygrounds = (await server.ssrLoadModule(playgroundsBase + '/src/build.ts'))
264
- .buildPlaygrounds;
265
- }
266
-
267
- // TODO: eventually - re-add support for watching local input changes (eg. reloading when OAS/config files change)
268
- },
216
+ // TODO: eventually - re-add support for watching local input changes (eg. reloading when OAS/config files change)
269
217
  resolveId(id) {
270
218
  if (id === virtualId) {
271
219
  return resolvedId;
272
220
  }
273
- if (id === 'virtual:stl-playground/unstable-update-language') {
274
- return resolveSrcFile('plugin/languages.ts');
275
- }
276
- if (id === 'virtual:stl-playground/create') {
277
- return fileURLToPath(
278
- new URL(
279
- pluginConfig.experimentalPlaygrounds
280
- ? path.join(playgroundsBase!, '/src/create.tsx')
281
- : './globalJs/create-playground.shim.ts',
282
- import.meta.url,
283
- ),
284
- );
285
- }
286
- if (id === 'virtual:stl-playground/data') {
287
- return fileURLToPath(
288
- new URL(
289
- pluginConfig.experimentalPlaygrounds
290
- ? './globalJs/playground-data.ts'
291
- : './globalJs/playground-data.shim.ts',
292
- import.meta.url,
293
- ),
294
- );
295
- }
296
- if (id.startsWith('virtual:stl-playground/')) {
297
- const result = path.join(
298
- this.environment.getTopLevelConfig().cacheDir,
299
- 'stl-playground',
300
- id.slice('virtual:stl-playground/'.length),
301
- );
302
- const config = this.environment.getTopLevelConfig();
303
- return startPlaygroundsBuild(path.join(config.cacheDir, 'stl-playground'))?.then(
304
- () => result,
305
- );
306
- }
307
221
  },
308
222
  load(id) {
309
223
  if (id === resolvedId) {
@@ -321,7 +235,6 @@ function stlStarlightAstroIntegration(pluginConfig: NormalizedStainlessStarlight
321
235
  pluginConfig.experimentalCollapsibleMethodDescriptions,
322
236
  PROPERTY_SETTINGS: pluginConfig.propertySettings,
323
237
  ENABLE_CONTEXT_MENU: pluginConfig.contextMenu,
324
- EXPERIMENTAL_PLAYGROUNDS: !!pluginConfig.experimentalPlaygrounds,
325
238
  EXPERIMENTAL_REQUEST_BUILDER: pluginConfig.experimentalRequestBuilder,
326
239
  STAINLESS_PROJECT: pluginConfig.stainlessProject,
327
240
  LLMS_TXT_DESCRIPTION: pluginConfig.llmsTxt.description,
@@ -332,42 +245,14 @@ function stlStarlightAstroIntegration(pluginConfig: NormalizedStainlessStarlight
332
245
  }
333
246
  },
334
247
  },
335
- prebundleWorkers({
336
- include: [
337
- '**/typescript/runner*',
338
- '**/typescript/worker*',
339
- '**/pyright.worker*',
340
- '**/python/pyodide*',
341
- ],
342
- configureEsBuild(_, opts) {
343
- opts.external ??= [];
344
- opts.external.push('url');
345
- opts.loader ??= {};
346
- opts.loader['.wasm'] = 'dataurl';
347
- return opts;
348
- },
349
- }),
350
248
  ],
351
249
  },
352
250
  });
353
251
  },
354
- 'astro:build:start'({ logger }) {
355
- collectedErrors = [];
356
- reportError = (message: string) => {
357
- logger.error(message);
358
- collectedErrors!.push(message);
359
- };
360
- },
361
252
  'astro:build:done': async ({ dir, logger }) => {
362
253
  const dist = fileURLToPath(dir);
363
254
  const stainlessDir = path.join(dist, '_stainless');
364
255
  await mkdir(stainlessDir, { recursive: true });
365
- if (collectedErrors) {
366
- for (const error of collectedErrors) {
367
- logger.error(error);
368
- }
369
- collectedErrors = null;
370
- }
371
256
 
372
257
  const manifest = {
373
258
  astroBase,
@@ -151,12 +151,6 @@ export type StainlessStarlightUserConfig = {
151
151
 
152
152
  contextMenu?: boolean;
153
153
 
154
- /**
155
- * When set to `import playgrounds from '@stainless-api/playgrounds'`, code snippets will have a "Play" button,
156
- * allowing users to edit and run code snippets in their browser.
157
- */
158
- experimentalPlaygrounds?: { playgroundsBase: string };
159
-
160
154
  /** When set to true, enables the experimental request builder interface for testing API endpoints. */
161
155
  experimentalRequestBuilder?: boolean;
162
156
 
@@ -397,12 +391,11 @@ function normalizeConfig(partial: SomeStainlessStarlightUserConfig, astroOptions
397
391
  includeModelProperties: partial.propertySettings?.includeModelProperties ?? true,
398
392
  },
399
393
  contextMenu: partial.contextMenu ?? true,
400
- experimentalPlaygrounds: partial.experimentalPlaygrounds ?? undefined,
401
394
  experimentalRequestBuilder: partial.experimentalRequestBuilder ?? false,
402
395
  experimentalPrerender: partial.experimentalPrerender ?? true,
403
396
  stainlessProject: partial.stainlessProject,
404
397
  llmsTxt: {
405
- enabled: partial.llmsTxt?.disabled ?? true,
398
+ enabled: !partial.llmsTxt?.disabled,
406
399
  description: partial.llmsTxt?.description ?? null,
407
400
  detailThreshold: partial.llmsTxt?.detailThreshold ?? 2000,
408
401
  },
@@ -93,14 +93,9 @@ async function generateSpecFromStrings({
93
93
  }
94
94
  }
95
95
 
96
- const opts = Object.entries(config.client_settings.opts).map(([k, v]) => ({ name: k, ...v }));
97
96
  return {
98
97
  sdkJson,
99
98
  languages,
100
- auth: sdkJson.security_schemes.map((scheme) => ({
101
- ...scheme,
102
- opts: opts.filter((opt) => opt.auth?.security_scheme === scheme.name),
103
- })),
104
99
  };
105
100
  }
106
101
 
package/stl-docs/index.ts CHANGED
@@ -27,6 +27,7 @@ import { stainlessStarlight } from '../plugin';
27
27
  import { getFontRoles, flattenFonts } from './fonts';
28
28
  import conditionalIntegration from '../shared/conditionalIntegration';
29
29
  import generateExamplesPlugin from './aiChatExamples';
30
+ import { ogImageStarlightPlugin } from './og-image';
30
31
 
31
32
  export * from '../plugin';
32
33
 
@@ -91,7 +92,11 @@ function stainlessDocsStarlightIntegration(config: NormalizedStainlessDocsConfig
91
92
  componentOverrides.Search = resolveSrcFile('/plugin/components/search/Search.astro');
92
93
  }
93
94
 
94
- plugins.push(...config.starlightCompat.plugins, ...config.plugins.map((p) => p(config)));
95
+ if (config.ogImage) {
96
+ plugins.push(ogImageStarlightPlugin(config.ogImage, config));
97
+ }
98
+
99
+ plugins.push(...config.starlightCompat.plugins);
95
100
 
96
101
  // TODO: re-add once we figure out what to do with the client router
97
102
  // if (config.enableClientRouter) {
@@ -1,10 +1,11 @@
1
1
  import type { StainlessStarlightUserConfig } from '../plugin/loadPluginConfig';
2
- import type { StarlightPlugin, StarlightUserConfig } from '@astrojs/starlight/types';
2
+ import type { StarlightUserConfig } from '@astrojs/starlight/types';
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
6
  import { normalizeFonts, type StlDocsFontConfig } from './fonts';
7
7
  import type { ExamplePromptResponse } from './aiChatExamples';
8
+ import type { OGImageConfig } from './og-image/config';
8
9
 
9
10
  type StarlightConfig = Parameters<typeof starlight>[0];
10
11
 
@@ -102,10 +103,11 @@ export type StainlessDocsUserConfig = {
102
103
  */
103
104
  renderPageDescriptions?: boolean;
104
105
  /**
105
- * Stainless Docs plugins.
106
- * Each plugin is a function that receives the normalized config and returns a Starlight plugin.
106
+ * Enable and configure Open Graph image generation.
107
+ *
108
+ * Requires `@takumi-rs/image-response` to be installed as a dependency.
107
109
  */
108
- plugins?: ((config: Exclude<NormalizedStainlessDocsConfig, 'plugins'>) => StarlightPlugin)[];
110
+ ogImage?: OGImageConfig;
109
111
  } & PassThroughStarlightConfigOptions;
110
112
 
111
113
  type HeaderLayout = 'default' | 'stacked';
@@ -195,11 +197,11 @@ function normalizeConfig(userConfig: StainlessDocsUserConfig) {
195
197
  contextMenu: userConfig.contextMenu ?? true,
196
198
  expressiveCode: userConfig.expressiveCode,
197
199
  renderPageDescriptions: userConfig.renderPageDescriptions ?? true,
198
- plugins: userConfig.plugins ?? [],
199
200
  aiChat: userConfig.experimental?.aiChat,
200
201
  linkGroupTitlesToOverviewPages: userConfig.experimental?.linkGroupTitlesToOverviewPages ?? false,
201
202
  credits: !userConfig.disableCredits,
202
203
  siteTitle: normalizeSiteTitle(userConfig),
204
+ ogImage: userConfig.ogImage ?? null,
203
205
  };
204
206
 
205
207
  return configWithDefaults;
@@ -0,0 +1,64 @@
1
+ import { darkThemeVars, lightThemeVars } from '../theme';
2
+
3
+ export default function OpenGraphFunctionSignature({
4
+ params,
5
+ fullyQualifiedName,
6
+ theme,
7
+ }: {
8
+ params: { ident: string; optional?: boolean }[] | undefined;
9
+ fullyQualifiedName: string | undefined;
10
+ theme?: 'light' | 'dark';
11
+ }) {
12
+ if (!params || !fullyQualifiedName) {
13
+ return null;
14
+ }
15
+ const colors = theme === 'dark' ? darkThemeVars : lightThemeVars;
16
+
17
+ const joinedParams = params
18
+ .map((param) => {
19
+ if (param.optional) {
20
+ return `${param.ident}?`;
21
+ }
22
+ return param.ident;
23
+ })
24
+ .filter((p): p is string => p !== null)
25
+ .join(', ');
26
+
27
+ return (
28
+ <p
29
+ style={{
30
+ lineClamp: 1,
31
+ textOverflow: 'ellipsis',
32
+ fontSize: '33px',
33
+ lineHeight: '150%',
34
+ overflow: 'hidden',
35
+ width: '100%',
36
+ color: colors.foreground,
37
+ marginBottom: 0,
38
+ marginTop: 0,
39
+ }}
40
+ >
41
+ <span
42
+ style={{
43
+ color: colors.foregroundReduced,
44
+ }}
45
+ >
46
+ {fullyQualifiedName.split('.').slice(0, -1).join('.')}.
47
+ </span>
48
+ <span
49
+ style={{
50
+ fontWeight: 600,
51
+ }}
52
+ >
53
+ {fullyQualifiedName.split('.').slice(-1)}
54
+ </span>
55
+ <span
56
+ style={{
57
+ fontWeight: 600,
58
+ }}
59
+ >
60
+ ({joinedParams})
61
+ </span>
62
+ </p>
63
+ );
64
+ }