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

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 +153 -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 +48 -17
  18. package/plugin/languages.ts +1 -1
  19. package/plugin/loadPluginConfig.ts +100 -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 +64 -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 +77 -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 +5 -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,7 @@ async function stlStarlightAstroIntegration(
213
225
  EXPERIMENTAL_COLLAPSIBLE_SNIPPETS: pluginConfig.experimentalCollapsibleSnippets,
214
226
  PROPERTY_SETTINGS: pluginConfig.propertySettings,
215
227
  SEARCH: pluginConfig.search,
228
+ ENABLE_CONTEXT_MENU: pluginConfig.contextMenu,
216
229
  }),
217
230
  vmMiddlewareExport,
218
231
  ].join('\n');
@@ -241,15 +254,20 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
241
254
  command,
242
255
  config: starlightConfig,
243
256
  astroConfig,
244
- logger,
257
+ logger: localLogger,
245
258
  }) => {
246
259
  if (command !== 'build' && command !== 'dev') {
247
260
  return;
248
261
  }
249
262
 
263
+ const logger = getSharedLogger({ fallback: localLogger });
264
+
250
265
  const configParseResult = parseStarlightPluginConfig(someUserConfig, command);
251
266
  if (configParseResult.result === 'error') {
252
- logger.error(configParseResult.message);
267
+ const errorLines = configParseResult.message.split('\n');
268
+ for (const line of errorLines) {
269
+ logger.error(line);
270
+ }
253
271
  process.exit(1);
254
272
  }
255
273
  const config = configParseResult.config;
@@ -260,17 +278,30 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
260
278
  addIntegration(react());
261
279
  }
262
280
 
281
+ if ('apiKey' in config.specRetrieverConfig) {
282
+ if (!config.specRetrieverConfig.apiKey) {
283
+ logger.info(`Stainless credentials not loaded`);
284
+ } else if (config.specRetrieverConfig.apiKey.source === 'explicit-config') {
285
+ logger.info(`Stainless credentials loaded from user config`);
286
+ } else if (config.specRetrieverConfig.apiKey.source === 'environment-variable') {
287
+ logger.info('Stainless credentials loaded from `STAINLESS_API_KEY` environment variable');
288
+ } else if (config.specRetrieverConfig.apiKey.source === 'cli') {
289
+ logger.info('Stainless credentials loaded from `stl` CLI');
290
+ }
291
+ }
292
+
263
293
  if (
264
294
  command === 'build' &&
265
295
  config.specRetrieverConfig.kind === 'local_spec_server_with_remote_files'
266
296
  ) {
267
297
  await buildAlgoliaIndex({
268
298
  version: config.specRetrieverConfig.version,
269
- apiKey: config.specRetrieverConfig.apiKey,
299
+ apiKey: config.specRetrieverConfig.apiKey.value,
300
+ logger,
270
301
  });
271
302
  }
272
303
 
273
- addIntegration(await stlStarlightAstroIntegration(config));
304
+ addIntegration(await stlStarlightAstroIntegration(config, logger));
274
305
 
275
306
  if (starlightConfig.sidebar) {
276
307
  // for pagination (https://starlight.astro.build/reference/configuration/#pagination) to work correctly
@@ -295,7 +326,7 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
295
326
  });
296
327
 
297
328
  addRouteMiddleware({
298
- entrypoint: '@stainless-api/docs/replaceSidebarPlaceholderMiddleware',
329
+ entrypoint: resolveSrcFile('/plugin/replaceSidebarPlaceholderMiddleware.ts'),
299
330
  order: 'post',
300
331
  });
301
332
  },
@@ -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)) {