@stainless-api/docs 0.1.0-beta.9 → 0.1.0-beta.90

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 (142) hide show
  1. package/CHANGELOG.md +748 -0
  2. package/eslint-suppressions.json +32 -0
  3. package/locals.d.ts +17 -0
  4. package/package.json +49 -40
  5. package/playground-virtual-modules.d.ts +96 -0
  6. package/plugin/assets/languages/cli.svg +14 -0
  7. package/plugin/assets/languages/csharp.svg +1 -0
  8. package/plugin/buildAlgoliaIndex.ts +38 -11
  9. package/plugin/components/MethodDescription.tsx +54 -0
  10. package/plugin/components/RequestBuilder/ParamEditor.tsx +55 -0
  11. package/plugin/components/RequestBuilder/SnippetStainlessIsland.tsx +107 -0
  12. package/plugin/components/RequestBuilder/index.tsx +31 -0
  13. package/plugin/components/RequestBuilder/props.ts +9 -0
  14. package/plugin/components/RequestBuilder/spec-helpers.ts +50 -0
  15. package/plugin/components/RequestBuilder/styles.css +67 -0
  16. package/plugin/components/SDKSelect.astro +20 -104
  17. package/plugin/components/SnippetCode.tsx +111 -66
  18. package/plugin/components/StainlessIslands.tsx +126 -0
  19. package/plugin/components/search/SearchAlgolia.astro +45 -28
  20. package/plugin/components/search/SearchIsland.tsx +47 -29
  21. package/plugin/generateAPIReferenceLink.ts +2 -2
  22. package/plugin/globalJs/ai-dropdown-options.ts +243 -0
  23. package/plugin/globalJs/code-snippets.ts +15 -8
  24. package/plugin/globalJs/copy.ts +94 -17
  25. package/plugin/globalJs/create-playground.shim.ts +3 -0
  26. package/plugin/globalJs/method-descriptions.ts +33 -0
  27. package/plugin/globalJs/navigation.ts +10 -29
  28. package/plugin/globalJs/playground-data.shim.ts +1 -0
  29. package/plugin/globalJs/playground-data.ts +14 -0
  30. package/plugin/helpers/generateDocsRoutes.ts +27 -0
  31. package/plugin/helpers/getDocsLanguages.ts +9 -0
  32. package/plugin/index.ts +292 -116
  33. package/plugin/languages.ts +7 -2
  34. package/plugin/loadPluginConfig.ts +155 -79
  35. package/plugin/middlewareBuilder/stainlessMiddleware.d.ts +1 -1
  36. package/plugin/react/Routing.tsx +204 -132
  37. package/plugin/referencePlaceholderUtils.ts +18 -15
  38. package/plugin/replaceSidebarPlaceholderMiddleware.ts +38 -34
  39. package/plugin/routes/Docs.astro +65 -117
  40. package/plugin/routes/DocsStatic.astro +7 -4
  41. package/plugin/routes/Overview.astro +20 -24
  42. package/plugin/routes/markdown.ts +12 -11
  43. package/plugin/{cms → sidebar-utils}/sidebar-builder.ts +30 -54
  44. package/plugin/specs/fetchSpecSSR.ts +21 -0
  45. package/plugin/specs/generateSpec.ts +50 -0
  46. package/plugin/specs/index.ts +238 -0
  47. package/plugin/{cms → specs}/worker.ts +82 -5
  48. package/plugin/vendor/preview.worker.docs.js +20928 -17830
  49. package/plugin/vendor/templates/go.md +1 -1
  50. package/plugin/vendor/templates/python.md +1 -1
  51. package/resolveSrcFile.ts +10 -0
  52. package/scripts/vendor_deps.ts +5 -5
  53. package/shared/getProsePages.ts +42 -0
  54. package/shared/getSharedLogger.ts +15 -0
  55. package/shared/terminalUtils.ts +3 -0
  56. package/shared/virtualModule.ts +54 -1
  57. package/src/content.config.ts +9 -0
  58. package/stl-docs/components/AIDropdown.tsx +63 -0
  59. package/stl-docs/components/AiChatIsland.tsx +14 -0
  60. package/stl-docs/components/{content-panel/ContentBreadcrumbs.tsx → ContentBreadcrumbs.tsx} +2 -2
  61. package/stl-docs/components/Head.astro +20 -0
  62. package/stl-docs/components/Header.astro +6 -8
  63. package/stl-docs/components/PageFrame.astro +18 -0
  64. package/stl-docs/components/PageTitle.astro +82 -0
  65. package/stl-docs/components/TableOfContents.astro +34 -0
  66. package/stl-docs/components/ThemeProvider.astro +36 -0
  67. package/stl-docs/components/ThemeSelect.astro +84 -139
  68. package/stl-docs/components/content-panel/ContentPanel.astro +16 -46
  69. package/stl-docs/components/headers/SplashMobileMenuToggle.astro +17 -1
  70. package/stl-docs/components/headers/StackedHeader.astro +29 -24
  71. package/stl-docs/components/icons/chat-gpt.tsx +2 -2
  72. package/stl-docs/components/icons/cursor.tsx +10 -0
  73. package/stl-docs/components/icons/gemini.tsx +19 -0
  74. package/stl-docs/components/icons/markdown.tsx +1 -1
  75. package/stl-docs/components/index.ts +1 -0
  76. package/stl-docs/components/mintlify-compat/Accordion.astro +7 -5
  77. package/stl-docs/components/mintlify-compat/AccordionGroup.astro +7 -3
  78. package/stl-docs/components/mintlify-compat/Columns.astro +40 -42
  79. package/stl-docs/components/mintlify-compat/Frame.astro +16 -18
  80. package/stl-docs/components/mintlify-compat/callouts/Callout.astro +1 -1
  81. package/stl-docs/components/mintlify-compat/callouts/Check.astro +1 -1
  82. package/stl-docs/components/mintlify-compat/callouts/Danger.astro +1 -1
  83. package/stl-docs/components/mintlify-compat/callouts/Info.astro +1 -1
  84. package/stl-docs/components/mintlify-compat/callouts/Note.astro +1 -1
  85. package/stl-docs/components/mintlify-compat/callouts/Tip.astro +1 -1
  86. package/stl-docs/components/mintlify-compat/callouts/Warning.astro +1 -1
  87. package/stl-docs/components/mintlify-compat/card.css +33 -35
  88. package/stl-docs/components/mintlify-compat/index.ts +2 -4
  89. package/stl-docs/components/nav-tabs/NavDropdown.astro +31 -70
  90. package/stl-docs/components/nav-tabs/NavTabs.astro +78 -80
  91. package/stl-docs/components/nav-tabs/SecondaryNavTabs.astro +15 -8
  92. package/stl-docs/components/nav-tabs/buildNavLinks.ts +3 -2
  93. package/stl-docs/components/pagination/HomeLink.astro +10 -0
  94. package/stl-docs/components/pagination/Pagination.astro +175 -0
  95. package/stl-docs/components/pagination/PaginationLinkEmphasized.astro +22 -0
  96. package/stl-docs/components/pagination/PaginationLinkQuiet.astro +13 -0
  97. package/stl-docs/components/pagination/util.ts +71 -0
  98. package/stl-docs/components/scripts.ts +1 -0
  99. package/stl-docs/components/sidebars/BaseSidebar.astro +9 -2
  100. package/stl-docs/components/sidebars/SidebarWithComponents.tsx +10 -0
  101. package/stl-docs/components/sidebars/convertAstroSidebarToStl.tsx +62 -0
  102. package/stl-docs/disableCalloutSyntax.ts +36 -0
  103. package/stl-docs/fonts.ts +186 -0
  104. package/stl-docs/index.ts +153 -50
  105. package/stl-docs/loadStlDocsConfig.ts +51 -7
  106. package/stl-docs/proseMarkdown/proseMarkdownIntegration.ts +61 -0
  107. package/stl-docs/proseMarkdown/proseMarkdownMiddleware.ts +41 -0
  108. package/stl-docs/proseMarkdown/toMarkdown.ts +158 -0
  109. package/stl-docs/proseSearchIndexing.ts +606 -0
  110. package/stl-docs/tabsMiddleware.ts +13 -4
  111. package/styles/code.css +128 -136
  112. package/styles/links.css +11 -48
  113. package/styles/method-descriptions.css +36 -0
  114. package/styles/overrides.css +49 -57
  115. package/styles/page.css +100 -59
  116. package/styles/sdk_select.css +9 -7
  117. package/styles/search.css +57 -69
  118. package/styles/sidebar.css +26 -156
  119. package/styles/{variables.css → sl-variables.css} +3 -2
  120. package/styles/stldocs-variables.css +6 -0
  121. package/styles/toc.css +41 -34
  122. package/theme.css +11 -11
  123. package/tsconfig.json +2 -5
  124. package/virtual-module.d.ts +47 -7
  125. package/components/variables.css +0 -135
  126. package/plugin/cms/client.ts +0 -62
  127. package/plugin/cms/server.ts +0 -268
  128. package/plugin/globalJs/ai-dropdown.ts +0 -57
  129. package/stl-docs/components/APIReferenceAIDropdown.tsx +0 -58
  130. package/stl-docs/components/content-panel/ProseAIDropdown.tsx +0 -55
  131. package/stl-docs/components/mintlify-compat/Step.astro +0 -58
  132. package/stl-docs/components/mintlify-compat/Steps.astro +0 -17
  133. package/styles/fonts.css +0 -68
  134. /package/{plugin/assets → assets}/fonts/geist/OFL.txt +0 -0
  135. /package/{plugin/assets → assets}/fonts/geist/geist-italic-latin-ext.woff2 +0 -0
  136. /package/{plugin/assets → assets}/fonts/geist/geist-italic-latin.woff2 +0 -0
  137. /package/{plugin/assets → assets}/fonts/geist/geist-latin-ext.woff2 +0 -0
  138. /package/{plugin/assets → assets}/fonts/geist/geist-latin.woff2 +0 -0
  139. /package/{plugin/assets → assets}/fonts/geist/geist-mono-italic-latin-ext.woff2 +0 -0
  140. /package/{plugin/assets → assets}/fonts/geist/geist-mono-italic-latin.woff2 +0 -0
  141. /package/{plugin/assets → assets}/fonts/geist/geist-mono-latin-ext.woff2 +0 -0
  142. /package/{plugin/assets → assets}/fonts/geist/geist-mono-latin.woff2 +0 -0
