@stainless-api/docs 0.1.0-beta.7 → 0.1.0-beta.70

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 (120) hide show
  1. package/CHANGELOG.md +554 -0
  2. package/README.md +1 -1
  3. package/eslint-suppressions.json +52 -0
  4. package/locals.d.ts +17 -0
  5. package/package.json +51 -40
  6. package/plugin/assets/languages/csharp.svg +1 -0
  7. package/plugin/buildAlgoliaIndex.ts +32 -7
  8. package/plugin/cms/server.ts +130 -58
  9. package/plugin/cms/sidebar-builder.ts +7 -26
  10. package/plugin/cms/worker.ts +83 -5
  11. package/plugin/components/MethodDescription.tsx +54 -0
  12. package/plugin/components/SDKSelect.astro +7 -87
  13. package/plugin/components/SnippetCode.tsx +53 -8
  14. package/plugin/components/search/SearchAlgolia.astro +45 -28
  15. package/plugin/components/search/SearchIsland.tsx +38 -24
  16. package/plugin/create-playground.shim.tsx +3 -0
  17. package/plugin/generateAPIReferenceLink.ts +2 -2
  18. package/plugin/globalJs/ai-dropdown-options.ts +243 -0
  19. package/plugin/globalJs/code-snippets.ts +15 -8
  20. package/plugin/globalJs/copy.ts +81 -16
  21. package/plugin/globalJs/method-descriptions.ts +33 -0
  22. package/plugin/globalJs/navigation.ts +7 -4
  23. package/plugin/helpers/generateDocsRoutes.ts +27 -0
  24. package/plugin/index.ts +178 -35
  25. package/plugin/languages.ts +5 -2
  26. package/plugin/loadPluginConfig.ts +121 -32
  27. package/plugin/middlewareBuilder/stainlessMiddleware.d.ts +1 -1
  28. package/plugin/react/Routing.tsx +208 -129
  29. package/plugin/referencePlaceholderUtils.ts +1 -1
  30. package/plugin/replaceSidebarPlaceholderMiddleware.ts +5 -1
  31. package/plugin/routes/Docs.astro +62 -89
  32. package/plugin/routes/DocsStatic.astro +1 -1
  33. package/plugin/routes/Overview.astro +10 -16
  34. package/plugin/routes/markdown.ts +9 -8
  35. package/plugin/vendor/preview.worker.docs.js +19768 -17702
  36. package/plugin/vendor/templates/go.md +1 -1
  37. package/plugin/vendor/templates/python.md +1 -1
  38. package/resolveSrcFile.ts +10 -0
  39. package/scripts/vendor_deps.ts +5 -5
  40. package/shared/getProsePages.ts +42 -0
  41. package/shared/getSharedLogger.ts +15 -0
  42. package/shared/terminalUtils.ts +3 -0
  43. package/src/content.config.ts +9 -0
  44. package/stl-docs/components/AIDropdown.tsx +63 -0
  45. package/stl-docs/components/AiChatIsland.tsx +14 -0
  46. package/stl-docs/components/{content-panel/ContentBreadcrumbs.tsx → ContentBreadcrumbs.tsx} +10 -18
  47. package/stl-docs/components/Head.astro +16 -0
  48. package/stl-docs/components/Header.astro +6 -8
  49. package/stl-docs/components/PageFrame.astro +18 -0
  50. package/stl-docs/components/PageTitle.astro +82 -0
  51. package/stl-docs/components/TableOfContents.astro +34 -0
  52. package/stl-docs/components/ThemeProvider.astro +36 -0
  53. package/stl-docs/components/ThemeSelect.astro +84 -139
  54. package/stl-docs/components/content-panel/ContentPanel.astro +16 -25
  55. package/stl-docs/components/headers/SplashMobileMenuToggle.astro +17 -1
  56. package/stl-docs/components/headers/StackedHeader.astro +29 -24
  57. package/stl-docs/components/icons/chat-gpt.tsx +17 -0
  58. package/stl-docs/components/icons/claude.tsx +10 -0
  59. package/stl-docs/components/icons/cursor.tsx +10 -0
  60. package/stl-docs/components/icons/gemini.tsx +19 -0
  61. package/stl-docs/components/icons/markdown.tsx +10 -0
  62. package/stl-docs/components/index.ts +1 -0
  63. package/stl-docs/components/mintlify-compat/Accordion.astro +7 -5
  64. package/stl-docs/components/mintlify-compat/AccordionGroup.astro +7 -3
  65. package/stl-docs/components/mintlify-compat/Columns.astro +40 -42
  66. package/stl-docs/components/mintlify-compat/Frame.astro +16 -18
  67. package/stl-docs/components/mintlify-compat/callouts/Callout.astro +1 -1
  68. package/stl-docs/components/mintlify-compat/callouts/Check.astro +1 -1
  69. package/stl-docs/components/mintlify-compat/callouts/Danger.astro +1 -1
  70. package/stl-docs/components/mintlify-compat/callouts/Info.astro +1 -1
  71. package/stl-docs/components/mintlify-compat/callouts/Note.astro +1 -1
  72. package/stl-docs/components/mintlify-compat/callouts/Tip.astro +1 -1
  73. package/stl-docs/components/mintlify-compat/callouts/Warning.astro +1 -1
  74. package/stl-docs/components/mintlify-compat/card.css +33 -35
  75. package/stl-docs/components/mintlify-compat/index.ts +2 -4
  76. package/stl-docs/components/nav-tabs/NavDropdown.astro +31 -70
  77. package/stl-docs/components/nav-tabs/NavTabs.astro +78 -80
  78. package/stl-docs/components/nav-tabs/SecondaryNavTabs.astro +15 -8
  79. package/stl-docs/components/nav-tabs/buildNavLinks.ts +3 -2
  80. package/stl-docs/components/pagination/HomeLink.astro +10 -0
  81. package/stl-docs/components/pagination/Pagination.astro +175 -0
  82. package/stl-docs/components/pagination/PaginationLinkEmphasized.astro +22 -0
  83. package/stl-docs/components/pagination/PaginationLinkQuiet.astro +13 -0
  84. package/stl-docs/components/pagination/util.ts +71 -0
  85. package/stl-docs/components/scripts.ts +1 -0
  86. package/stl-docs/disableCalloutSyntax.ts +36 -0
  87. package/stl-docs/index.ts +141 -50
  88. package/stl-docs/loadStlDocsConfig.ts +45 -5
  89. package/stl-docs/proseMarkdown/proseMarkdownIntegration.ts +61 -0
  90. package/stl-docs/proseMarkdown/proseMarkdownMiddleware.ts +39 -0
  91. package/stl-docs/proseMarkdown/toMarkdown.ts +158 -0
  92. package/stl-docs/proseSearchIndexing.ts +450 -0
  93. package/stl-docs/tabsMiddleware.ts +11 -3
  94. package/styles/code.css +108 -140
  95. package/styles/fonts.css +32 -17
  96. package/styles/links.css +11 -48
  97. package/styles/method-descriptions.css +36 -0
  98. package/styles/overrides.css +48 -60
  99. package/styles/page.css +92 -52
  100. package/styles/sdk_select.css +9 -7
  101. package/styles/search.css +56 -69
  102. package/styles/sidebar.css +211 -131
  103. package/styles/{variables.css → sl-variables.css} +3 -2
  104. package/styles/stldocs-variables.css +6 -0
  105. package/styles/toc.css +41 -34
  106. package/theme.css +10 -10
  107. package/tsconfig.json +2 -5
  108. package/virtual-module.d.ts +26 -4
  109. package/components/variables.css +0 -135
  110. package/stl-docs/components/mintlify-compat/Step.astro +0 -58
  111. package/stl-docs/components/mintlify-compat/Steps.astro +0 -17
  112. /package/{plugin/assets → assets}/fonts/geist/OFL.txt +0 -0
  113. /package/{plugin/assets → assets}/fonts/geist/geist-italic-latin-ext.woff2 +0 -0
  114. /package/{plugin/assets → assets}/fonts/geist/geist-italic-latin.woff2 +0 -0
  115. /package/{plugin/assets → assets}/fonts/geist/geist-latin-ext.woff2 +0 -0
  116. /package/{plugin/assets → assets}/fonts/geist/geist-latin.woff2 +0 -0
  117. /package/{plugin/assets → assets}/fonts/geist/geist-mono-italic-latin-ext.woff2 +0 -0
  118. /package/{plugin/assets → assets}/fonts/geist/geist-mono-italic-latin.woff2 +0 -0
  119. /package/{plugin/assets → assets}/fonts/geist/geist-mono-latin-ext.woff2 +0 -0
  120. /package/{plugin/assets → assets}/fonts/geist/geist-mono-latin.woff2 +0 -0
