@stainless-api/docs 0.1.0-beta.2 → 0.1.0-beta.20

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 (91) hide show
  1. package/CHANGELOG.md +147 -0
  2. package/components/variables.css +1 -27
  3. package/eslint-suppressions.json +47 -0
  4. package/locals.d.ts +14 -0
  5. package/package.json +30 -27
  6. package/plugin/buildAlgoliaIndex.ts +29 -4
  7. package/plugin/cms/server.ts +97 -54
  8. package/plugin/cms/sidebar-builder.ts +6 -25
  9. package/plugin/cms/worker.ts +2 -2
  10. package/plugin/components/SnippetCode.tsx +7 -4
  11. package/plugin/components/search/SearchAlgolia.astro +0 -7
  12. package/plugin/components/search/SearchIsland.tsx +30 -17
  13. package/plugin/generateAPIReferenceLink.ts +1 -1
  14. package/plugin/globalJs/ai-dropdown-options.ts +161 -0
  15. package/plugin/globalJs/navigation.ts +0 -23
  16. package/plugin/helpers/getPageLoadEvent.ts +1 -1
  17. package/plugin/index.ts +49 -17
  18. package/plugin/languages.ts +1 -1
  19. package/plugin/loadPluginConfig.ts +92 -13
  20. package/plugin/react/Routing.tsx +30 -33
  21. package/plugin/referencePlaceholderUtils.ts +1 -1
  22. package/plugin/replaceSidebarPlaceholderMiddleware.ts +4 -0
  23. package/plugin/routes/Docs.astro +59 -85
  24. package/plugin/routes/Overview.astro +9 -15
  25. package/plugin/routes/markdown.ts +1 -1
  26. package/plugin/vendor/preview.worker.docs.js +6357 -6132
  27. package/resolveSrcFile.ts +10 -0
  28. package/shared/getSharedLogger.ts +15 -0
  29. package/shared/terminalUtils.ts +3 -0
  30. package/src/content.config.ts +9 -0
  31. package/stl-docs/components/AIDropdown.tsx +52 -0
  32. package/stl-docs/components/Head.astro +9 -0
  33. package/stl-docs/components/Header.astro +3 -2
  34. package/stl-docs/components/PageTitle.astro +65 -0
  35. package/stl-docs/components/TableOfContents.astro +34 -0
  36. package/stl-docs/components/ThemeSelect.astro +4 -2
  37. package/stl-docs/components/content-panel/ContentPanel.astro +9 -39
  38. package/stl-docs/components/headers/DefaultHeader.astro +1 -1
  39. package/stl-docs/components/headers/HeaderLinks.astro +1 -1
  40. package/stl-docs/components/headers/StackedHeader.astro +30 -25
  41. package/stl-docs/components/icons/chat-gpt.tsx +17 -0
  42. package/stl-docs/components/icons/claude.tsx +10 -0
  43. package/stl-docs/components/icons/markdown.tsx +10 -0
  44. package/stl-docs/components/index.ts +2 -0
  45. package/stl-docs/components/mintlify-compat/Accordion.astro +7 -38
  46. package/stl-docs/components/mintlify-compat/AccordionGroup.astro +9 -23
  47. package/stl-docs/components/mintlify-compat/Columns.astro +40 -42
  48. package/stl-docs/components/mintlify-compat/Frame.astro +16 -18
  49. package/stl-docs/components/mintlify-compat/Step.astro +30 -32
  50. package/stl-docs/components/mintlify-compat/Steps.astro +8 -10
  51. package/stl-docs/components/mintlify-compat/callouts/Callout.astro +10 -3
  52. package/stl-docs/components/mintlify-compat/callouts/Check.astro +7 -3
  53. package/stl-docs/components/mintlify-compat/callouts/Danger.astro +7 -3
  54. package/stl-docs/components/mintlify-compat/callouts/Info.astro +7 -3
  55. package/stl-docs/components/mintlify-compat/callouts/Note.astro +7 -3
  56. package/stl-docs/components/mintlify-compat/callouts/Tip.astro +7 -3
  57. package/stl-docs/components/mintlify-compat/callouts/Warning.astro +7 -3
  58. package/stl-docs/components/mintlify-compat/card.css +33 -35
  59. package/stl-docs/components/nav-tabs/NavDropdown.astro +1 -1
  60. package/stl-docs/components/nav-tabs/SecondaryNavTabs.astro +15 -7
  61. package/stl-docs/components/nav-tabs/buildNavLinks.ts +4 -3
  62. package/stl-docs/components/pagination/HomeLink.astro +10 -0
  63. package/stl-docs/components/pagination/Pagination.astro +173 -0
  64. package/stl-docs/components/pagination/PaginationLinkEmphasized.astro +22 -0
  65. package/stl-docs/components/pagination/PaginationLinkQuiet.astro +13 -0
  66. package/stl-docs/components/pagination/util.ts +71 -0
  67. package/stl-docs/components/{Sidebar.astro → sidebars/BaseSidebar.astro} +2 -3
  68. package/stl-docs/components/sidebars/SDKSelectSidebar.astro +8 -0
  69. package/stl-docs/disableCalloutSyntax.ts +36 -0
  70. package/stl-docs/index.ts +76 -13
  71. package/stl-docs/loadStlDocsConfig.ts +25 -3
  72. package/stl-docs/proseMarkdown/proseMarkdownIntegration.ts +64 -0
  73. package/stl-docs/proseMarkdown/proseMarkdownMiddleware.ts +34 -0
  74. package/stl-docs/proseMarkdown/toMarkdown.ts +158 -0
  75. package/stl-docs/tabsMiddleware.ts +12 -4
  76. package/styles/code.css +115 -127
  77. package/styles/fonts.css +24 -9
  78. package/styles/links.css +10 -49
  79. package/styles/overrides.css +55 -57
  80. package/styles/page.css +89 -59
  81. package/styles/sdk_select.css +6 -7
  82. package/styles/search.css +65 -67
  83. package/styles/sidebar.css +199 -128
  84. package/styles/toc.css +37 -33
  85. package/theme.css +9 -1
  86. package/tsconfig.json +2 -5
  87. package/virtual-module.d.ts +4 -1
  88. package/plugin/globalJs/ai-dropdown.ts +0 -57
  89. package/stl-docs/components/APIReferenceAIDropdown.tsx +0 -86
  90. package/stl-docs/components/content-panel/ProseAIDropdown.tsx +0 -64
  91. /package/stl-docs/components/{content-panel/ContentBreadcrumbs.tsx → ContentBreadcrumbs.tsx} +0 -0