package/plugin/index.ts CHANGED
@@ -1,33 +1,46 @@
1
1
  import react from '@astrojs/react';
2
2
  import type { StarlightPlugin } from '@astrojs/starlight/types';
3
3
  import type { AstroIntegration } from 'astro';
4
+ import type { BundledTheme } from 'shiki';
4
5
  import { config } from 'dotenv';
5
- import getPort from 'get-port';
6
- import { startDevServer } from './cms/server';
7
6
  import { buildAlgoliaIndex } from './buildAlgoliaIndex';
8
7
  import {
9
8
  getAPIReferencePlaceholderItemFromSidebarConfig,
10
9
  makePlaceholderItems,
11
10
  } from './referencePlaceholderUtils';
12
- import type {
13
- GeneratedSidebarConfig,
14
- ReferenceSidebarConfigGenerateOptions,
15
- ReferenceSidebarConfigItem,
16
- } from './cms/sidebar-builder';
11
+ import {
12
+ SidebarConfigItemsBuilder,
13
+ toStarlightSidebar,
14
+ type GeneratedSidebarConfig,
15
+ type ReferenceSidebarConfigGenerateOptions,
16
+ type ReferenceSidebarConfigItem,
17
+ } from './sidebar-utils/sidebar-builder';
17
18
  import {
18
19
  parseStarlightPluginConfig,
19
20
  type NormalizedStainlessStarlightConfig,
20
21
  type SomeStainlessStarlightUserConfig,
21
- type SpecRetrieverConfig,
22
22
  } from './loadPluginConfig';