package/plugin/index.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import react from '@astrojs/react';
2
2
  import type { StarlightPlugin } from '@astrojs/starlight/types';
3
- import type { AstroIntegration } from 'astro';
3
+ import type { AstroIntegration, AstroIntegrationLogger } from 'astro';
4
+ import type { BundledTheme } from 'shiki';
4
5
  import { config } from 'dotenv';
5
6
  import getPort from 'get-port';
6
- import { startDevServer } from './cms/server';
7
+ import { startDevServer, type SpecResp } from './cms/server';
7
8
  import { buildAlgoliaIndex } from './buildAlgoliaIndex';
8
9
  import {
9
10
  getAPIReferencePlaceholderItemFromSidebarConfig,
@@ -21,13 +22,21 @@ import {
21
22
  type SpecRetrieverConfig,
22
23
  } from './loadPluginConfig';
23
24
  import { buildVirtualModuleString } from '../shared/virtualModule';
25
+ import type * as StlStarlightVirtualModule from 'virtual:stl-starlight-virtual-module';
24
26
  import path from 'path';
25
27
  import fs from 'fs';
28
+ import { getSharedLogger } from '../shared/getSharedLogger';
29
+ import { resolveSrcFile } from '../resolveSrcFile';
30
+ import { mkdir } from 'fs/promises';
31
+ import { fileURLToPath } from 'url';
32
+ import prebundleWorkers from 'vite-plugin-prebundle-workers';
26
33
 
27
34
  export { generateAPILink } from './generateAPIReferenceLink';
28
35
  export type { ReferenceSidebarConfigItem };
29
36
 
30
- config();
37
+ config({
38
+ quiet: true,
39
+ });
31
40
 
32
41
  let sidebarIdCounter = 0;
33
42
 
@@ -108,20 +117,24 @@ function tmpGetCMSServerConfig(specRetrieverConfig: SpecRetrieverConfig) {
108
117
 
109
118
  async function stlStarlightAstroIntegration(
110
119
  pluginConfig: NormalizedStainlessStarlightConfig,
120
+ stlStarlightPluginLogger: AstroIntegrationLogger,
111
121
  ): Promise<AstroIntegration> {
112
122
  const virtualId = `virtual:stl-starlight-virtual-module`;
113
123
  // The '\0' prefix tells Vite “this is a virtual module” and prevents it from being resolved again.
114
124
  const resolvedId = `\0${virtualId}`;
125
+ let playgroundsBase: string | undefined;
126
+ let buildPlaygrounds;
115
127
 
116
128
  const CMS_PORT = await getPort();
117
129
 
118
130
  const { apiKey, version, devPaths } = tmpGetCMSServerConfig(pluginConfig.specRetrieverConfig);
119
131
 
120
- const cmsServer = await startDevServer({
132
+ const cmsServer = startDevServer({
121
133
  port: CMS_PORT,
122
- apiKey,
134
+ apiKey: apiKey.value,
123
135
  version,
124
136
  devPaths,
137
+ logger: stlStarlightPluginLogger,
125
138
  getGeneratedSidebarConfig: (id: number) => {
126
139
  const config = sidebarConfigs.get(id);
127
140
  if (!config) {
@@ -131,50 +144,98 @@ async function stlStarlightAstroIntegration(
131
144
  },
132
145
  });
133
146
 
147
+ let building: Promise<void> | undefined;
148
+ let reportError: ((message: string) => void) | null = null;
149
+ let collectedErrors: string[] | null = null;
150
+
151
+ function startPlaygroundsBuild(playgroundsCachePath: string) {
152
+ if (!pluginConfig.experimentalPlaygrounds) return;
153
+ if (building) return building;
154
+ return (building = (async () => {
155
+ const { data: spec, auth } = (await (
156
+ await fetch(`http://localhost:${CMS_PORT}/retrieve_spec`, {
157
+ method: 'POST',
158
+ body: JSON.stringify({}),
159
+ })
160
+ ).json()) as SpecResp;
161
+ if (!spec || !auth) throw new Error('expected spec');
162
+
163
+ const langs = (spec.docs?.languages ?? ['http'])
164
+ .filter((lang) => lang !== 'terraform')
165
+ .filter((lang) => !pluginConfig.excludeLanguages?.includes(lang));
166
+
167
+ await buildPlaygrounds!({
168
+ spec,
169
+ langs,
170
+ auth,
171
+ playPath: playgroundsCachePath,
172
+ reportError(message: string) {
173
+ (reportError ?? console.error)(message);
174
+ },
175
+ });
176
+ })());
177
+ }
178
+
134
179
  return {
135
180
  name: 'stl-starlight-astro',
136
181
  hooks: {
137
- 'astro:config:setup': async ({ injectRoute, updateConfig, logger, command, config: astroConfig }) => {
182
+ 'astro:config:setup': async ({
183
+ injectRoute,
184
+ updateConfig,
185
+ logger: localLogger,
186
+ command,
187
+ config: astroConfig,
188
+ }) => {
189
+ const logger = getSharedLogger({ fallback: localLogger });
138
190
  const projectDir = astroConfig.root.pathname;
139
191
 
192
+ reportError = (message: string) => logger.error(message);
193
+
194
+ if (pluginConfig.experimentalPlaygrounds) {
195
+ playgroundsBase = pluginConfig.experimentalPlaygrounds.playgroundsBase;
196
+ }
197
+
140
198
  const middlewareFile = path.join(projectDir, 'middleware.stainless.ts');
141
199
 
142
200
  let vmMiddlewareExport = 'export const MIDDLEWARE = {};';
143
201
  if (fs.existsSync(middlewareFile)) {
144
- logger.info(`Loading middleware from ${middlewareFile}`);
202
+ logger.debug(`Loading middleware from ${middlewareFile}`);
145
203
  vmMiddlewareExport = `export { default as MIDDLEWARE } from '${middlewareFile}';`;
146
204
  }
147
205
 
148
206
  injectRoute({
149
- pattern: `${pluginConfig.basePath}/[...slug].md`,
150
- entrypoint: '@stainless-api/docs/MarkdownRoute',
207
+ pattern: `${pluginConfig.basePath}/[...slug]/index.md`,
208
+ entrypoint: resolveSrcFile('/plugin/routes/markdown.ts'),
151
209
  prerender: command === 'build',
152
210
  });
153
211
 
154
- const astroFile = command === 'build' ? 'DocsStaticRoute' : 'DocsRoute';
212
+ const astroFile = command === 'build' ? 'DocsStatic' : 'Docs';
155
213
  injectRoute({
156
214
  pattern: `${pluginConfig.basePath}/[...slug]`,
157
- // in prod I think this points to @stainless-starlight/components/docs.astro
158
- entrypoint: `@stainless-api/docs/${astroFile}`,
215
+ entrypoint: resolveSrcFile(`/plugin/routes/${astroFile}.astro`),
159
216
  prerender: command === 'build',
160
217
  });
161
218
 
162
219
  injectRoute({
163
220
  pattern: pluginConfig.basePath,
164
- entrypoint: '@stainless-api/docs/OverviewRoute',
221
+ entrypoint: resolveSrcFile('/plugin/routes/Overview.astro'),
165
222
  prerender: command === 'build',
166
223
  });
167
224
 
168
225
  updateConfig({
169
226
  vite: {
170
- ssr: {
171
- noExternal: ['@stainless-api/ui-primitives'],
172
- },
173
- optimizeDeps: { include: ['@stainless-api/ui-primitives'] },
174
227
  plugins: [
175
228
  {
176
229
  name: 'stl-starlight-vite',
177
- configureServer(server) {
230
+ buildStart() {
231
+ building = undefined;
232
+ },
233
+ async configureServer(server) {
234
+ if (playgroundsBase) {
235
+ buildPlaygrounds = (await server.ssrLoadModule(playgroundsBase + '/src/build.ts'))
236
+ .buildPlaygrounds;
237
+ }
238
+
178
239
  for (const filePath of Object.values(devPaths)) {
179
240
  if (!filePath) {
180
241
  continue;
@@ -197,6 +258,30 @@ async function stlStarlightAstroIntegration(
197
258
  if (id === virtualId) {
198
259
  return resolvedId;
199
260
  }
261
+ if (id === 'virtual:stl-playground/unstable-update-language') {
262
+ return resolveSrcFile('plugin/languages.ts');
263
+ }
264
+ if (id === 'virtual:stl-playground/create') {
265
+ return fileURLToPath(
266
+ new URL(
267
+ pluginConfig.experimentalPlaygrounds
268
+ ? path.join(playgroundsBase!, '/src/create.tsx')
269
+ : './create-playground.shim.tsx',
270
+ import.meta.url,
271
+ ),
272
+ );
273
+ }
274
+ if (id.startsWith('virtual:stl-playground/')) {
275
+ const result = path.join(
276
+ this.environment.getTopLevelConfig().cacheDir,
277
+ 'stl-playground',
278
+ id.slice('virtual:stl-playground/'.length),
279
+ );
280
+ const config = this.environment.getTopLevelConfig();
281
+ return startPlaygroundsBuild(path.join(config.cacheDir, 'stl-playground'))?.then(
282
+ () => result,
283
+ );
284
+ }
200
285
  },
201
286
  load(id) {
202
287
  if (id === resolvedId) {
@@ -204,21 +289,39 @@ async function stlStarlightAstroIntegration(
204
289
  buildVirtualModuleString({
205
290
  BASE_PATH: pluginConfig.basePath,
206
291
  CMS_PORT,
207
- EXCLUDE_LANGUAGES: pluginConfig.excludeLanguages,
292
+ EXCLUDE_LANGUAGES: ['php', ...pluginConfig.excludeLanguages],
208
293
  DEFAULT_LANGUAGE: pluginConfig.defaultLanguage,
209
294
  BREADCRUMB_CONFIG: pluginConfig.breadcrumbs,
210
295
  EXPAND_RESOURCES: pluginConfig.expandResources,
211
296
  HIGHLIGHT_THEMES: pluginConfig.highlighting.themes,
212
297
  CONTENT_PANEL_LAYOUT: pluginConfig.contentPanel.layout,
213
298
  EXPERIMENTAL_COLLAPSIBLE_SNIPPETS: pluginConfig.experimentalCollapsibleSnippets,
299
+ EXPERIMENTAL_COLLAPSIBLE_METHOD_DESCRIPTIONS:
300
+ pluginConfig.experimentalCollapsibleMethodDescriptions,
214
301
  PROPERTY_SETTINGS: pluginConfig.propertySettings,
215
- SEARCH: pluginConfig.search,
216
- }),
302
+ ENABLE_CONTEXT_MENU: pluginConfig.contextMenu,
303
+ EXPERIMENTAL_PLAYGROUNDS: !!pluginConfig.experimentalPlaygrounds,
304
+ } satisfies Omit<typeof StlStarlightVirtualModule, 'MIDDLEWARE'>),
217
305
  vmMiddlewareExport,
218
306
  ].join('\n');
219
307
  }
220
308
  },
221
309
  },
310
+ prebundleWorkers({
311
+ include: [
312
+ '**/typescript/runner*',
313
+ '**/typescript/worker*',
314
+ '**/pyright.worker*',
315
+ '**/python/pyodide*',
316
+ ],
317
+ configureEsBuild(_, opts) {
318
+ opts.external ??= [];
319
+ opts.external.push('url');
320
+ opts.loader ??= {};
321
+ opts.loader['.wasm'] = 'dataurl';
322
+ return opts;
323
+ },
324
+ }),
222
325
  ],
223
326
  },
224
327
  });
@@ -226,6 +329,24 @@ async function stlStarlightAstroIntegration(
226
329
  'astro:server:done': async () => {
227
330
  await cmsServer.destroy();
228
331
  },
332
+ 'astro:build:start'({ logger }) {
333
+ collectedErrors = [];
334
+ reportError = (message: string) => {
335
+ logger.error(message);
336
+ collectedErrors!.push(message);
337
+ };
338
+ },
339
+ 'astro:build:done': async ({ dir, logger }) => {
340
+ const dist = fileURLToPath(dir);
341
+ const stainlessDir = path.join(dist, '_stainless');
342
+ await mkdir(stainlessDir, { recursive: true });
343
+ if (collectedErrors) {
344
+ for (const error of collectedErrors) {
345
+ logger.error(error);
346
+ }
347
+ collectedErrors = null;
348
+ }
349
+ },
229
350
  },
230
351
  };
231
352
  }
@@ -241,15 +362,20 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
241
362
  command,
242
363
  config: starlightConfig,
243
364
  astroConfig,
244
- logger,
365
+ logger: localLogger,
245
366
  }) => {
246
367
  if (command !== 'build' && command !== 'dev') {
247
368
  return;
248
369
  }
249
370
 
371
+ const logger = getSharedLogger({ fallback: localLogger });
372
+
250
373
  const configParseResult = parseStarlightPluginConfig(someUserConfig, command);
251
374
  if (configParseResult.result === 'error') {
252
- logger.error(configParseResult.message);
375
+ const errorLines = configParseResult.message.split('\n');
376
+ for (const line of errorLines) {
377
+ logger.error(line);
378
+ }
253
379
  process.exit(1);
254
380
  }
255
381
  const config = configParseResult.config;
@@ -260,17 +386,30 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
260
386
  addIntegration(react());
261
387
  }
262
388
 
389
+ if ('apiKey' in config.specRetrieverConfig) {
390
+ if (!config.specRetrieverConfig.apiKey) {
391
+ logger.info(`Stainless credentials not loaded`);
392
+ } else if (config.specRetrieverConfig.apiKey.source === 'explicit-config') {
393
+ logger.info(`Stainless credentials loaded from user config`);
394
+ } else if (config.specRetrieverConfig.apiKey.source === 'environment-variable') {
395
+ logger.info('Stainless credentials loaded from `STAINLESS_API_KEY` environment variable');
396
+ } else if (config.specRetrieverConfig.apiKey.source === 'cli') {
397
+ logger.info('Stainless credentials loaded from `stl` CLI');
398
+ }
399
+ }
400
+
263
401
  if (
264
402
  command === 'build' &&
265
403
  config.specRetrieverConfig.kind === 'local_spec_server_with_remote_files'
266
404
  ) {
267
405
  await buildAlgoliaIndex({
268
406
  version: config.specRetrieverConfig.version,
269
- apiKey: config.specRetrieverConfig.apiKey,
407
+ apiKey: config.specRetrieverConfig.apiKey.value,
408
+ logger,
270
409
  });
271
410
  }
272
411
 
273
- addIntegration(await stlStarlightAstroIntegration(config));
412
+ addIntegration(await stlStarlightAstroIntegration(config, logger));
274
413
 
275
414
  if (starlightConfig.sidebar) {
276
415
  // for pagination (https://starlight.astro.build/reference/configuration/#pagination) to work correctly
@@ -281,21 +420,25 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
281
420
  }
282
421
  }
283
422
 
284
- const currentExpressiveCode =
423
+ const expressiveCodeConfig =
285
424
  typeof starlightConfig.expressiveCode === 'object' ? starlightConfig.expressiveCode : {};
286
- updateConfig({
287
- expressiveCode: {
288
- ...currentExpressiveCode,
289
- themes: [...(currentExpressiveCode.themes || []), 'github-light', 'github-dark'],
290
- },
291
- });
425
+
426
+ const themes = expressiveCodeConfig.themes
427
+ ? (expressiveCodeConfig.themes as BundledTheme[])
428
+ : (['github-light', 'github-dark'] as BundledTheme[]);
292
429
 
293
430
  updateConfig({
294
431
  sidebar: starlightConfig.sidebar,
432
+ ...(expressiveCodeConfig && {
433
+ expressiveCode: {
434
+ ...expressiveCodeConfig,
435
+ themes,
436
+ },
437
+ }),
295
438
  });
296
439
 
297
440
  addRouteMiddleware({
298
- entrypoint: '@stainless-api/docs/replaceSidebarPlaceholderMiddleware',
441
+ entrypoint: resolveSrcFile('/plugin/replaceSidebarPlaceholderMiddleware.ts'),
299
442
  order: 'post',
300
443
  });
301
444
  },
@@ -304,5 +447,5 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
304
447
  }
305
448
 
306
449
  // 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';
450
+ export { parseStainlessPath } from '@stainless-api/docs-ui/routing';
451
+ export { renderMarkdown } from '@stainless-api/docs-ui/markdown';
@@ -1,4 +1,4 @@
1
- import type { DocsLanguage } from '@stainless-api/docs-ui/src/routing';
1
+ import type { DocsLanguage } from '@stainless-api/docs-ui/routing';
2
2
  import KotlinIcon from './assets/languages/kotlin.svg';
3
3
  import RubyIcon from './assets/languages/ruby.svg';
4
4
  import TerraformIcon from './assets/languages/terraform.svg';
@@ -7,6 +7,7 @@ import PythonIcon from './assets/languages/python.svg';
7
7
  import JavaIcon from './assets/languages/java.svg';
8
8
  import GoIcon from './assets/languages/go.svg';
9
9
  import CurlIcon from './assets/languages/curl.svg';
10
+ import CSharpIcon from './assets/languages/csharp.svg';
10
11
 
11
12
  export const Languages: Record<
12
13
  DocsLanguage,
@@ -29,6 +30,8 @@ export const Languages: Record<
29
30
  http: { name: 'HTTP', icon: CurlIcon, alt: 'HTTP logo' },
30
31
  terraform: { name: 'Terraform', icon: TerraformIcon, alt: 'Terraform logo' },
31
32
  ruby: { name: 'Ruby', icon: RubyIcon, alt: 'Ruby logo' },
33
+ csharp: { name: 'C#', icon: CSharpIcon, alt: 'C# logo' },
34
+ php: { name: 'PHP', icon: CSharpIcon, alt: 'PHP logo' }, // TODO update PHP icon
32
35
  };
33
36
 
34
37
  export function generatePrefix(basePath: string, language: string) {
@@ -44,7 +47,7 @@ export function applyLanguageToLinks(basePath?: string, defaultLanguage?: string
44
47
  `[data-stldocs-overview],[data-stldocs-method],a.nav-link[href^='${basePath}']`,
45
48
  );
46
49
 
47
- for (var link of links) {
50
+ for (const link of links) {
48
51
  const href = link.getAttribute('href');
49
52
  const prefix = generatePrefix(basePath, language);
50
53
  if (href?.startsWith(basePath) && !href?.startsWith(prefix)) {
@@ -1,9 +1,12 @@
1
1
  import path from 'path';
2
+ import { homedir } from 'os';
3
+ import { existsSync, readFileSync } from 'fs';
2
4
 
3
5
  import type { CreateShikiHighlighterOptions } from '@astrojs/markdown-remark';
4
- import type { DocsLanguage } from '@stainless-api/docs-ui/src/routing';
5
- import type { PropertySettingsType } from '@stainless-api/docs-ui/src/contexts';
6
+ import type { DocsLanguage } from '@stainless-api/docs-ui/routing';
7
+ import type { PropertySettingsType } from '@stainless-api/docs-ui/contexts';
6
8
  import type { InputFilePaths } from '../plugin/cms/server';
9
+ import { bold } from '../shared/terminalUtils';
7
10
 
8
11
  export type AstroCommand = 'dev' | 'build' | 'preview' | 'sync';
9
12
 
@@ -18,7 +21,7 @@ export type VersionUserConfig = {
18
21
  type BreadcrumbUserConfig = {
19
22
  /**
20
23
  * Include the current page in the breadcrumb list.
21
- * Defaults to `false`.
24
+ * Default: `false`
22
25
  */
23
26
  includeCurrentPage?: boolean;
24
27
  };
@@ -26,7 +29,12 @@ type BreadcrumbUserConfig = {
26
29
  export type StainlessStarlightUserConfig = {
27
30
  /**
28
31
  * Optional api key for Stainless API.
29
- * If not provided, it will look for the STAINLESS_API_KEY environment variable.
32
+ * If not provided, we will handle Stainless auth via the `stl` CLI or look for the STAINLESS_API_KEY environment variable.
33
+ * Precedence:
34
+ * 1. Explicity `apiKey` option provided
35
+ * 2. `STAINLESS_API_KEY` environment variable
36
+ * 3. Login status from the `stl` CLI
37
+ * 4. Error (no auth found)
30
38
  */
31
39
  apiKey?: string;
32
40
 
@@ -42,8 +50,8 @@ export type StainlessStarlightUserConfig = {
42
50
 
43
51
  /**
44
52
  * Optional mount point for API reference docs.
45
- * Defaults to `/api`.
46
53
  * Example: `/my-api` → docs available at `/my-api/…`.
54
+ * @default `/api`
47
55
  */
48
56
  basePath?: string;
49
57
 
@@ -55,8 +63,8 @@ export type StainlessStarlightUserConfig = {
55
63
 
56
64
  /**
57
65
  * Optional language to treat as the default when the user hasn't selected one.
58
- * Defaults to `"http"` when none is provided.
59
- * Example: `"python"`
66
+ * Example: `'python'`
67
+ * @default 'http'
60
68
  */
61
69
  defaultLanguage?: DocsLanguage;
62
70
 
@@ -90,7 +98,7 @@ export type StainlessStarlightUserConfig = {
90
98
  contentPanel?: {
91
99
  /**
92
100
  * Optional layout for the content panel.
93
- * Defaults to `"double-pane"`.
101
+ * @default 'double-pane'
94
102
  */
95
103
  layout?: ContentLayout;
96
104
  };
@@ -100,23 +108,35 @@ export type StainlessStarlightUserConfig = {
100
108
  */
101
109
  propertySettings?: PropertySettingsType;
102
110
 
103
- /**
104
- * Options to control the documentation site's search functionality
105
- */
106
- search?: {
107
- /**
108
- * When set to `true`, the enableAISearch` setting turns on support for
109
- * LLM-based conversations with the API documentation
110
- */
111
- enableAISearch?: boolean;
112
- };
113
-
114
111
  /**
115
112
  * Enable experimental collapsible code snippets. Snippets will be collapsed by default for
116
113
  * single-pane and mobile layouts.
117
- * Defaults to `false`.
114
+ *
115
+ * @default false
118
116
  */
119
117
  experimentalCollapsibleSnippets?: boolean;
118
+
119
+ /**
120
+ * Enable experimental collapsible method descriptions. Method descriptions will be
121
+ * collapsed if their content exceeds a certain length.
122
+ *
123
+ * @default false
124
+ */
125
+ experimentalCollapsibleMethodDescriptions?: boolean;
126
+
127
+ /**
128
+ * Whether to show the context menu with options like "Copy as Markdown" and "Open in ChatGPT".
129
+ *
130
+ * @default true
131
+ */
132
+
133
+ contextMenu?: boolean;
134
+
135
+ /**
136
+ * When set to `import playgrounds from '@stainless-api/playgrounds'`, code snippets will have a "Play" button,
137
+ * allowing users to edit and run code snippets in their browser.
138
+ */
139
+ experimentalPlaygrounds?: { playgroundsBase: string };
120
140
  };
121
141
 
122
142
  export type ExternalSpecServerUserConfig = Omit<StainlessStarlightUserConfig, 'stainlessProject'> & {
@@ -133,15 +153,29 @@ function getLocalFilePaths(command: AstroCommand): InputFilePaths | null {
133
153
  if (command !== 'dev') {
134
154
  return null;
135
155
  }
136
- if (!process.env.OPENAPI_PATH || !process.env.STAINLESS_SPEC_PATH) {
156
+
157
+ // eslint-disable-next-line turbo/no-undeclared-env-vars
158
+ const oasPath = process.env.OPENAPI_PATH;
159
+ // eslint-disable-next-line turbo/no-undeclared-env-vars
160
+ const configPath = process.env.STAINLESS_CONFIG_PATH;
161
+
162
+ if (!oasPath || !configPath) {
137
163
  return null;
138
164
  }
165
+
139
166
  return {
140
- oasPath: resolvePath(process.env.OPENAPI_PATH),
141
- configPath: resolvePath(process.env.STAINLESS_SPEC_PATH),
167
+ oasPath: resolvePath(oasPath),
168
+ configPath: resolvePath(configPath),
142
169
  };
143
170
  }
144
171
 
172
+ export type ApiKeySource = 'explicit-config' | 'environment-variable' | 'cli';
173
+
174
+ export type LoadedApiKey = {
175
+ value: string;
176
+ source: ApiKeySource;
177
+ };
178
+
145
179
  export type SpecRetrieverConfig =
146
180
  | {
147
181
  kind: 'external_spec_server';
@@ -152,16 +186,63 @@ export type SpecRetrieverConfig =
152
186
  kind: 'local_spec_server_with_files';
153
187
  stainlessProject: string;
154
188
  devPaths: InputFilePaths;
155
- apiKey: string | null;
189
+ apiKey: LoadedApiKey | null;
156
190
  version: VersionUserConfig;
157
191
  }
158
192
  | {
159
193
  kind: 'local_spec_server_with_remote_files';
160
194
  stainlessProject: string;
161
- apiKey: string;
195
+ apiKey: LoadedApiKey;
162
196
  version: VersionUserConfig;
163
197
  };
164
198
 
199
+ function parseAuthJson(authJsonStr: string) {
200
+ let json: unknown;
201
+ try {
202
+ json = JSON.parse(authJsonStr);
203
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
204
+ } catch (_error) {
205
+ return null;
206
+ }
207
+
208
+ if (typeof json !== 'object' || json === null) {
209
+ return null;
210
+ }
211
+ if (!('access_token' in json)) {
212
+ return null;
213
+ }
214
+ const accessToken = json['access_token'];
215
+ if (typeof accessToken !== 'string') {
216
+ return null;
217
+ }
218
+ return accessToken;
219
+ }
220
+
221
+ function loadApiKey(configValue: string | undefined): LoadedApiKey | null {
222
+ if (typeof configValue === 'string') {
223
+ return { value: configValue, source: 'explicit-config' };
224
+ }
225
+ if (process.env.STAINLESS_API_KEY) {
226
+ return { value: process.env.STAINLESS_API_KEY, source: 'environment-variable' };
227
+ }
228
+
229
+ const homeDirPath = homedir();
230
+
231
+ const authJsonPath = path.join(homeDirPath, '.config', 'stainless', 'auth.json');
232
+
233
+ if (!existsSync(authJsonPath)) {
234
+ return null;
235
+ }
236
+
237
+ const authJsonStr = readFileSync(authJsonPath, 'utf-8');
238
+ const accessToken = parseAuthJson(authJsonStr);
239
+ if (!accessToken) {
240
+ return null;
241
+ }
242
+
243
+ return { value: accessToken, source: 'cli' };
244
+ }
245
+
165
246
  function normalizeConfig(partial: SomeStainlessStarlightUserConfig, command: AstroCommand) {
166
247
  const configWithDefaults = {
167
248
  basePath: partial.basePath ?? '/api',
@@ -181,15 +262,16 @@ function normalizeConfig(partial: SomeStainlessStarlightUserConfig, command: Ast
181
262
  layout: partial.contentPanel?.layout ?? 'double-pane',
182
263
  },
183
264
  experimentalCollapsibleSnippets: partial.experimentalCollapsibleSnippets ?? false,
265
+ experimentalCollapsibleMethodDescriptions: partial.experimentalCollapsibleMethodDescriptions ?? false,
184
266
  propertySettings: {
185
267
  types: partial.propertySettings?.types ?? 'rich',
186
268
  collapseDescription: partial.propertySettings?.collapseDescription ?? true,
269
+ showTitle: partial.propertySettings?.showTitle ?? false,
187
270
  expandDepth: partial.propertySettings?.expandDepth ?? 0,
188
271
  includeModelProperties: partial.propertySettings?.includeModelProperties ?? true,
189
272
  },
190
- search: {
191
- enableAISearch: partial.search?.enableAISearch ?? false,
192
- },
273
+ contextMenu: partial.contextMenu ?? true,
274
+ experimentalPlaygrounds: partial.experimentalPlaygrounds ?? undefined,
193
275
  };
194
276
 
195
277
  function getSpecRetrieverConfig(): SpecRetrieverConfig {
@@ -205,7 +287,7 @@ function normalizeConfig(partial: SomeStainlessStarlightUserConfig, command: Ast
205
287
  throw new Error('You must provide a stainlessProject when using Stainless Starlight');
206
288
  }
207
289
 
208
- const apiKey = partial.apiKey ?? process.env.STAINLESS_API_KEY ?? null;
290
+ const apiKey = loadApiKey(partial.apiKey);
209
291
 
210
292
  const version = {
211
293
  stainlessProject: partial.stainlessProject,
@@ -226,7 +308,14 @@ function normalizeConfig(partial: SomeStainlessStarlightUserConfig, command: Ast
226
308
 
227
309
  if (!apiKey) {
228
310
  throw new Error(
229
- 'Please provide a Stainless API key via the STAINLESS_API_KEY environment variable or the apiKey option in the Stainless Starlight config.',
311
+ [
312
+ bold(
313
+ 'No Stainless credentials found. Please choose one of the following options to authenticate with Stainless:',
314
+ ),
315
+ '- Run `stl auth login` to authenticate via the Stainless CLI',
316
+ '- Provide a Stainless API key via the `STAINLESS_API_KEY` environment variable (eg. in a .env file)',
317
+ '- Set the `apiKey` option in the Stainless Docs config',
318
+ ].join('\n'),
230
319
  );
231
320
  }
232
321
 
@@ -249,11 +338,11 @@ function normalizeConfig(partial: SomeStainlessStarlightUserConfig, command: Ast
249
338
  export type NormalizedStainlessStarlightConfig = ReturnType<typeof normalizeConfig>;
250
339
 
251
340
  /*
252
- The goal of the code in this file is to take a user's config and normalize it.
341
+ The goal of the code in this file is to take a user's config and normalize it.
253
342
  Specifically: we want a single complete config format used throughout the internals of the plugin.
254
343
 
255
344
  We've tried to avoid any config values being optional/undefined. To accomplish this:
256
- - Any optional config values should have their defaults set here: eg. basePath defaults to /api
345
+ - Any optional config values should have their defaults set here: eg. basePath defaults to /api
257
346
  - If a field is only used in certain contexts, we make each context a discriminated union (see SpecRetrieverConfig)
258
347
  - We prefer empty arrays over undefined/null
259
348
  */
@@ -1,4 +1,4 @@
1
- import type { TransformRequestSnippetFn } from '@stainless-api/docs-ui/src/components/sdk';
1
+ import type { TransformRequestSnippetFn } from '@stainless-api/docs-ui/components/sdk';
2
2
 
3
3
  export type StlStarlightMiddleware = {
4
4
  transformRequestSnippet?: TransformRequestSnippetFn;