@@ -1,4 +1,4 @@
1
- import type * as SDKJSON from '~/lib/json-spec-v2/types';
1
+ import type * as SDKJSON from '@stainless/sdk-json';
2
2
  import { generateRoute, walkTree, type DocsLanguage } from '@stainless-api/docs-ui/src/routing';
3
3
  import type { StarlightRouteData } from '@astrojs/starlight/route-data';
4
4
 
@@ -120,22 +120,12 @@ export type GeneratedSidebarConfig = {
120
120
  };
121
121
 
122
122
  function countKeys(obj?: Record<string, any>) {
123
- let o = obj ?? {};
123
+ const o = obj ?? {};
124
124
  return Object.keys(o).length;
125
125
  }
126
126
 
127
- function getMethodDeclForLanguage(entry: SDKJSON.Method, spec: SDKJSON.Spec, language: DocsLanguage) {
128
- const decls = spec.decls[language] ?? {};
129
- const decl = decls[entry.stainlessPath];
130
- if (decl !== undefined) {
131
- if ('ident' in decl) {
132
- return decl;
133
- }
134
- }
135
- return null;
136
- }
137
-
138
- type MethodDecl = Exclude<ReturnType<typeof getMethodDeclForLanguage>, null>;
127
+ type HasIdent<T> = T extends { ident: unknown } ? T : never;
128
+ type MethodDecl = HasIdent<SDKJSON.LanguageDeclNodes[SDKJSON.SpecLanguage]>;
139
129
 
