@stainless-api/docs 0.1.0-beta.11 → 0.1.0-beta.110

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