@stainless-api/docs 0.1.0-beta.6 → 0.1.0-beta.60

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/CHANGELOG.md +476 -0
  2. package/README.md +1 -1
  3. package/eslint-suppressions.json +52 -0
  4. package/locals.d.ts +16 -0
  5. package/package.json +45 -40
  6. package/plugin/assets/languages/csharp.svg +1 -0
  7. package/plugin/buildAlgoliaIndex.ts +32 -7
  8. package/plugin/cms/server.ts +130 -58
  9. package/plugin/cms/sidebar-builder.ts +7 -26
  10. package/plugin/cms/worker.ts +83 -5
  11. package/plugin/components/MethodDescription.tsx +54 -0
  12. package/plugin/components/SDKSelect.astro +7 -87
  13. package/plugin/components/SnippetCode.tsx +53 -8
  14. package/plugin/components/search/SearchAlgolia.astro +14 -26
  15. package/plugin/components/search/SearchIsland.tsx +38 -24
  16. package/plugin/create-playground.shim.tsx +3 -0
  17. package/plugin/generateAPIReferenceLink.ts +2 -2
  18. package/plugin/globalJs/ai-dropdown-options.ts +235 -0
  19. package/plugin/globalJs/code-snippets.ts +15 -8
  20. package/plugin/globalJs/copy.ts +81 -16
  21. package/plugin/globalJs/method-descriptions.ts +33 -0
  22. package/plugin/globalJs/navigation.ts +7 -4
  23. package/plugin/index.ts +179 -35
  24. package/plugin/languages.ts +5 -2
  25. package/plugin/loadPluginConfig.ts +121 -32
  26. package/plugin/middlewareBuilder/stainlessMiddleware.d.ts +1 -1
  27. package/plugin/react/Routing.tsx +208 -104
  28. package/plugin/referencePlaceholderUtils.ts +1 -1
  29. package/plugin/replaceSidebarPlaceholderMiddleware.ts +5 -1
  30. package/plugin/routes/Docs.astro +61 -83
  31. package/plugin/routes/Overview.astro +10 -16
  32. package/plugin/routes/markdown.ts +7 -7
  33. package/plugin/vendor/preview.worker.docs.js +19768 -17702
  34. package/plugin/vendor/templates/go.md +1 -1
  35. package/plugin/vendor/templates/python.md +1 -1
  36. package/resolveSrcFile.ts +10 -0
  37. package/scripts/vendor_deps.ts +5 -5
  38. package/shared/getSharedLogger.ts +15 -0
  39. package/shared/terminalUtils.ts +3 -0
  40. package/src/content.config.ts +9 -0
  41. package/stl-docs/components/AIDropdown.tsx +63 -0
  42. package/stl-docs/components/AiChatIsland.tsx +10 -0
  43. package/stl-docs/components/{content-panel/ContentBreadcrumbs.tsx → ContentBreadcrumbs.tsx} +10 -18
  44. package/stl-docs/components/Head.astro +16 -0
  45. package/stl-docs/components/Header.astro +6 -8
  46. package/stl-docs/components/PageFrame.astro +14 -0
  47. package/stl-docs/components/PageTitle.astro +82 -0
  48. package/stl-docs/components/TableOfContents.astro +34 -0
  49. package/stl-docs/components/ThemeSelect.astro +118 -136
  50. package/stl-docs/components/content-panel/ContentPanel.astro +16 -25
  51. package/stl-docs/components/headers/SplashMobileMenuToggle.astro +17 -1
  52. package/stl-docs/components/headers/StackedHeader.astro +29 -24
  53. package/stl-docs/components/icons/chat-gpt.tsx +17 -0
  54. package/stl-docs/components/icons/claude.tsx +10 -0
  55. package/stl-docs/components/icons/cursor.tsx +10 -0
  56. package/stl-docs/components/icons/gemini.tsx +19 -0
  57. package/stl-docs/components/icons/markdown.tsx +10 -0
  58. package/stl-docs/components/index.ts +1 -0
  59. package/stl-docs/components/mintlify-compat/Accordion.astro +7 -5
  60. package/stl-docs/components/mintlify-compat/AccordionGroup.astro +7 -3
  61. package/stl-docs/components/mintlify-compat/Columns.astro +40 -42
  62. package/stl-docs/components/mintlify-compat/Frame.astro +16 -18
  63. package/stl-docs/components/mintlify-compat/Step.astro +30 -32
  64. package/stl-docs/components/mintlify-compat/Steps.astro +8 -10
  65. package/stl-docs/components/mintlify-compat/callouts/Callout.astro +1 -1
  66. package/stl-docs/components/mintlify-compat/callouts/Check.astro +1 -1
  67. package/stl-docs/components/mintlify-compat/callouts/Danger.astro +1 -1
  68. package/stl-docs/components/mintlify-compat/callouts/Info.astro +1 -1
  69. package/stl-docs/components/mintlify-compat/callouts/Note.astro +1 -1
  70. package/stl-docs/components/mintlify-compat/callouts/Tip.astro +1 -1
  71. package/stl-docs/components/mintlify-compat/callouts/Warning.astro +1 -1
  72. package/stl-docs/components/mintlify-compat/card.css +33 -35
  73. package/stl-docs/components/nav-tabs/NavDropdown.astro +31 -70
  74. package/stl-docs/components/nav-tabs/NavTabs.astro +78 -80
  75. package/stl-docs/components/nav-tabs/SecondaryNavTabs.astro +15 -8
  76. package/stl-docs/components/nav-tabs/buildNavLinks.ts +3 -2
  77. package/stl-docs/components/pagination/HomeLink.astro +10 -0
  78. package/stl-docs/components/pagination/Pagination.astro +175 -0
  79. package/stl-docs/components/pagination/PaginationLinkEmphasized.astro +22 -0
  80. package/stl-docs/components/pagination/PaginationLinkQuiet.astro +13 -0
  81. package/stl-docs/components/pagination/util.ts +71 -0
  82. package/stl-docs/components/scripts.ts +1 -0
  83. package/stl-docs/disableCalloutSyntax.ts +36 -0
  84. package/stl-docs/index.ts +121 -48
  85. package/stl-docs/loadStlDocsConfig.ts +44 -4
  86. package/stl-docs/proseMarkdown/proseMarkdownIntegration.ts +64 -0
  87. package/stl-docs/proseMarkdown/proseMarkdownMiddleware.ts +34 -0
  88. package/stl-docs/proseMarkdown/toMarkdown.ts +158 -0
  89. package/stl-docs/proseSearchIndexing.ts +113 -0
  90. package/stl-docs/tabsMiddleware.ts +11 -3
  91. package/styles/code.css +108 -140
  92. package/styles/fonts.css +32 -17
  93. package/styles/links.css +11 -48
  94. package/styles/method-descriptions.css +36 -0
  95. package/styles/overrides.css +48 -60
  96. package/styles/page.css +92 -52
  97. package/styles/sdk_select.css +9 -7
  98. package/styles/search.css +58 -69
  99. package/styles/sidebar.css +211 -131
  100. package/styles/{variables.css → sl-variables.css} +3 -2
  101. package/styles/stldocs-variables.css +6 -0
  102. package/styles/toc.css +41 -34
  103. package/theme.css +10 -10
  104. package/tsconfig.json +2 -5
  105. package/virtual-module.d.ts +23 -3
  106. package/components/variables.css +0 -135
  107. /package/{plugin/assets → assets}/fonts/geist/OFL.txt +0 -0
  108. /package/{plugin/assets → assets}/fonts/geist/geist-italic-latin-ext.woff2 +0 -0
  109. /package/{plugin/assets → assets}/fonts/geist/geist-italic-latin.woff2 +0 -0
  110. /package/{plugin/assets → assets}/fonts/geist/geist-latin-ext.woff2 +0 -0
  111. /package/{plugin/assets → assets}/fonts/geist/geist-latin.woff2 +0 -0
  112. /package/{plugin/assets → assets}/fonts/geist/geist-mono-italic-latin-ext.woff2 +0 -0
  113. /package/{plugin/assets → assets}/fonts/geist/geist-mono-italic-latin.woff2 +0 -0
  114. /package/{plugin/assets → assets}/fonts/geist/geist-mono-latin-ext.woff2 +0 -0
  115. /package/{plugin/assets → assets}/fonts/geist/geist-mono-latin.woff2 +0 -0