23
- import { buildVirtualModuleString } from '../shared/virtualModule';
23
+ import { buildVirtualModuleString, makeAsyncVirtualModPlugin } from '../shared/virtualModule';
24
+ import type * as StlStarlightVirtualModule from 'virtual:stl-starlight-virtual-module';
24
25
  import path from 'path';
25
26
  import fs from 'fs';
27
+ import { getSharedLogger } from '../shared/getSharedLogger';
28
+ import { resolveSrcFile } from '../resolveSrcFile';
29
+ import { mkdir, writeFile } from 'fs/promises';
30
+ import { fileURLToPath } from 'url';
31
+ import prebundleWorkers from 'vite-plugin-prebundle-workers';
32
+ import { SpecLoader, startSpecLoader } from './specs';
33
+
34
+ import type * as ReferenceSidebarsVirtualModule from 'virtual:stl-starlight-reference-sidebars';
35
+ import { getDocsLanguages } from './helpers/getDocsLanguages';
36
+ import { generateMissingRouteList } from '@stainless-api/docs-ui/routing';
26
37
 
27
38
  export { generateAPILink } from './generateAPIReferenceLink';
28
39
  export type { ReferenceSidebarConfigItem };
29
40
 
30
- config();
41
+ config({
42
+ quiet: true,
43
+ });
31
44
 
32
45
  let sidebarIdCounter = 0;
33
46
 
@@ -78,132 +91,216 @@ export function generateAPIReferenceItems(
78
91
  return makePlaceholderItems(id);
79
92
  }
