@stainless-api/docs 0.1.0-beta.12 → 0.1.0-beta.120

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