@@ -1,30 +1,39 @@
1
1
  import { EXPERIMENTAL_COLLAPSIBLE_SNIPPETS } from 'virtual:stl-starlight-virtual-module';
2
+ import { getPageLoadEvent } from '../helpers/getPageLoadEvent';
2
3
  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"/>`;
3
4
  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"/>`;
4
5
  const checkIcon = `<path d="M20 6 9 17l-5-5"/>`;
5
6
 
7
+ function getContent(button: HTMLElement, full: boolean) {
8
+ const isContentCollapsed = !!document.querySelector('.stldocs-snippet-code.stl-snippet-code-is-collapsed');
9
+
10
+ const content = button.closest('[data-stldocs-copy-parent]')!.querySelector('[data-stldocs-copy-content]')!;
11
+
12
+ const contentCopy = content.cloneNode(true) as HTMLElement;
13
+
14
+ contentCopy.querySelectorAll('.ellipsis').forEach((el) => el.remove());
15
+ if (EXPERIMENTAL_COLLAPSIBLE_SNIPPETS && isContentCollapsed && !full) {
16
+ contentCopy.querySelectorAll('.hidden').forEach((el) => el.remove());
17
+ contentCopy.querySelectorAll('.leading-ws').forEach((el) => el.remove());
18
+ }
19
+
20
+ return contentCopy.textContent!;
21
+ }
22
+
23
+ const preloadPlayground = async (event: Event) => {
24
+ const playButton = (event.target as HTMLElement).closest('[data-stldocs-snippet-play]') as HTMLElement;
25
+ if (playButton) {
26
+ loadPlayground(playButton);
27
+ }
28
+ };
29
+ addEventListener('mouseover', preloadPlayground);
6
30
  addEventListener('click', async (event) => {
7
31
  const copyButton = (event.target as HTMLElement).closest('[data-stldocs-snippet-copy]') as HTMLElement;
8
32
  if (copyButton) {
9
33
  const iconElement = copyButton.querySelector('.stldocs-icon') as SVGElement;
10
34
  clearTimeout(copyButton.dataset.__stldocsCopyTimeout);
11
35
  try {
12
- const isContentCollapsed = !!document.querySelector(
13
- '.stldocs-snippet-code.stl-snippet-code-is-collapsed',
14
- );
15
-
16
- const content = copyButton
17
- .closest('[data-stldocs-copy-parent]')!
18
- .querySelector('[data-stldocs-copy-content]')!;
19
-
20
- const contentCopy = content.cloneNode(true) as HTMLElement;
21
-
22
- contentCopy.querySelectorAll('.ellipsis').forEach((el) => el.remove());
23
- if (EXPERIMENTAL_COLLAPSIBLE_SNIPPETS && isContentCollapsed) {
24
- contentCopy.querySelectorAll('.hidden').forEach((el) => el.remove());
25
- contentCopy.querySelectorAll('.leading-ws').forEach((el) => el.remove());
26
- }
27
- await navigator.clipboard.writeText(contentCopy.textContent!);
36
+ await navigator.clipboard.writeText(getContent(copyButton, false));
28
37
  iconElement.innerHTML = checkIcon;
29
38
  } catch {
30
39
  iconElement.innerHTML = circleAlertIcon;
@@ -34,4 +43,60 @@ addEventListener('click', async (event) => {
34
43
  iconElement.innerHTML = copyIcon;
35
44
  }, 1000) + '';
36
45
  }
46
+
47
+ const playButton = (event.target as HTMLElement).closest('[data-stldocs-snippet-play]') as HTMLElement;
48
+ if (playButton) {
49
+ showPlayground(playButton);
50
+ }
51
+ });
52
+
53
+ async function showPlayground(playButton: HTMLElement) {
54
+ if (playButton.getAttribute('aria-disabled') === 'true') return;
55
+ const iconElement = playButton.querySelector('.stldocs-icon') as SVGElement;
56
+ try {
57
+ // use aria-disabled, not disabled, to avoid losing focus
58
+ playButton.setAttribute('aria-disabled', 'true');
59
+ playButton.setAttribute('aria-label', 'Loading playground...');
60
+ playButton.classList.add('stl-ui-button--loading');
61
+ const showPlayground = loadPlayground(playButton);
62
+ await showPlayground();
63
+ } catch (e) {
64
+ console.error(e);
65
+ iconElement.innerHTML = circleAlertIcon;
66
+ }
67
+ playButton.removeAttribute('aria-disabled');
68
+ playButton.removeAttribute('aria-label');
69
+ playButton.classList.remove('stl-ui-button--loading');
70
+ }
71
+
72
+ function loadPlayground(playButton: HTMLElement) {
73
+ (playButton as any).__playgroundLoadPromise ??= (async () => {
74
+ const container = playButton.closest('.stldocs-snippet') as HTMLElement;
75
+ const language = (container.querySelector('.stl-sdk-select') as HTMLElement).dataset.currentValue;
76
+ const code = getContent(playButton, true);
77
+ // eslint-disable-next-line turbo/no-undeclared-env-vars
78
+ if (import.meta.env.DEV) {
79
+ const id = '/@id/astro:scripts/before-hydration.js';
80
+ await import(/* @vite-ignore */ id).catch(console.warn);
81
+ }
82
+ const { createPlayground } = await import('virtual:stl-playground/create');
83
+ return createPlayground({
84
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
85
+ lang: language as any,
86
+ doc: (language === 'python' ? 'from rich import print\n' : '') + code.trimEnd(),
87
+ container,
88
+ });
89
+ })();
90
+ return async () => {
91
+ const promise = (playButton as any).__playgroundLoadPromise;
92
+ (playButton as any).__playgroundLoadPromise = null;
93
+ await ((await promise) as () => Promise<void>)();
94
+ };
95
+ }
96
+ document.addEventListener(getPageLoadEvent(), () => {
97
+ if (new URL(location.href).searchParams.has('play')) {
98
+ document.querySelectorAll('[data-stldocs-snippet-play]').forEach((e) => {
99
+ showPlayground(e as HTMLElement);
100
+ });
101
+ }
37
102
  });
@@ -0,0 +1,33 @@
1
+ document.addEventListener('DOMContentLoaded', function () {
2
+ const COLLAPSED_HEIGHT = 170;
3
+
4
+ const container = document.querySelector<HTMLElement>('[data-stldocs-property-group="method-description"]');
5
+
6
+ if (!container) return;
7
+
8
+ const toggle = document?.querySelector<HTMLButtonElement>('[data-method-description-toggle]');
9
+ if (!toggle) return;
10
+
11
+ // If content isn't tall enough, don't show the button
12
+ if (container.scrollHeight <= COLLAPSED_HEIGHT + 1) {
13
+ toggle.hidden = true;
14
+ container.dataset.collapsed = 'false';
15
+ return;
16
+ }
17
+
18
+ // Only show button if content is taller than collapsed max height
19
+ if (container.scrollHeight > COLLAPSED_HEIGHT + 1) {
20
+ toggle.hidden = false;
21
+ } else {
22
+ // Not tall enough to need collapsing — show full content and hide button
23
+ container.dataset.collapsed = 'false';
24
+ toggle.hidden = true;
25
+ return;
26
+ }
27
+
28
+ toggle.addEventListener('click', function () {
29
+ const isCollapsed = container.dataset.collapsed !== 'false';
30
+ container.dataset.collapsed = isCollapsed ? 'false' : 'true';
31
+ toggle.textContent = isCollapsed ? 'Show less' : 'Show more';
32
+ });
33
+ });
@@ -1,10 +1,10 @@
1
- import { parseRoute, scrollToPath } from '@stainless-api/docs-ui/src/routing';
1
+ import { parseRoute, scrollToPath } from '@stainless-api/docs-ui/routing';
2
2
  import { BASE_PATH } from 'virtual:stl-starlight-virtual-module';
3
3
  import { updateSelectedLanguage } from '../languages';
4
4
  import { navigate } from 'astro:transitions/client';
5
5
  import { getPageLoadEvent } from '../helpers/getPageLoadEvent.ts';
6
6
 
7
- import { initDropdown } from '@stainless-api/docs-ui/src/components/scripts/dropdown';
7
+ import { initDropdown } from '@stainless-api/docs/components/scripts';
8
8
 
9
9
  history.scrollRestoration = 'auto';
10
10
 
@@ -22,10 +22,13 @@ window.addEventListener('popstate', (ev: PopStateEvent) => {
22
22
  });
23
23
 
24
24
  document.addEventListener(getPageLoadEvent(), () => {
25
+ const rootElement = document.getElementById('stldocs-snippet-select');
26
+ if (!rootElement) return;
27
+
25
28
  initDropdown({
26
- dropdownId: 'stldocs-snippet-select',
29
+ root: rootElement,
27
30
  onSelect: (value) => {
28
- const originalLanguage = document.getElementById('stldocs-snippet-select')?.dataset.currentValue;
31
+ const originalLanguage = rootElement?.dataset.currentValue;
29
32
  navigate(updateSelectedLanguage(BASE_PATH, originalLanguage, value));
30
33
  },
31
34
  });
package/plugin/index.ts CHANGED
@@ -1,9 +1,10 @@
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
+ import type { BundledTheme } from 'shiki';
4
5
  import { config } from 'dotenv';
5
6
  import getPort from 'get-port';
6
- import { startDevServer } from './cms/server';
7
+ import { startDevServer, type SpecResp } from './cms/server';
7
8
  import { buildAlgoliaIndex } from './buildAlgoliaIndex';
8
9
  import {
9
10
  getAPIReferencePlaceholderItemFromSidebarConfig,
@@ -21,13 +22,21 @@ import {
21
22
  type SpecRetrieverConfig,
22
23
  } from './loadPluginConfig';
23
24
  import { buildVirtualModuleString } from '../shared/virtualModule';
25
+ import type * as StlStarlightVirtualModule from 'virtual:stl-starlight-virtual-module';
24
26
  import path from 'path';
25
27
  import fs from 'fs';
28
+ import { getSharedLogger } from '../shared/getSharedLogger';
29
+ import { resolveSrcFile } from '../resolveSrcFile';
30
+ import { mkdir } from 'fs/promises';
31
+ import { fileURLToPath } from 'url';
32
+ import prebundleWorkers from 'vite-plugin-prebundle-workers';
26
33
 
27
34
  export { generateAPILink } from './generateAPIReferenceLink';
28
35
  export type { ReferenceSidebarConfigItem };
29
36
 
30
- config();
37
+ config({
38
+ quiet: true,
39
+ });
31
40
 
32
41
  let sidebarIdCounter = 0;
33
42
 
@@ -108,20 +117,24 @@ function tmpGetCMSServerConfig(specRetrieverConfig: SpecRetrieverConfig) {
108
117
 
109
118
  async function stlStarlightAstroIntegration(
110
119
  pluginConfig: NormalizedStainlessStarlightConfig,
120
+ stlStarlightPluginLogger: AstroIntegrationLogger,
111
121
  ): Promise<AstroIntegration> {
112
122
  const virtualId = `virtual:stl-starlight-virtual-module`;
113
123
  // The '\0' prefix tells Vite “this is a virtual module” and prevents it from being resolved again.
114
124
  const resolvedId = `\0${virtualId}`;
125
+ let playgroundsBase: string | undefined;
126
+ let buildPlaygrounds;
115
127
 
116
128
  const CMS_PORT = await getPort();
117
129
 
118
130
  const { apiKey, version, devPaths } = tmpGetCMSServerConfig(pluginConfig.specRetrieverConfig);
119
131
 
120
- const cmsServer = await startDevServer({
132
+ const cmsServer = startDevServer({
121
133
  port: CMS_PORT,
122
- apiKey,
134
+ apiKey: apiKey.value,
123
135
  version,
124
136
  devPaths,
137
+ logger: stlStarlightPluginLogger,
125
138
  getGeneratedSidebarConfig: (id: number) => {
126
139
  const config = sidebarConfigs.get(id);
127
140
  if (!config) {
@@ -131,50 +144,98 @@ async function stlStarlightAstroIntegration(
131
144
  },
132
145
  });
133
146
 
147
+ let building: Promise<void> | undefined;
148
+ let reportError: ((message: string) => void) | null = null;
149
+ let collectedErrors: string[] | null = null;
150
+
151
+ function startPlaygroundsBuild(playgroundsCachePath: string) {
152
+ if (!pluginConfig.experimentalPlaygrounds) return;
153
+ if (building) return building;
154
+ return (building = (async () => {
155
+ const { data: spec, auth } = (await (
156
+ await fetch(`http://localhost:${CMS_PORT}/retrieve_spec`, {
157
+ method: 'POST',
158
+ body: JSON.stringify({}),
159
+ })
160
+ ).json()) as SpecResp;
161
+ if (!spec || !auth) throw new Error('expected spec');
162
+
163
+ const langs = (spec.docs?.languages ?? ['http'])
164
+ .filter((lang) => lang !== 'terraform')
165
+ .filter((lang) => !pluginConfig.excludeLanguages?.includes(lang));
166
+
167
+ await buildPlaygrounds!({
168
+ spec,
169
+ langs,
170
+ auth,
171
+ playPath: playgroundsCachePath,
172
+ reportError(message: string) {
173
+ (reportError ?? console.error)(message);
174
+ },
175
+ });
176
+ })());
177
+ }
178
+
134
179
  return {
135
180
  name: 'stl-starlight-astro',
136
181
  hooks: {
137
- 'astro:config:setup': async ({ injectRoute, updateConfig, logger, command, config: astroConfig }) => {
182
+ 'astro:config:setup': async ({
183
+ injectRoute,
184
+ updateConfig,
185
+ logger: localLogger,
186
+ command,
187
+ config: astroConfig,
188
+ }) => {
189
+ const logger = getSharedLogger({ fallback: localLogger });
138
190
  const projectDir = astroConfig.root.pathname;
139
191
 
192
+ reportError = (message: string) => logger.error(message);
193
+
194
+ if (pluginConfig.experimentalPlaygrounds) {
195
+ playgroundsBase = pluginConfig.experimentalPlaygrounds.playgroundsBase;
196
+ }
197
+
140
198
  const middlewareFile = path.join(projectDir, 'middleware.stainless.ts');
141
199
 
142
200
  let vmMiddlewareExport = 'export const MIDDLEWARE = {};';
143
201
  if (fs.existsSync(middlewareFile)) {
144
- logger.info(`Loading middleware from ${middlewareFile}`);
202
+ logger.debug(`Loading middleware from ${middlewareFile}`);
145
203
  vmMiddlewareExport = `export { default as MIDDLEWARE } from '${middlewareFile}';`;
146
204
  }
147
205
 
148
206
  injectRoute({
149
- pattern: `${pluginConfig.basePath}/[...slug].md`,
150
- entrypoint: '@stainless-api/docs/MarkdownRoute',
207
+ pattern: `${pluginConfig.basePath}/[...slug]/index.md`,
208
+ entrypoint: resolveSrcFile('/plugin/routes/markdown.ts'),
151
209
  prerender: command === 'build',
152
210
  });
153
211
 
154
- const astroFile = command === 'build' ? 'DocsStaticRoute' : 'DocsRoute';
212
+ const astroFile = command === 'build' ? 'DocsStatic' : 'Docs';
155
213
  injectRoute({
156
214
  pattern: `${pluginConfig.basePath}/[...slug]`,
157
- // in prod I think this points to @stainless-starlight/components/docs.astro
158
- entrypoint: `@stainless-api/docs/${astroFile}`,
215
+ entrypoint: resolveSrcFile(`/plugin/routes/${astroFile}.astro`),
159
216
  prerender: command === 'build',
160
217
  });
161
218
 
162
219
  injectRoute({
163
220
  pattern: pluginConfig.basePath,
164
- entrypoint: '@stainless-api/docs/OverviewRoute',
221
+ entrypoint: resolveSrcFile('/plugin/routes/Overview.astro'),
165
222
  prerender: command === 'build',
166
223
  });
167
224
 
168
225
  updateConfig({
169
226
  vite: {
170
- ssr: {
171
- noExternal: ['@stainless-api/ui-primitives'],
172
- },
173
- optimizeDeps: { include: ['@stainless-api/ui-primitives'] },
174
227
  plugins: [
175
228
  {
176
229
  name: 'stl-starlight-vite',
177
- configureServer(server) {
230
+ buildStart() {
231
+ building = undefined;
232
+ },
233
+ async configureServer(server) {
234
+ if (playgroundsBase) {
235
+ buildPlaygrounds = (await server.ssrLoadModule(playgroundsBase + '/src/build.ts'))
236
+ .buildPlaygrounds;
237
+ }
238
+
178
239
  for (const filePath of Object.values(devPaths)) {
179
240
  if (!filePath) {
180
241
  continue;
@@ -197,6 +258,30 @@ async function stlStarlightAstroIntegration(
197
258
  if (id === virtualId) {
198
259
  return resolvedId;
199
260
  }
261
+ if (id === 'virtual:stl-playground/unstable-update-language') {
262
+ return resolveSrcFile('plugin/languages.ts');
263
+ }
264
+ if (id === 'virtual:stl-playground/create') {
265
+ return fileURLToPath(
266
+ new URL(
267
+ pluginConfig.experimentalPlaygrounds
268
+ ? path.join(playgroundsBase!, '/src/create.tsx')
269
+ : './create-playground.shim.tsx',
270
+ import.meta.url,
271
+ ),
272
+ );
273
+ }
274
+ if (id.startsWith('virtual:stl-playground/')) {
275
+ const result = path.join(
276
+ this.environment.getTopLevelConfig().cacheDir,
277
+ 'stl-playground',
278
+ id.slice('virtual:stl-playground/'.length),
279
+ );
280
+ const config = this.environment.getTopLevelConfig();
281
+ return startPlaygroundsBuild(path.join(config.cacheDir, 'stl-playground'))?.then(
282
+ () => result,
283
+ );
284
+ }
200
285
  },
201
286
  load(id) {
202
287
  if (id === resolvedId) {
@@ -204,21 +289,40 @@ async function stlStarlightAstroIntegration(
204
289
  buildVirtualModuleString({
205
290
  BASE_PATH: pluginConfig.basePath,
206
291
  CMS_PORT,
207
- EXCLUDE_LANGUAGES: pluginConfig.excludeLanguages,
292
+ EXCLUDE_LANGUAGES: ['php', ...pluginConfig.excludeLanguages],
208
293
  DEFAULT_LANGUAGE: pluginConfig.defaultLanguage,
209
294
  BREADCRUMB_CONFIG: pluginConfig.breadcrumbs,
210
295
  EXPAND_RESOURCES: pluginConfig.expandResources,
211
296
  HIGHLIGHT_THEMES: pluginConfig.highlighting.themes,
212
297
  CONTENT_PANEL_LAYOUT: pluginConfig.contentPanel.layout,
213
298
  EXPERIMENTAL_COLLAPSIBLE_SNIPPETS: pluginConfig.experimentalCollapsibleSnippets,
299
+ EXPERIMENTAL_COLLAPSIBLE_METHOD_DESCRIPTIONS:
300
+ pluginConfig.experimentalCollapsibleMethodDescriptions,
214
301
  PROPERTY_SETTINGS: pluginConfig.propertySettings,
215
- SEARCH: pluginConfig.search,
216
- }),
302
+ ENABLE_CONTEXT_MENU: pluginConfig.contextMenu,
303
+ EXPERIMENTAL_PLAYGROUNDS: !!pluginConfig.experimentalPlaygrounds,
304
+ STAINLESS_PROJECT: version.stainlessProject,
305
+ } satisfies Omit<typeof StlStarlightVirtualModule, 'MIDDLEWARE'>),
217
306
  vmMiddlewareExport,
218
307
  ].join('\n');
219
308
  }
220
309
  },
221
310
  },
311
+ prebundleWorkers({
312
+ include: [
313
+ '**/typescript/runner*',
314
+ '**/typescript/worker*',
315
+ '**/pyright.worker*',
316
+ '**/python/pyodide*',
317
+ ],
318
+ configureEsBuild(_, opts) {
319
+ opts.external ??= [];
320
+ opts.external.push('url');
321
+ opts.loader ??= {};
322
+ opts.loader['.wasm'] = 'dataurl';
323
+ return opts;
324
+ },
325
+ }),
222
326
  ],
223
327
  },
224
328
  });
@@ -226,6 +330,24 @@ async function stlStarlightAstroIntegration(
226
330
  'astro:server:done': async () => {
227
331
  await cmsServer.destroy();
228
332
  },
333
+ 'astro:build:start'({ logger }) {
334
+ collectedErrors = [];
335
+ reportError = (message: string) => {
336
+ logger.error(message);
337
+ collectedErrors!.push(message);
338
+ };
339
+ },
340
+ 'astro:build:done': async ({ dir, logger }) => {
341
+ const dist = fileURLToPath(dir);
342
+ const stainlessDir = path.join(dist, '_stainless');
343
+ await mkdir(stainlessDir, { recursive: true });
344
+ if (collectedErrors) {
345
+ for (const error of collectedErrors) {
346
+ logger.error(error);
347
+ }
348
+ collectedErrors = null;
349
+ }
350
+ },
229
351
  },
230
352
  };
231
353
  }
@@ -241,15 +363,20 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
241
363
  command,
242
364
  config: starlightConfig,
243
365
  astroConfig,
244
- logger,
366
+ logger: localLogger,
245
367
  }) => {
246
368
  if (command !== 'build' && command !== 'dev') {
247
369
  return;
248
370
  }
249
371
 
372
+ const logger = getSharedLogger({ fallback: localLogger });
373
+
250
374
  const configParseResult = parseStarlightPluginConfig(someUserConfig, command);
251
375
  if (configParseResult.result === 'error') {
252
- logger.error(configParseResult.message);
376
+ const errorLines = configParseResult.message.split('\n');
377
+ for (const line of errorLines) {
378
+ logger.error(line);
379
+ }
253
380
  process.exit(1);
254
381
  }
255
382
  const config = configParseResult.config;
@@ -260,17 +387,30 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
260
387
  addIntegration(react());
261
388
  }
262
389
 
390
+ if ('apiKey' in config.specRetrieverConfig) {
391
+ if (!config.specRetrieverConfig.apiKey) {
392
+ logger.info(`Stainless credentials not loaded`);
393
+ } else if (config.specRetrieverConfig.apiKey.source === 'explicit-config') {
394
+ logger.info(`Stainless credentials loaded from user config`);
395
+ } else if (config.specRetrieverConfig.apiKey.source === 'environment-variable') {
396
+ logger.info('Stainless credentials loaded from `STAINLESS_API_KEY` environment variable');
397
+ } else if (config.specRetrieverConfig.apiKey.source === 'cli') {
398
+ logger.info('Stainless credentials loaded from `stl` CLI');
399
+ }
400
+ }
401
+
263
402
  if (
264
403
  command === 'build' &&
265
404
  config.specRetrieverConfig.kind === 'local_spec_server_with_remote_files'
266
405
  ) {
267
406
  await buildAlgoliaIndex({
268
407
  version: config.specRetrieverConfig.version,
269
- apiKey: config.specRetrieverConfig.apiKey,
408
+ apiKey: config.specRetrieverConfig.apiKey.value,
409
+ logger,
270
410
  });
271
411
  }
272
412
 
273
- addIntegration(await stlStarlightAstroIntegration(config));
413
+ addIntegration(await stlStarlightAstroIntegration(config, logger));
274
414
 
275
415
  if (starlightConfig.sidebar) {
276
416
  // for pagination (https://starlight.astro.build/reference/configuration/#pagination) to work correctly
@@ -281,21 +421,25 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
281
421
  }
282
422
  }
283
423
 
284
- const currentExpressiveCode =
424
+ const expressiveCodeConfig =
285
425
  typeof starlightConfig.expressiveCode === 'object' ? starlightConfig.expressiveCode : {};
286
- updateConfig({
287
- expressiveCode: {
288
- ...currentExpressiveCode,
289
- themes: [...(currentExpressiveCode.themes || []), 'github-light', 'github-dark'],
290
- },
291
- });
426
+
427
+ const themes = expressiveCodeConfig.themes
428
+ ? (expressiveCodeConfig.themes as BundledTheme[])
429
+ : (['github-light', 'github-dark'] as BundledTheme[]);
292
430
 
293
431
  updateConfig({
294
432
  sidebar: starlightConfig.sidebar,
433
+ ...(expressiveCodeConfig && {
434
+ expressiveCode: {
435
+ ...expressiveCodeConfig,
436
+ themes,
437
+ },
438
+ }),
295
439
  });
296
440
 
297
441
  addRouteMiddleware({
298
- entrypoint: '@stainless-api/docs/replaceSidebarPlaceholderMiddleware',
442
+ entrypoint: resolveSrcFile('/plugin/replaceSidebarPlaceholderMiddleware.ts'),
299
443
  order: 'post',
300
444
  });
301
445
  },
@@ -304,5 +448,5 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
304
448
  }
305
449
 
306
450
  // Additional exports we want for Stainless <-> docs integration.
307
- export { parseStainlessPath } from '@stainless-api/docs-ui/src/routing';
308
- export { renderMarkdown } from '@stainless-api/docs-ui/src/markdown';
451
+ export { parseStainlessPath } from '@stainless-api/docs-ui/routing';
452
+ export { renderMarkdown } from '@stainless-api/docs-ui/markdown';
@@ -1,4 +1,4 @@
1
- import type { DocsLanguage } from '@stainless-api/docs-ui/src/routing';
1
+ import type { DocsLanguage } from '@stainless-api/docs-ui/routing';
2
2
  import KotlinIcon from './assets/languages/kotlin.svg';
3
3
  import RubyIcon from './assets/languages/ruby.svg';
4
4
  import TerraformIcon from './assets/languages/terraform.svg';
@@ -7,6 +7,7 @@ import PythonIcon from './assets/languages/python.svg';
7
7
  import JavaIcon from './assets/languages/java.svg';
8
8
  import GoIcon from './assets/languages/go.svg';
9
9
  import CurlIcon from './assets/languages/curl.svg';
10
+ import CSharpIcon from './assets/languages/csharp.svg';
10
11
 
11
12
  export const Languages: Record<
12
13
  DocsLanguage,
@@ -29,6 +30,8 @@ export const Languages: Record<
29
30
  http: { name: 'HTTP', icon: CurlIcon, alt: 'HTTP logo' },
30
31
  terraform: { name: 'Terraform', icon: TerraformIcon, alt: 'Terraform logo' },
31
32
  ruby: { name: 'Ruby', icon: RubyIcon, alt: 'Ruby logo' },
33
+ csharp: { name: 'C#', icon: CSharpIcon, alt: 'C# logo' },
34
+ php: { name: 'PHP', icon: CSharpIcon, alt: 'PHP logo' }, // TODO update PHP icon
32
35
  };
33
36
 
34
37
  export function generatePrefix(basePath: string, language: string) {
@@ -44,7 +47,7 @@ export function applyLanguageToLinks(basePath?: string, defaultLanguage?: string
44
47
  `[data-stldocs-overview],[data-stldocs-method],a.nav-link[href^='${basePath}']`,
45
48
  );
46
49
 
47
- for (var link of links) {
50
+ for (const link of links) {
48
51
  const href = link.getAttribute('href');
49
52
  const prefix = generatePrefix(basePath, language);
50
53
  if (href?.startsWith(basePath) && !href?.startsWith(prefix)) {