80
93
 
81
- function tmpGetCMSServerConfig(specRetrieverConfig: SpecRetrieverConfig) {
82
- if (specRetrieverConfig.kind === 'external_spec_server') {
83
- throw new Error('External spec server is not yet supported');
84
- }
85
- if (specRetrieverConfig.kind === 'local_spec_server_with_files') {
86
- if (!specRetrieverConfig.apiKey) {
87
- throw new Error('API key is required');
88
- }
89
- return {
90
- apiKey: specRetrieverConfig.apiKey,
91
- version: specRetrieverConfig.version,
92
- devPaths: specRetrieverConfig.devPaths,
93
- };
94
- }
95
-
96
- if (specRetrieverConfig.kind === 'local_spec_server_with_remote_files') {
97
- return {
98
- apiKey: specRetrieverConfig.apiKey,
99
- version: specRetrieverConfig.version,
100
- devPaths: {
101
- oasPath: undefined,
102
- configPath: undefined,
103
- },
104
- };
105
- }
106
- throw new Error('Invalid spec retriever config');
107
- }
108
-
109
94
  async function stlStarlightAstroIntegration(
110
95
  pluginConfig: NormalizedStainlessStarlightConfig,
111
96
  ): Promise<AstroIntegration> {
112
97
  const virtualId = `virtual:stl-starlight-virtual-module`;
113
98
  // The '\0' prefix tells Vite “this is a virtual module” and prevents it from being resolved again.
114
99
  const resolvedId = `\0${virtualId}`;
100
+ let playgroundsBase: string | undefined;
101
+ let buildPlaygrounds;
102
+ let astroBase = '/';
103
+
104
+ let specLoader: SpecLoader | undefined;
105
+ async function getSpec() {
106
+ if (!specLoader) throw new Error('Expected spec loader to exist');
107
+ const result = await specLoader.specPromise;
108
+ return {
109
+ spec: result.spec.data,
110
+ auth: result.spec.auth,
111
+ };
112
+ }
115
113
 
116
- const CMS_PORT = await getPort();
117
-
118
- const { apiKey, version, devPaths } = tmpGetCMSServerConfig(pluginConfig.specRetrieverConfig);
119
-
120
- const cmsServer = await startDevServer({
121
- port: CMS_PORT,
122
- apiKey,
123
- version,
124
- devPaths,
125
- getGeneratedSidebarConfig: (id: number) => {
126
- const config = sidebarConfigs.get(id);
127
- if (!config) {
128
- return null;
129
- }
130
- return config;
131
- },
132
- });
114
+ let building: Promise<void> | undefined;
115
+ let reportError: ((message: string) => void) | null = null;
116
+ let collectedErrors: string[] | null = null;
117
+
118
+ function startPlaygroundsBuild(playgroundsCachePath: string) {
119
+ if (!pluginConfig.experimentalPlaygrounds) return;
120
+ if (building) return building;
121
+ return (building = (async () => {
122
+ const { spec, auth } = await getSpec();
123
+
124
+ const langs = getDocsLanguages(spec, pluginConfig.excludeLanguages);
125
+
126
+ await buildPlaygrounds!({
127
+ spec,
128
+ langs,
129
+ auth,
130
+ playPath: playgroundsCachePath,
131
+ reportError(message: string) {
132
+ (reportError ?? console.error)(message);
133
+ },
134
+ });
135
+ })());
136
+ }
133
137
 
134
138
  return {
135
139
  name: 'stl-starlight-astro',
136
140
  hooks: {
137
- 'astro:config:setup': async ({ injectRoute, updateConfig, logger, command, config: astroConfig }) => {
141
+ 'astro:config:setup': async ({
142
+ injectRoute,
143
+ updateConfig,
144
+ logger: localLogger,
145
+ command,
146
+ config: astroConfig,
147
+ createCodegenDir,
148
+ }) => {
149
+ const logger = getSharedLogger({ fallback: localLogger });
138
150
  const projectDir = astroConfig.root.pathname;
151
+ astroBase = astroConfig.base;
152
+
153
+ specLoader = await startSpecLoader(pluginConfig, logger, createCodegenDir());
154
+ specLoader.specPromise.then((specResult) => {
155
+ if (specResult.result === 'generated') {
156
+ logger.info(`Generated new SDKJSON`);
157
+ } else if (specResult.result === 'exists') {
158
+ logger.info(`Loaded SDKJSON`);
159
+ }
160
+ });
161
+
162
+ reportError = (message: string) => logger.error(message);
163
+
164
+ if (pluginConfig.experimentalPlaygrounds) {
165
+ playgroundsBase = pluginConfig.experimentalPlaygrounds.playgroundsBase;
166
+ }
139
167
 
140
168
  const middlewareFile = path.join(projectDir, 'middleware.stainless.ts');
141
169
 
142
170
  let vmMiddlewareExport = 'export const MIDDLEWARE = {};';
143
171
  if (fs.existsSync(middlewareFile)) {
144
- logger.info(`Loading middleware from ${middlewareFile}`);
172
+ logger.debug(`Loading middleware from ${middlewareFile}`);
145
173
  vmMiddlewareExport = `export { default as MIDDLEWARE } from '${middlewareFile}';`;
146
174
  }
147
175
 
148
176
  injectRoute({
149
- pattern: `${pluginConfig.basePath}/[...slug].md`,
150
- entrypoint: '@stainless-api/docs/MarkdownRoute',
151
- prerender: command === 'build',
177
+ pattern: `${pluginConfig.basePath}/[...slug]/index.md`,
178
+ entrypoint: resolveSrcFile('/plugin/routes/markdown.ts'),
179
+ prerender: pluginConfig.experimentalPrerender ? command === 'build' : false,
152
180
  });
153
181
 
154
- const astroFile = command === 'build' ? 'DocsStaticRoute' : 'DocsRoute';
182
+ const astroFile = command === 'build' ? 'DocsStatic' : 'Docs';
155
183
  injectRoute({
156
184
  pattern: `${pluginConfig.basePath}/[...slug]`,
157
- // in prod I think this points to @stainless-starlight/components/docs.astro
158
- entrypoint: `@stainless-api/docs/${astroFile}`,
159
- prerender: command === 'build',
185
+ entrypoint: resolveSrcFile(`/plugin/routes/${astroFile}.astro`),
186
+ prerender: pluginConfig.experimentalPrerender ? command === 'build' : false,
160
187
  });
161
188
 
162
189
  injectRoute({
163
190
  pattern: pluginConfig.basePath,
164
- entrypoint: '@stainless-api/docs/OverviewRoute',
165
- prerender: command === 'build',
191
+ entrypoint: resolveSrcFile('/plugin/routes/Overview.astro'),
192
+ prerender: pluginConfig.experimentalPrerender ? command === 'build' : false,
166
193
  });
167
194
 
168
195
  updateConfig({
169
196
  vite: {
170
- ssr: {
171
- noExternal: ['@stainless-api/ui-primitives'],
172
- },
173
- optimizeDeps: { include: ['@stainless-api/ui-primitives'] },
174
197
  plugins: [
198
+ makeAsyncVirtualModPlugin<typeof ReferenceSidebarsVirtualModule>(
199
+ 'virtual:stl-starlight-reference-sidebars',
200
+ async () => {
201
+ const { spec } = await getSpec();
202
+ const languages = getDocsLanguages(spec, pluginConfig.excludeLanguages);
203
+
204
+ const sidebars = [...sidebarConfigs.entries()]
205
+ // produce all { id, language } combos with the attached config
206
+ // flattens to one item per language * id combo
207
+ .flatMap(([id, config]) =>
208
+ languages.map((language) => ({
209
+ id,
210
+ config,
211
+ language,
212
+ })),
213
+ )
214
+ // produce a sidebar for each
215
+ // later we will .find() the sidebar that matches the (id, language)
216
+ .map(({ id, config, language }) => {
217
+ const configItemsBuilder = new SidebarConfigItemsBuilder(
218
+ spec,
219
+ language,
220
+ config.options,
221
+ );
222
+
223
+ let userSidebarConfig = configItemsBuilder.generateItems();
224
+ if (config.transformFn) {
225
+ const transformedSidebarConfig = config.transformFn(userSidebarConfig, language);
226
+ if (transformedSidebarConfig) userSidebarConfig = transformedSidebarConfig;
227
+ }
228
+
229
+ return {
230
+ id,
231
+ language,
232
+ // this has to run multpile times because it depends on the
233
+ // userSidebarConfig (which is per-id) and the language
234
+ entries: toStarlightSidebar({
235
+ basePath: path.posix.join(astroBase, pluginConfig.basePath), // TODO, is this right?
236
+ spec,
237
+ entries: userSidebarConfig,
238
+ currentLanguage: language,
239
+ }),
240
+ };
241
+ });
242
+
243
+ return { sidebars };
244
+ },
245
+ ),
246
+ specLoader.vitePlugin,
175
247
  {
176
248
  name: 'stl-starlight-vite',
177
- configureServer(server) {
178
- for (const filePath of Object.values(devPaths)) {
179
- if (!filePath) {
180
- continue;
181
- }
182
- server.watcher.add(filePath);
249
+ buildStart() {
250
+ building = undefined;
251
+ },
252
+ async configureServer(server) {
253
+ if (playgroundsBase) {
254
+ buildPlaygrounds = (await server.ssrLoadModule(playgroundsBase + '/src/build.ts'))
255
+ .buildPlaygrounds;
183
256
  }
184
257
 
185
- server.watcher.on('change', async (changed) => {
186
- if (Object.values(devPaths).includes(changed)) {
187
- logger.info(`${changed} changed, reloading...`);
188
- cmsServer.invalidate();
189
- server.hot.send({
190
- type: 'full-reload',
191
- path: '*',
192
- });
193
- }
194
- });
258
+ // TODO: eventually - re-add support for watching local input changes (eg. reloading when OAS/config files change)
195
259
  },
196
260
  resolveId(id) {
197
261
  if (id === virtualId) {
198
262
  return resolvedId;
199
263
  }
264
+ if (id === 'virtual:stl-playground/unstable-update-language') {
265
+ return resolveSrcFile('plugin/languages.ts');
266
+ }
267
+ if (id === 'virtual:stl-playground/create') {
268
+ return fileURLToPath(
269
+ new URL(
270
+ pluginConfig.experimentalPlaygrounds
271
+ ? path.join(playgroundsBase!, '/src/create.tsx')
272
+ : './globalJs/create-playground.shim.ts',
273
+ import.meta.url,
274
+ ),
275
+ );
276
+ }
277
+ if (id === 'virtual:stl-playground/data') {
278
+ return fileURLToPath(
279
+ new URL(
280
+ pluginConfig.experimentalPlaygrounds
281
+ ? './globalJs/playground-data.ts'
282
+ : './globalJs/playground-data.shim.ts',
283
+ import.meta.url,
284
+ ),
285
+ );
286
+ }
287
+ if (id.startsWith('virtual:stl-playground/')) {
288
+ const result = path.join(
289
+ this.environment.getTopLevelConfig().cacheDir,
290
+ 'stl-playground',
291
+ id.slice('virtual:stl-playground/'.length),
292
+ );
293
+ const config = this.environment.getTopLevelConfig();
294
+ return startPlaygroundsBuild(path.join(config.cacheDir, 'stl-playground'))?.then(
295
+ () => result,
296
+ );
297
+ }
200
298
  },
201
299
  load(id) {
202
300
  if (id === resolvedId) {
203
301
  return [
204
302
  buildVirtualModuleString({
205
- BASE_PATH: pluginConfig.basePath,
206
- CMS_PORT,
303
+ RESOLVED_API_REFERENCE_PATH: path.posix.join(astroConfig.base, pluginConfig.basePath),
207
304
  EXCLUDE_LANGUAGES: pluginConfig.excludeLanguages,
208
305
  DEFAULT_LANGUAGE: pluginConfig.defaultLanguage,
209
306
  BREADCRUMB_CONFIG: pluginConfig.breadcrumbs,
@@ -211,21 +308,76 @@ async function stlStarlightAstroIntegration(
211
308
  HIGHLIGHT_THEMES: pluginConfig.highlighting.themes,
212
309
  CONTENT_PANEL_LAYOUT: pluginConfig.contentPanel.layout,
213
310
  EXPERIMENTAL_COLLAPSIBLE_SNIPPETS: pluginConfig.experimentalCollapsibleSnippets,
311
+ EXPERIMENTAL_COLLAPSIBLE_METHOD_DESCRIPTIONS:
312
+ pluginConfig.experimentalCollapsibleMethodDescriptions,
214
313
  PROPERTY_SETTINGS: pluginConfig.propertySettings,
215
- SEARCH: pluginConfig.search,
216
- INCLUDE_AI_DROPDOWN_OPTIONS: pluginConfig.includeAIDropdownOptions,
217
- }),
314
+ ENABLE_CONTEXT_MENU: pluginConfig.contextMenu,
315
+ EXPERIMENTAL_PLAYGROUNDS: !!pluginConfig.experimentalPlaygrounds,
316
+ EXPERIMENTAL_REQUEST_BUILDER: pluginConfig.experimentalRequestBuilder,
317
+ STAINLESS_PROJECT: pluginConfig.specInputs.project,
318
+ } satisfies Omit<typeof StlStarlightVirtualModule, 'MIDDLEWARE'>),
218
319
  vmMiddlewareExport,
219
320
  ].join('\n');
220
321
  }
221
322
  },
222
323
  },
324
+ prebundleWorkers({
325
+ include: [
326
+ '**/typescript/runner*',
327
+ '**/typescript/worker*',
328
+ '**/pyright.worker*',
329
+ '**/python/pyodide*',
330
+ ],
331
+ configureEsBuild(_, opts) {
332
+ opts.external ??= [];
333
+ opts.external.push('url');
334
+ opts.loader ??= {};
335
+ opts.loader['.wasm'] = 'dataurl';
336
+ return opts;
337
+ },
338
+ }),
223
339
  ],
224
340
  },
225
341
  });
226
342
  },
227
- 'astro:server:done': async () => {
228
- await cmsServer.destroy();
343
+ 'astro:build:start'({ logger }) {
344
+ collectedErrors = [];
345
+ reportError = (message: string) => {
346
+ logger.error(message);
347
+ collectedErrors!.push(message);
348
+ };
349
+ },
350
+ 'astro:build:done': async ({ dir, logger }) => {
351
+ const dist = fileURLToPath(dir);
352
+ const stainlessDir = path.join(dist, '_stainless');
353
+ await mkdir(stainlessDir, { recursive: true });
354
+ if (collectedErrors) {
355
+ for (const error of collectedErrors) {
356
+ logger.error(error);
357
+ }
358
+ collectedErrors = null;
359
+ }
360
+
361
+ const manifest = {
362
+ astroBase,
363
+ };
364
+ await writeFile(path.join(stainlessDir, 'stl-manifest.json'), JSON.stringify(manifest, null, 2));
365
+
366
+ // Generate a list of missing API routes to enable graceful handling of unimplemented SDK methods.
367
+ // When users switch languages in the docs, some API methods may not be implemented in the target SDK.
368
+ // Instead of showing a generic 404, we statically generate pages for these routes and mark them
369
+ // in this file so Cloudflare can serve them with a 404 status. These pages display helpful information
370
+ // about the missing method and provide links to SDKs where it is available.
371
+ const { spec } = await getSpec();
372
+ const missingRoutes = generateMissingRouteList({
373
+ spec,
374
+ basePath: path.posix.join(astroBase, pluginConfig.basePath),
375
+ });
376
+ await mkdir(stainlessDir, { recursive: true });
377
+ await writeFile(
378
+ path.join(stainlessDir, 'missing-routes.json'),
379
+ JSON.stringify(missingRoutes, null, 2),
380
+ );
229
381
  },
230
382
  },
231
383
  };
@@ -242,17 +394,23 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
242
394
  command,
243
395
  config: starlightConfig,
244
396
  astroConfig,
245
- logger,
397
+ logger: localLogger,
246
398
  }) => {
247
399
  if (command !== 'build' && command !== 'dev') {
248
400
  return;
249
401
  }
250
402
 
403
+ const logger = getSharedLogger({ fallback: localLogger });
404
+
251
405
  const configParseResult = parseStarlightPluginConfig(someUserConfig, command);
252
406
  if (configParseResult.result === 'error') {
253
- logger.error(configParseResult.message);
407
+ const errorLines = configParseResult.message.split('\n');
408
+ for (const line of errorLines) {
409
+ logger.error(line);
410
+ }
254
411
  process.exit(1);
255
412
  }
413
+
256
414
  const config = configParseResult.config;
257
415
 
258
416
  const isReactLoaded = astroConfig.integrations.find(({ name }) => name === '@astrojs/react');
@@ -261,42 +419,60 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
261
419
  addIntegration(react());
262
420
  }
263
421
 
264
- if (
265
- command === 'build' &&
266
- config.specRetrieverConfig.kind === 'local_spec_server_with_remote_files'
267
- ) {
422
+ if ('apiKey' in config.specInputs) {
423
+ if (!config.specInputs.apiKey) {
424
+ logger.info(`Stainless credentials not loaded`);
425
+ } else if (config.specInputs.apiKey.source === 'explicit-config') {
426
+ logger.info(`Stainless credentials loaded from user config`);
427
+ } else if (config.specInputs.apiKey.source === 'environment-variable') {
428
+ logger.info('Stainless credentials loaded from `STAINLESS_API_KEY` environment variable');
429
+ } else if (config.specInputs.apiKey.source === 'cli') {
430
+ logger.info('Stainless credentials loaded from `stl` CLI');
431
+ }
432
+ }
433
+
434
+ if (command === 'build' && config.specInputs.kind === 'stainless_api_inputs') {
268
435
  await buildAlgoliaIndex({
269
- version: config.specRetrieverConfig.version,
270
- apiKey: config.specRetrieverConfig.apiKey,
436
+ stainlessProject: config.specInputs.project,
437
+ branch: config.specInputs.branch,
438
+ apiKey: config.specInputs.apiKey.value,
439
+ logger,
271
440
  });
272
441
  }
273
442
 
274
- addIntegration(await stlStarlightAstroIntegration(config));
275
-
276
443
  if (starlightConfig.sidebar) {
277
444
  // for pagination (https://starlight.astro.build/reference/configuration/#pagination) to work correctly
278
445
  // update the placeholder link to be correct
279
- const item = getAPIReferencePlaceholderItemFromSidebarConfig(starlightConfig.sidebar);
280
- if (item && typeof item === 'object' && 'link' in item) {
281
- item.link = config.basePath;
446
+ for (const placeholder of getAPIReferencePlaceholderItemFromSidebarConfig(
447
+ starlightConfig.sidebar,
448
+ )) {
449
+ if (placeholder && typeof placeholder === 'object' && 'link' in placeholder) {
450
+ placeholder.link = config.basePath;
451
+ }
282
452
  }
283
453
  }
284
454
 
285
- const currentExpressiveCode =
455
+ addIntegration(await stlStarlightAstroIntegration(config));
456
+
457
+ const expressiveCodeConfig =
286
458
  typeof starlightConfig.expressiveCode === 'object' ? starlightConfig.expressiveCode : {};
287
- updateConfig({
288
- expressiveCode: {
289
- ...currentExpressiveCode,
290
- themes: [...(currentExpressiveCode.themes || []), 'github-light', 'github-dark'],
291
- },
292
- });
459
+
460
+ const themes = expressiveCodeConfig.themes
461
+ ? (expressiveCodeConfig.themes as BundledTheme[])
462
+ : (['github-light', 'github-dark'] as BundledTheme[]);
293
463
 
294
464
  updateConfig({
295
465
  sidebar: starlightConfig.sidebar,
466
+ ...(expressiveCodeConfig && {
467
+ expressiveCode: {
468
+ ...expressiveCodeConfig,
469
+ themes,
470
+ },
471
+ }),
296
472
  });
297
473
 
298
474
  addRouteMiddleware({
299
- entrypoint: '@stainless-api/docs/replaceSidebarPlaceholderMiddleware',
475
+ entrypoint: resolveSrcFile('/plugin/replaceSidebarPlaceholderMiddleware.ts'),
300
476
  order: 'post',
301
477
  });
302
478
  },
@@ -305,5 +481,5 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
305
481
  }
306
482
 
307
483
  // Additional exports we want for Stainless <-> docs integration.
308
- export { parseStainlessPath } from '@stainless-api/docs-ui/src/routing';
309
- export { renderMarkdown } from '@stainless-api/docs-ui/src/markdown';
484
+ export { parseStainlessPath } from '@stainless-api/docs-ui/routing';
485
+ 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,8 @@ 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';
11
+ import CLIIcon from './assets/languages/cli.svg';
10
12
 
11
13
  export const Languages: Record<
12
14
  DocsLanguage,
@@ -29,6 +31,9 @@ export const Languages: Record<
29
31
  http: { name: 'HTTP', icon: CurlIcon, alt: 'HTTP logo' },
30
32
  terraform: { name: 'Terraform', icon: TerraformIcon, alt: 'Terraform logo' },
31
33
  ruby: { name: 'Ruby', icon: RubyIcon, alt: 'Ruby logo' },
34
+ csharp: { name: 'C#', icon: CSharpIcon, alt: 'C# logo' },
35
+ cli: { name: 'CLI Tool', icon: CLIIcon, alt: 'CLI logo' },
36
+ php: { name: 'PHP', icon: CSharpIcon, alt: 'PHP logo' }, // TODO update PHP icon
32
37
  };
33
38
 
34
39
  export function generatePrefix(basePath: string, language: string) {
@@ -44,7 +49,7 @@ export function applyLanguageToLinks(basePath?: string, defaultLanguage?: string
44
49
  `[data-stldocs-overview],[data-stldocs-method],a.nav-link[href^='${basePath}']`,
45
50
  );
46
51
 
47
- for (var link of links) {
52
+ for (const link of links) {
48
53
  const href = link.getAttribute('href');
49
54
  const prefix = generatePrefix(basePath, language);
50
55
  if (href?.startsWith(basePath) && !href?.startsWith(prefix)) {