@stainless-api/docs 0.1.0-beta.1 → 0.1.0-beta.100

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