140
130
  function makeAPIOverviewPage(): UserSidebarAPIOverviewPage {
141
131
  return {
@@ -166,7 +156,7 @@ function pullOutSharedModelsResource(resources: SDKJSON.Resource[]): {
166
156
  }
167
157
 
168
158
  export class SidebarConfigItemsBuilder {
169
- private getMethodDeclForLanguage(entry: SDKJSON.Method) {
159
+ private getMethodDeclForLanguage(entry: SDKJSON.Method): MethodDecl | null {
170
160
  const decls = this.spec.decls[this.language] ?? {};
171
161
  const decl = decls[entry.stainlessPath];
172
162
  if (decl !== undefined) {
@@ -208,13 +198,6 @@ export class SidebarConfigItemsBuilder {
208
198
  };
209
199
  }
210
200
 
211
- private sortByLabel<T extends UserSidebarConfigItem>(items: T[]) {
212
- // sorts in place
213
- items.sort((a, b) => {
214
- return a.label.localeCompare(b.label);
215
- });
216
- }
217
-
218
201
  private generateResourceGroup(resource: SDKJSON.Resource, collapsed: boolean): ReferenceSidebarGroup {
219
202
  const entries: ReferenceSidebarConfigItem[] = [];
220
203
  if (!this.options?.excludeResourceOverviewPages) {
@@ -228,7 +211,6 @@ export class SidebarConfigItemsBuilder {
228
211
  methodPages.push(this.toMethodPage(m, langDecl));
229
212
  }
230
213
  }
231
- this.sortByLabel(methodPages);
232
214
  entries.push(...methodPages);
233
215
 
234
216
  const subresources = Object.values(resource.subresources ?? {});
@@ -238,7 +220,6 @@ export class SidebarConfigItemsBuilder {
238
220
  subresourceGroups.push(this.generateResourceGroup(sub, true));
239
221
  }
240
222
  }
241
- this.sortByLabel(subresourceGroups);
242
223
  entries.push(...subresourceGroups);
243
224
 
244
225
  return {
@@ -253,7 +234,7 @@ export class SidebarConfigItemsBuilder {
253
234
 
254
235
  public generateItems(): ReferenceSidebarConfigItem[] {
255
236
  const resourceMap = this.spec.resources;
256
- let { resources, sharedModelsResource } = pullOutSharedModelsResource(Object.values(resourceMap ?? {}));
237
+ const { resources, sharedModelsResource } = pullOutSharedModelsResource(Object.values(resourceMap ?? {}));
257
238
 
258
239
  const entries: ReferenceSidebarConfigItem[] = resources.filter(isResourceNonEmpty).map((r) => {
259
240
  return this.generateResourceGroup(r, false);
@@ -1,6 +1,6 @@
1
1
  import Worker from 'web-worker';
2
2
  import { Languages, type DocsLanguage } from '@stainless-api/docs-ui/src/routing';
3
- import type * as SDKJSON from '~/lib/json-spec-v2/types';
3
+ import type * as SDKJSON from '@stainless/sdk-json';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { dirname, resolve } from 'node:path';
6
6
  import fs from 'fs/promises';
@@ -98,7 +98,7 @@ export async function createSDKJSON({
98
98
  try {
99
99
  const content = await fs.readFile(mdfile);
100
100
  return [language, content.toString()];
101
- } catch (err) {
101
+ } catch {
102
102
  return [language, null];
103
103
  }
104
104
  }),
@@ -8,6 +8,7 @@ import style from '@stainless-api/docs-ui/src/style';
8
8
  import * as cheerio from 'cheerio/slim';
9
9
  import { EXPERIMENTAL_COLLAPSIBLE_SNIPPETS } from 'virtual:stl-starlight-virtual-module';
10
10
  import clsx from 'clsx';
11
+ import { Button } from '@stainless-api/ui-primitives';
11
12
  /*
12
13
  * This may be replaced by additional data from the sdk.
13
14
  * Without information from the sdk, we use simple heuristics per language.
@@ -53,7 +54,7 @@ function wrapFirstNSpaces($line: cheerio.Cheerio<any>, n: number) {
53
54
  const m = inner.match(new RegExp(`^( {1,${n}})`));
54
55
  if (!m) return;
55
56
 
56
- const lead = m[1];
57
+ const lead = m[1]!;
57
58
  $firstSpan.html(`<span class="leading-ws">${lead}</span>${inner.slice(lead.length)}`);
58
59
  }
59
60
 
@@ -130,12 +131,14 @@ export function SnippetRequestContainer({ children, signature }: SnippetRequestC
130
131
  <div className="stl-snippet-request-container">
131
132
  {children}
132
133
  {signature && isCollapsible && (
133
- <button
134
- className={clsx(style.Button, style.ButtonSecondary, 'stl-snippet-expand-button')}
134
+ <Button
135
+ className={'stl-snippet-expand-button'}
135
136
  id="stl-snippet-expand-button"
137
+ size="sm"
138
+ variant="outline"
136
139
  >
137
140
  Show more
138
- </button>
141
+ </Button>
139
142
  )}
140
143
  </div>
141
144
  );
@@ -2,13 +2,6 @@
2
2
  import { Icon } from '@astrojs/starlight/components';
3
3
  import { DocsSearch } from './SearchIsland';
4
4
  import { SEARCH } from 'virtual:stl-starlight-virtual-module';
5
-
6
- import '@stainless-api/docs-ui/src/styles/resets.css';
7
- import '@stainless-api/docs-ui/src/styles/primitives.css';
8
- import '@stainless-api/docs-ui/src/styles/main.css';
9
- import '@stainless-api/docs-ui/src/styles/snippets.css';
10
- import '@stainless-api/docs-ui/src/styles/search.css';
11
- import '../../../components/variables.css';
12
5
  ---
13
6
 
14
7
  <site-search>
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { BASE_PATH, SEARCH } from 'virtual:stl-starlight-virtual-module';
2
+ import { BASE_PATH, HIGHLIGHT_THEMES, SEARCH } from 'virtual:stl-starlight-virtual-module';
3
3
  import { parseRoute, generateRoute } from '@stainless-api/docs-ui/src/routing';
4
4
  import { SearchModal } from '@stainless-api/docs-ui/src/search/index';
5
5
  import { ChatModal } from '@stainless-api/docs-ui/src/components/chat';
@@ -9,10 +9,12 @@ import type { BundledLanguage, BundledTheme, HighlighterGeneric } from 'shiki';
9
9
 
10
10
  import {
11
11
  DocsProvider,
12
+ type MarkdownContext,
12
13
  MarkdownProvider,
13
14
  NavigationProvider,
14
15
  SearchProvider,
15
16
  } from '@stainless-api/docs-ui/src/contexts';
17
+ import { ComponentProvider } from '@stainless-api/docs-ui/src/contexts/component';
16
18
  import type { SearchSettings } from '@stainless-api/docs-ui/src/search/types';
17
19
 
18
20
  let $$highlighter: HighlighterGeneric<BundledLanguage, BundledTheme> | null = null;
@@ -27,7 +29,7 @@ async function getHighlighter() {
27
29
  return $$highlighter;
28
30
  }
29
31
 
30
- async function createMarkdownRenderer() {
32
+ async function createMarkdownRenderer(): Promise<MarkdownContext> {
31
33
  const highlighter = await getHighlighter();
32
34
  const markdocConfig: Markdoc.Config = {
33
35
  nodes: {
@@ -66,16 +68,25 @@ async function createMarkdownRenderer() {
66
68
  },
67
69
  };
68
70
 
69
- return (content: string) => {
70
- const ast = Markdoc.parse(content);
71
- const transformed = Markdoc.transform(ast, markdocConfig);
72
- return Markdoc.renderers.html(transformed);
71
+ return {
72
+ render: (content: string) => {
73
+ const ast = Markdoc.parse(content);
74
+ const transformed = Markdoc.transform(ast, markdocConfig);
75
+ return Markdoc.renderers.html(transformed);
76
+ },
77
+ highlight: (content: string, language: string) => {
78
+ return highlighter.codeToHtml(content, {
79
+ lang: language ?? 'javascript',
80
+ themes: HIGHLIGHT_THEMES || {},
81
+ });
82
+ },
73
83
  };
74
84
  }
75
85
 
76
86
  export function DocsSearch({ settings, currentPath }: { settings: SearchSettings; currentPath: string }) {
77
- const renderMarkdown = React.use(createMarkdownRenderer());
87
+ const markdownRenderer = React.use(createMarkdownRenderer());
78
88
  const { stainlessPath, language } = parseRoute(BASE_PATH, currentPath);
89
+ // eslint-disable-next-line turbo/no-undeclared-env-vars
79
90
  const pageFind = import.meta.env.DEV ? undefined : '/pagefind/pagefind.js';
80
91
 
81
92
  function handleSelect(path: string) {
@@ -85,16 +96,18 @@ export function DocsSearch({ settings, currentPath }: { settings: SearchSettings
85
96
 
86
97
  return (
87
98
  <DocsProvider spec={null} language={language}>
88
- <NavigationProvider basePath="/" selectedPath={stainlessPath}>
89
- <MarkdownProvider render={renderMarkdown}>
90
- <SearchProvider onSelect={handleSelect} pageFind={pageFind} settings={settings}>
91
- <div className="stldocs-root">
92
- <SearchModal id="stldocs-search" />
93
- {SEARCH?.enableAISearch === true && <ChatModal id="stldocs-chat" />}
94
- </div>
95
- </SearchProvider>
96
- </MarkdownProvider>
97
- </NavigationProvider>
99
+ <ComponentProvider>
100
+ <NavigationProvider basePath="/" selectedPath={stainlessPath}>
101
+ <MarkdownProvider {...markdownRenderer}>
102
+ <SearchProvider onSelect={handleSelect} pageFind={pageFind} settings={settings}>
103
+ <div className="stldocs-root">
104
+ <SearchModal id="stldocs-search" />
105
+ {SEARCH?.enableAISearch === true && <ChatModal id="stldocs-chat" />}
106
+ </div>
107
+ </SearchProvider>
108
+ </MarkdownProvider>
109
+ </NavigationProvider>
110
+ </ComponentProvider>
98
111
  </DocsProvider>
99
112
  );
100
113
  }
@@ -1,7 +1,7 @@
1
1
  // This is probably temporary, but it fills in functionality needed for Mintlify imports
2
2
 
3
3
  import type { StarlightRouteData } from '@astrojs/starlight/route-data';
4
- import type * as SDKJSON from '~/lib/json-spec-v2/types';
4
+ import type * as SDKJSON from '@stainless/sdk-json';
5
5
  import { walkTree } from '@stainless-api/docs-ui/src/routing';
6
6
 
7
7
  const INTERNAL_REFERENCE_ENTRY_MARKER = 'STL_STARLIGHT_API_REFERENCE_METHOD_LINK_PLACEHOLDER';
@@ -0,0 +1,161 @@
1
+ import { initDropdownButton } from '@stainless-api/ui-primitives/scripts';
2
+ import { getPageLoadEvent } from '../helpers/getPageLoadEvent';
3
+
4
+ export type DropdownIcon = 'markdown' | 'copy' | 'claude' | 'chatgpt';
5
+
6
+ interface DropdownOptionInputProps {
7
+ onClick: () => void;
8
+ icon: DropdownIcon;
9
+ primaryAction?: boolean;
10
+ label: string[] | string;
11
+ }
12
+
13
+ function getMarkdownUrl(type: 'relative' | 'absolute') {
14
+ const currentUrl = new URL(window.location.href);
15
+ const hasTrailingSlash = currentUrl.pathname.endsWith('/');
16
+
17
+ const markdownUrl = [
18
+ type === 'absolute' ? currentUrl.origin : '',
19
+ currentUrl.pathname,
20
+ hasTrailingSlash ? '' : '/',
21
+ 'index.md',
22
+ ].join('');
23
+
24
+ return markdownUrl;
25
+ }
26
+
27
+ function openInLLM(serviceUrl: string) {
28
+ const mdUrl = getMarkdownUrl('absolute');
29
+ const prompt = encodeURIComponent(
30
+ `Load the contents of ${mdUrl} into this chat's context so we can discuss it.`,
31
+ );
32
+ window.open(`${serviceUrl}${prompt}`, '_blank');
33
+ }
34
+
35
+ // 2d array of dropdown options
36
+ // each sub-array is a group, separated by a horizontal rule in the UI
37
+ const aiDropdownOptions: DropdownOptionInputProps[][] = [
38
+ [
39
+ {
40
+ label: 'View as Markdown',
41
+ onClick: () => {
42
+ window.open(getMarkdownUrl('absolute'), '_blank');
43
+ },
44
+ icon: 'markdown',
45
+ primaryAction: true,
46
+ },
47
+ {
48
+ label: 'Copy Markdown',
49
+ onClick: () => {
50
+ // Source: https://wolfgangrittner.dev/how-to-use-clipboard-api-in-firefox/
51
+ const markdownUrl = getMarkdownUrl('relative');
52
+ // ClipboardItem doesn't exist in every browser
53
+ // eslint-disable-next-line no-constant-binary-expression
54
+ if (typeof ClipboardItem && navigator.clipboard.write) {
55
+ // NOTE: Safari locks down the clipboard API to only work when triggered
56
+ // by a direct user interaction. You can't use it async in a promise.
57
+ // But! You can wrap the promise in a ClipboardItem, and give that to
58
+ // the clipboard API.
59
+ // Found this on https://developer.apple.com/forums/thread/691873
60
+ const text = new ClipboardItem({
61
+ 'text/plain': fetch(markdownUrl)
62
+ .then((response) => response.text())
63
+ .then((text) => new Blob([text], { type: 'text/plain' })),
64
+ });
65
+ navigator.clipboard.write([text]);
66
+ } else {
67
+ // NOTE: Firefox has support for ClipboardItem and navigator.clipboard.write,
68
+ // but those are behind `dom.events.asyncClipboard.clipboardItem` preference.
69
+ // Good news is that other than Safari, Firefox does not care about
70
+ // Clipboard API being used async in a Promise.
71
+ fetch(markdownUrl)
72
+ .then((response) => response.text())
73
+ .then((text) => navigator.clipboard.writeText(text));
74
+ }
75
+ },
76
+ icon: 'copy',
77
+ primaryAction: false,
78
+ },
79
+ ],
80
+ [
81
+ {
82
+ label: ['Open in ', 'Claude'],
83
+ onClick: () => {
84
+ openInLLM('https://claude.ai/new?q=');
85
+ },
86
+ icon: 'claude',
87
+ primaryAction: false,
88
+ },
89
+ {
90
+ label: ['Open in ', 'ChatGPT'],
91
+ onClick: () => {
92
+ openInLLM('https://chatgpt.com/?hints=search&prompt=');
93
+ },
94
+ icon: 'chatgpt',
95
+ primaryAction: false,
96
+ },
97
+ // TODO: Add Cursor support
98
+ // {
99
+ // label: ['Open in ', 'Cursor'],
100
+ // onClick: () => {
101
+ // openInLLM('https://www.cursor.so/?prompt=');
102
+ // },
103
+ // icon: 'cursor',
104
+ // primaryAction: false,
105
+ // }
106
+ ],
107
+ ];
108
+
109
+ function renderGroup(group: DropdownOptionInputProps[]) {
110
+ return group.map((option) => {
111
+ const label = typeof option.label === 'string' ? [option.label] : option.label;
112
+ return {
113
+ ...option,
114
+ label: label,
115
+ primaryAction: option.primaryAction ?? false,
116
+ id: label.join('').toLowerCase().replace(/ /g, '-'),
117
+ };
118
+ });
119
+ }
120
+
121
+ export function getAIDropdownOptions() {
122
+ const renderedOptions = aiDropdownOptions.map((group, index) => {
123
+ return {
124
+ options: renderGroup(group),
125
+ isLast: index === aiDropdownOptions.length - 1,
126
+ reactKey: index,
127
+ };
128
+ });
129
+
130
+ const allOptions = renderedOptions.flatMap((group) => group.options);
131
+ const primaryAction = allOptions.find((o) => o.primaryAction) ?? allOptions[0]!;
132
+
133
+ return {
134
+ primaryAction,
135
+ groups: renderedOptions,
136
+ };
137
+ }
138
+
139
+ export function wireAIDropdown() {
140
+ const { primaryAction, groups } = getAIDropdownOptions();
141
+ function triggerOption(id: string) {
142
+ const option = groups.flatMap((group) => group.options).find((option) => option.id === id);
143
+ if (!option) return;
144
+ option.onClick();
145
+ }
146
+
147
+ document.addEventListener(getPageLoadEvent(), () => {
148
+ const dropdowns = document.querySelectorAll('[data-dropdown-id]');
149
+ dropdowns.forEach((dropdown) => {
150
+ initDropdownButton({
151
+ dropdown: dropdown,
152
+ onSelect: (value) => {
153
+ triggerOption(value);
154
+ },
155
+ onPrimaryAction: () => {
156
+ triggerOption(primaryAction.id);
157
+ },
158
+ });
159
+ });
160
+ });
161
+ }
@@ -5,8 +5,6 @@ import { navigate } from 'astro:transitions/client';
5
5
  import { getPageLoadEvent } from '../helpers/getPageLoadEvent.ts';
6
6
 
7
7
  import { initDropdown } from '@stainless-api/docs-ui/src/components/scripts/dropdown';
8
- import { initDropdownButton } from '@stainless-api/ui-primitives/scripts';
9
- import { copyCurrentPageAsMarkdown, onSelectAIOption } from './ai-dropdown.ts';
10
8
 
11
9
  history.scrollRestoration = 'auto';
12
10
 
@@ -36,27 +34,6 @@ document.addEventListener(getPageLoadEvent(), () => {
36
34
  if (path) setTimeout(() => scrollToPath(path.slice(1)), 10);
37
35
  });
38
36
 
39
- document.addEventListener(getPageLoadEvent(), () => {
40
- console.log('Initializing AI Dropdown');
41
- initDropdownButton({
42
- dropdownId: 'ai-dropdown-button',
43
- onSelect: onSelectAIOption,
44
- onPrimaryAction: (el) => {
45
- copyCurrentPageAsMarkdown();
46
- const innerText = el.querySelector('[data-part="primary-action-text"]');
47
- if (!innerText) return;
48
-
49
- const originalInnerHtml = innerText.innerHTML;
50
- innerText.innerHTML = 'Copied!';
51
- el.classList.add('disabled');
52
- setTimeout(() => {
53
- innerText.innerHTML = originalInnerHtml;
54
- el.classList.remove('disabled');
55
- }, 1000);
56
- },
57
- });
58
- });
59
-
60
37
  document.addEventListener('click', (event) => {
61
38
  const toggle = (event.target as HTMLElement).closest(
62
39
  '[data-stldocs-property-toggle-expanded] > .stldocs-expand-toggle-content',
@@ -1,4 +1,4 @@
1
- // import { ENABLE_CLIENT_ROUTER } from 'virtual:stl-stl-starlight-virtual-module';
1
+ // import { ENABLE_CLIENT_ROUTER } from 'virtual:stl-docs-virtual-module';
2
2
 
3
3
  export function getPageLoadEvent() {
4
4
  // if (ENABLE_CLIENT_ROUTER) {
package/plugin/index.ts CHANGED
@@ -1,6 +1,6 @@
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
4
  import { config } from 'dotenv';
5
5
  import getPort from 'get-port';
6
6
  import { startDevServer } from './cms/server';
@@ -23,11 +23,15 @@ import {
23
23
  import { buildVirtualModuleString } from '../shared/virtualModule';
24
24
  import path from 'path';
25
25
  import fs from 'fs';
26
+ import { getSharedLogger } from '../shared/getSharedLogger';
27
+ import { resolveSrcFile } from '../resolveSrcFile';
26
28
 
27
29
  export { generateAPILink } from './generateAPIReferenceLink';
28
30
  export type { ReferenceSidebarConfigItem };
29
31
 
30
- config();
32
+ config({
33
+ quiet: true,
34
+ });
31
35
 
32
36
  let sidebarIdCounter = 0;
33
37
 
@@ -108,6 +112,7 @@ function tmpGetCMSServerConfig(specRetrieverConfig: SpecRetrieverConfig) {
108
112
 
109
113
  async function stlStarlightAstroIntegration(
110
114
  pluginConfig: NormalizedStainlessStarlightConfig,
115
+ stlStarlightPluginLogger: AstroIntegrationLogger,
111
116
  ): Promise<AstroIntegration> {
112
117
  const virtualId = `virtual:stl-starlight-virtual-module`;
113
118
  // The '\0' prefix tells Vite “this is a virtual module” and prevents it from being resolved again.
@@ -117,11 +122,12 @@ async function stlStarlightAstroIntegration(
117
122
 
118
123
  const { apiKey, version, devPaths } = tmpGetCMSServerConfig(pluginConfig.specRetrieverConfig);
119
124
 
120
- const cmsServer = await startDevServer({
125
+ const cmsServer = startDevServer({
121
126
  port: CMS_PORT,
122
- apiKey,
127
+ apiKey: apiKey.value,
123
128
  version,
124
129
  devPaths,
130
+ logger: stlStarlightPluginLogger,
125
131
  getGeneratedSidebarConfig: (id: number) => {
126
132
  const config = sidebarConfigs.get(id);
127
133
  if (!config) {
@@ -134,34 +140,40 @@ async function stlStarlightAstroIntegration(
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
+ }) => {
150
+ const logger = getSharedLogger({ fallback: localLogger });
138
151
  const projectDir = astroConfig.root.pathname;
139
152
 
140
153
  const middlewareFile = path.join(projectDir, 'middleware.stainless.ts');
141
154
 
142
155
  let vmMiddlewareExport = 'export const MIDDLEWARE = {};';
143
156
  if (fs.existsSync(middlewareFile)) {
144
- logger.info(`Loading middleware from ${middlewareFile}`);
157
+ logger.debug(`Loading middleware from ${middlewareFile}`);
145
158
  vmMiddlewareExport = `export { default as MIDDLEWARE } from '${middlewareFile}';`;
146
159
  }
147
160
 
148
161
  injectRoute({
149
- pattern: `${pluginConfig.basePath}/[...slug].md`,
150
- entrypoint: '@stainless-api/docs/MarkdownRoute',
162
+ pattern: `${pluginConfig.basePath}/[...slug]/index.md`,
163
+ entrypoint: resolveSrcFile('/plugin/routes/markdown.ts'),
151
164
  prerender: command === 'build',
152
165
  });
153
166
 
154
- const astroFile = command === 'build' ? 'DocsStaticRoute' : 'DocsRoute';
167
+ const astroFile = command === 'build' ? 'DocsStatic' : 'Docs';
155
168
  injectRoute({
156
169
  pattern: `${pluginConfig.basePath}/[...slug]`,
157
- // in prod I think this points to @stainless-starlight/components/docs.astro
158
- entrypoint: `@stainless-api/docs/${astroFile}`,
170
+ entrypoint: resolveSrcFile(`/plugin/routes/${astroFile}.astro`),
159
171
  prerender: command === 'build',
160
172
  });
161
173
 
162
174
  injectRoute({
163
175
  pattern: pluginConfig.basePath,
164
- entrypoint: '@stainless-api/docs/OverviewRoute',
176
+ entrypoint: resolveSrcFile('/plugin/routes/Overview.astro'),
165
177
  prerender: command === 'build',
166
178
  });
167
179
 
@@ -213,6 +225,8 @@ async function stlStarlightAstroIntegration(
213
225
  EXPERIMENTAL_COLLAPSIBLE_SNIPPETS: pluginConfig.experimentalCollapsibleSnippets,
214
226
  PROPERTY_SETTINGS: pluginConfig.propertySettings,
215
227
  SEARCH: pluginConfig.search,
228
+ // @ts-expect-error internal prop
229
+ ENABLE_CONTEXT_MENU: pluginConfig._contextMenu,
216
230
  }),
217
231
  vmMiddlewareExport,
218
232
  ].join('\n');
@@ -241,15 +255,20 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
241
255
  command,
242
256
  config: starlightConfig,
243
257
  astroConfig,
244
- logger,
258
+ logger: localLogger,
245
259
  }) => {
246
260
  if (command !== 'build' && command !== 'dev') {
247
261
  return;
248
262
  }
249
263
 
264
+ const logger = getSharedLogger({ fallback: localLogger });
265
+
250
266
  const configParseResult = parseStarlightPluginConfig(someUserConfig, command);
251
267
  if (configParseResult.result === 'error') {
252
- logger.error(configParseResult.message);
268
+ const errorLines = configParseResult.message.split('\n');
269
+ for (const line of errorLines) {
270
+ logger.error(line);
271
+ }
253
272
  process.exit(1);
254
273
  }
255
274
  const config = configParseResult.config;
@@ -260,17 +279,30 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
260
279
  addIntegration(react());
261
280
  }
262
281
 
282
+ if ('apiKey' in config.specRetrieverConfig) {
283
+ if (!config.specRetrieverConfig.apiKey) {
284
+ logger.info(`Stainless credentials not loaded`);
285
+ } else if (config.specRetrieverConfig.apiKey.source === 'explicit-config') {
286
+ logger.info(`Stainless credentials loaded from user config`);
287
+ } else if (config.specRetrieverConfig.apiKey.source === 'environment-variable') {
288
+ logger.info('Stainless credentials loaded from `STAINLESS_API_KEY` environment variable');
289
+ } else if (config.specRetrieverConfig.apiKey.source === 'cli') {
290
+ logger.info('Stainless credentials loaded from `stl` CLI');
291
+ }
292
+ }
293
+
263
294
  if (
264
295
  command === 'build' &&
265
296
  config.specRetrieverConfig.kind === 'local_spec_server_with_remote_files'
266
297
  ) {
267
298
  await buildAlgoliaIndex({
268
299
  version: config.specRetrieverConfig.version,
269
- apiKey: config.specRetrieverConfig.apiKey,
300
+ apiKey: config.specRetrieverConfig.apiKey.value,
301
+ logger,
270
302
  });
271
303
  }
272
304
 
273
- addIntegration(await stlStarlightAstroIntegration(config));
305
+ addIntegration(await stlStarlightAstroIntegration(config, logger));
274
306
 
275
307
  if (starlightConfig.sidebar) {
276
308
  // for pagination (https://starlight.astro.build/reference/configuration/#pagination) to work correctly
@@ -295,7 +327,7 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
295
327
  });
296
328
 
297
329
  addRouteMiddleware({
298
- entrypoint: '@stainless-api/docs/replaceSidebarPlaceholderMiddleware',
330
+ entrypoint: resolveSrcFile('/plugin/replaceSidebarPlaceholderMiddleware.ts'),
299
331
  order: 'post',
300
332
  });
301
333
  },
@@ -44,7 +44,7 @@ export function applyLanguageToLinks(basePath?: string, defaultLanguage?: string
44
44
  `[data-stldocs-overview],[data-stldocs-method],a.nav-link[href^='${basePath}']`,
45
45
  );
46
46
 
47
- for (var link of links) {
47
+ for (const link of links) {
48
48
  const href = link.getAttribute('href');
49
49
  const prefix = generatePrefix(basePath, language);
50
50
  if (href?.startsWith(basePath) && !href?.startsWith(prefix)) {