@stainless-api/docs 0.1.0-beta.0

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 (119) hide show
  1. package/.env.example +1 -0
  2. package/CHANGELOG.md +13 -0
  3. package/README.md +11 -0
  4. package/components/variables.css +139 -0
  5. package/eslint.config.js +10 -0
  6. package/package.json +74 -0
  7. package/plugin/assets/fonts/geist/OFL.txt +93 -0
  8. package/plugin/assets/fonts/geist/geist-italic-latin-ext.woff2 +0 -0
  9. package/plugin/assets/fonts/geist/geist-italic-latin.woff2 +0 -0
  10. package/plugin/assets/fonts/geist/geist-latin-ext.woff2 +0 -0
  11. package/plugin/assets/fonts/geist/geist-latin.woff2 +0 -0
  12. package/plugin/assets/fonts/geist/geist-mono-italic-latin-ext.woff2 +0 -0
  13. package/plugin/assets/fonts/geist/geist-mono-italic-latin.woff2 +0 -0
  14. package/plugin/assets/fonts/geist/geist-mono-latin-ext.woff2 +0 -0
  15. package/plugin/assets/fonts/geist/geist-mono-latin.woff2 +0 -0
  16. package/plugin/assets/languages/curl.svg +10 -0
  17. package/plugin/assets/languages/go.svg +4 -0
  18. package/plugin/assets/languages/java.svg +7 -0
  19. package/plugin/assets/languages/kotlin.svg +10 -0
  20. package/plugin/assets/languages/powershell.svg +3 -0
  21. package/plugin/assets/languages/python.svg +19 -0
  22. package/plugin/assets/languages/ruby.svg +125 -0
  23. package/plugin/assets/languages/terraform.svg +5 -0
  24. package/plugin/assets/languages/typescript.svg +11 -0
  25. package/plugin/assets/stainless-logo-dark.png +0 -0
  26. package/plugin/assets/stainless-logo.png +0 -0
  27. package/plugin/buildAlgoliaIndex.ts +72 -0
  28. package/plugin/cms/client.ts +62 -0
  29. package/plugin/cms/server.ts +268 -0
  30. package/plugin/cms/sidebar-builder.ts +420 -0
  31. package/plugin/cms/worker.ts +122 -0
  32. package/plugin/components/SDKSelect.astro +154 -0
  33. package/plugin/components/SnippetCode.tsx +212 -0
  34. package/plugin/components/search/Search.astro +6 -0
  35. package/plugin/components/search/SearchAlgolia.astro +87 -0
  36. package/plugin/components/search/SearchIsland.tsx +100 -0
  37. package/plugin/generateAPIReferenceLink.ts +71 -0
  38. package/plugin/globalJs/ai-dropdown.ts +57 -0
  39. package/plugin/globalJs/code-snippets.ts +87 -0
  40. package/plugin/globalJs/copy.ts +37 -0
  41. package/plugin/globalJs/navigation.ts +81 -0
  42. package/plugin/globalJs/tooltip.ts +32 -0
  43. package/plugin/helpers/getPageLoadEvent.ts +8 -0
  44. package/plugin/index.ts +308 -0
  45. package/plugin/languages.ts +67 -0
  46. package/plugin/loadPluginConfig.ts +273 -0
  47. package/plugin/middlewareBuilder/stainlessMiddleware.d.ts +5 -0
  48. package/plugin/middlewareBuilder/stlStarlightMiddleware.ts +5 -0
  49. package/plugin/react/Routing.tsx +435 -0
  50. package/plugin/referencePlaceholderUtils.ts +82 -0
  51. package/plugin/replaceSidebarPlaceholderMiddleware.ts +50 -0
  52. package/plugin/routes/Docs.astro +171 -0
  53. package/plugin/routes/DocsStatic.astro +14 -0
  54. package/plugin/routes/Overview.astro +67 -0
  55. package/plugin/routes/markdown.ts +58 -0
  56. package/plugin/vendor/preview.worker.docs.js +21657 -0
  57. package/plugin/vendor/templates/go.md +314 -0
  58. package/plugin/vendor/templates/java.md +87 -0
  59. package/plugin/vendor/templates/kotlin.md +87 -0
  60. package/plugin/vendor/templates/node.md +233 -0
  61. package/plugin/vendor/templates/python.md +249 -0
  62. package/plugin/vendor/templates/ruby.md +145 -0
  63. package/plugin/vendor/templates/terraform.md +60 -0
  64. package/plugin/vendor/templates/typescript.md +317 -0
  65. package/scripts/vendor_deps.ts +50 -0
  66. package/shared/virtualModule.ts +7 -0
  67. package/stl-docs/components/APIReferenceAIDropdown.tsx +86 -0
  68. package/stl-docs/components/ClientRouterHead.astro +41 -0
  69. package/stl-docs/components/Header.astro +91 -0
  70. package/stl-docs/components/Sidebar.astro +11 -0
  71. package/stl-docs/components/ThemeSelect.astro +225 -0
  72. package/stl-docs/components/content-panel/ContentBreadcrumbs.tsx +84 -0
  73. package/stl-docs/components/content-panel/ContentPanel.astro +72 -0
  74. package/stl-docs/components/content-panel/ProseAIDropdown.tsx +64 -0
  75. package/stl-docs/components/headers/DefaultHeader.astro +36 -0
  76. package/stl-docs/components/headers/HeaderLinks.astro +16 -0
  77. package/stl-docs/components/headers/SplashMobileMenuToggle.astro +49 -0
  78. package/stl-docs/components/headers/StackedHeader.astro +75 -0
  79. package/stl-docs/components/mintlify-compat/Accordion.astro +46 -0
  80. package/stl-docs/components/mintlify-compat/AccordionGroup.astro +25 -0
  81. package/stl-docs/components/mintlify-compat/Card.tsx +32 -0
  82. package/stl-docs/components/mintlify-compat/Columns.astro +66 -0
  83. package/stl-docs/components/mintlify-compat/Frame.astro +37 -0
  84. package/stl-docs/components/mintlify-compat/Step.astro +58 -0
  85. package/stl-docs/components/mintlify-compat/Steps.astro +17 -0
  86. package/stl-docs/components/mintlify-compat/Tab.astro +13 -0
  87. package/stl-docs/components/mintlify-compat/Tabs.astro +7 -0
  88. package/stl-docs/components/mintlify-compat/callouts/Callout.astro +7 -0
  89. package/stl-docs/components/mintlify-compat/callouts/Check.astro +7 -0
  90. package/stl-docs/components/mintlify-compat/callouts/Danger.astro +7 -0
  91. package/stl-docs/components/mintlify-compat/callouts/Info.astro +7 -0
  92. package/stl-docs/components/mintlify-compat/callouts/Note.astro +7 -0
  93. package/stl-docs/components/mintlify-compat/callouts/Tip.astro +7 -0
  94. package/stl-docs/components/mintlify-compat/callouts/Warning.astro +7 -0
  95. package/stl-docs/components/mintlify-compat/callouts/index.ts +9 -0
  96. package/stl-docs/components/mintlify-compat/card.css +44 -0
  97. package/stl-docs/components/mintlify-compat/index.ts +15 -0
  98. package/stl-docs/components/nav-tabs/NavDropdown.astro +106 -0
  99. package/stl-docs/components/nav-tabs/NavTabs.astro +165 -0
  100. package/stl-docs/components/nav-tabs/SecondaryNavTabs.astro +62 -0
  101. package/stl-docs/components/nav-tabs/buildNavLinks.ts +14 -0
  102. package/stl-docs/index.ts +174 -0
  103. package/stl-docs/loadStlDocsConfig.ts +160 -0
  104. package/stl-docs/redirects.ts +33 -0
  105. package/stl-docs/tabsMiddleware.ts +183 -0
  106. package/styles/code.css +189 -0
  107. package/styles/fonts.css +68 -0
  108. package/styles/links.css +51 -0
  109. package/styles/mintlify-compat.css +1 -0
  110. package/styles/overrides.css +79 -0
  111. package/styles/page.css +76 -0
  112. package/styles/sdk_select.css +11 -0
  113. package/styles/search.css +85 -0
  114. package/styles/sidebar.css +168 -0
  115. package/styles/toc.css +42 -0
  116. package/styles/variables.css +18 -0
  117. package/theme.css +15 -0
  118. package/tsconfig.json +18 -0
  119. package/virtual-module.d.ts +43 -0
@@ -0,0 +1,72 @@
1
+ import Markdoc from '@markdoc/markdoc';
2
+ import Stainless from '@stainless-api/sdk';
3
+ import { createSDKJSON, parseInputs, transformOAS } from './cms/worker';
4
+ import type * as SDKJSON from '~/lib/json-spec-v2/types';
5
+ import { Languages } from '@stainless-api/docs-ui/src/routing';
6
+ import { buildIndex } from '@stainless-api/docs-ui/src/search/providers/algolia';
7
+ import type { VersionUserConfig } from './loadPluginConfig';
8
+
9
+ const markdocConfig = {
10
+ nodes: {
11
+ document: { ...Markdoc.nodes.document, render: '' },
12
+ },
13
+ };
14
+
15
+ function renderMarkdown(content?: string) {
16
+ if (!content) return null;
17
+
18
+ const ast = Markdoc.parse(content);
19
+ const transformed = Markdoc.transform(ast, markdocConfig);
20
+ return Markdoc.renderers.html(transformed);
21
+ }
22
+
23
+ export async function buildAlgoliaIndex({ version, apiKey }: { version: VersionUserConfig; apiKey: string }) {
24
+ const client = new Stainless({ apiKey });
25
+ const configs = await client.projects.configs.retrieve({
26
+ project: version.stainlessProject,
27
+ branch: version.branch,
28
+ include: 'openapi',
29
+ });
30
+
31
+ const configYML = Object.values(configs)[0] as { content: any };
32
+ const oasJson = Object.values(configs)[1] as { content: any };
33
+ const configStr = configYML['content'];
34
+ const oasStr = oasJson['content'];
35
+ const { oas, config } = await parseInputs({
36
+ oas: oasStr,
37
+ config: configStr,
38
+ });
39
+
40
+ const transformedOAS = await transformOAS({ oas, config });
41
+
42
+ const languages =
43
+ config.docs?.languages ??
44
+ (Object.entries(config.targets)
45
+ // @ts-expect-error we don't have the actual Stainless config type here
46
+ .filter(([name, target]) => Languages.includes(name) && !target.skip)
47
+ .map(([name]) => name) as SDKJSON.SpecLanguage[]);
48
+
49
+ const sdkJson = await createSDKJSON({
50
+ oas: transformedOAS,
51
+ config,
52
+ languages,
53
+ });
54
+
55
+ const {
56
+ PUBLIC_ALGOLIA_APP_ID: appId,
57
+ PUBLIC_ALGOLIA_INDEX: indexName,
58
+ PRIVATE_ALGOLIA_WRITE_KEY: algoliaWriteKey,
59
+ } = process.env;
60
+
61
+ if (!appId || !indexName || !algoliaWriteKey) {
62
+ const missing = [
63
+ !appId && 'PUBLIC_ALGOLIA_APP_ID',
64
+ !indexName && 'PUBLIC_ALGOLIA_INDEX',
65
+ !algoliaWriteKey && 'PRIVATE_ALGOLIA_WRITE_KEY',
66
+ ].filter(Boolean);
67
+ console.warn(`⚠️ Skipping Algolia indexing due to missing environment variables: ${missing.join(', ')}`);
68
+ return;
69
+ }
70
+ await buildIndex(appId, indexName, algoliaWriteKey, sdkJson, renderMarkdown);
71
+ console.log('Indexing complete.');
72
+ }
@@ -0,0 +1,62 @@
1
+ import { CMS_PORT } from 'virtual:stl-starlight-virtual-module';
2
+ import type { SpecResp } from './server';
3
+ import type { StarlightRouteData } from '@astrojs/starlight/route-data';
4
+ import type { BuildSidebarParams } from './sidebar-builder';
5
+
6
+ const SERVER_URL = `http://localhost:${CMS_PORT}`;
7
+
8
+ async function makeRequest<T>(method: 'POST' | 'GET', endpoint: string, body?: any) {
9
+ const response = await fetch(`${SERVER_URL}${endpoint}`, {
10
+ method,
11
+ headers: {
12
+ 'Content-Type': 'application/json',
13
+ },
14
+ body: body ? JSON.stringify(body) : undefined,
15
+ });
16
+ return response.json() as T;
17
+ }
18
+
19
+ class Client {
20
+ private specCache: SpecResp | null = null;
21
+ private clientId: string;
22
+
23
+ get id() {
24
+ return this.clientId;
25
+ }
26
+
27
+ async buildSidebar(params: BuildSidebarParams & { sidebarId: number }) {
28
+ const response = await makeRequest<{ data: StarlightRouteData['sidebar'] }>(
29
+ 'POST',
30
+ '/build_sidebar',
31
+ params,
32
+ );
33
+ return response.data;
34
+ }
35
+
36
+ async getSpec() {
37
+ const cached = this.specCache;
38
+
39
+ const response = await makeRequest<SpecResp>('POST', '/retrieve_spec', {
40
+ currentId: cached?.id,
41
+ });
42
+
43
+ if (response.data === null) {
44
+ if (!cached) {
45
+ throw new Error('Cache should not be null');
46
+ }
47
+ if (cached.data === null) {
48
+ throw new Error('Cached spec should not be null');
49
+ }
50
+ return cached.data;
51
+ }
52
+ // console.log("invalidating spec cache", this.clientId);
53
+ this.specCache = response;
54
+ return response.data;
55
+ }
56
+
57
+ constructor() {
58
+ this.clientId = crypto.randomUUID();
59
+ }
60
+ }
61
+
62
+ export const cmsClient = new Client();
@@ -0,0 +1,268 @@
1
+ import { createServer, IncomingMessage } from 'http';
2
+ import { readFile } from 'fs/promises';
3
+
4
+ import type * as SDKJSON from '~/lib/json-spec-v2/types';
5
+ import { createSDKJSON, parseInputs, transformOAS } from './worker';
6
+ import { Languages, parseRoute, type DocsLanguage } from '@stainless-api/docs-ui/src/routing';
7
+ import Stainless from '@stainless-api/sdk';
8
+
9
+ import {
10
+ toStarlightSidebar,
11
+ type GeneratedSidebarConfig,
12
+ SidebarConfigItemsBuilder,
13
+ } from './sidebar-builder';
14
+ import type { VersionUserConfig } from '../loadPluginConfig';
15
+
16
+ export type InputFilePaths = {
17
+ oasPath?: string;
18
+ configPath?: string;
19
+ };
20
+
21
+ async function versionInfo(project: string, apiKey: string) {
22
+ const data = await fetch(`https://api.stainless.com/api/projects/${project}/package-versions`, {
23
+ headers: { Authorization: `Bearer ${apiKey}` },
24
+ });
25
+
26
+ const content = await data.text();
27
+ return JSON.parse(content);
28
+ }
29
+
30
+ async function loadSpec({
31
+ apiKey,
32
+ devPaths,
33
+ version,
34
+ }: {
35
+ apiKey: string;
36
+ devPaths: InputFilePaths;
37
+ version: VersionUserConfig;
38
+ }) {
39
+ let oasStr: string;
40
+ let configStr: string;
41
+ let versions: Record<DocsLanguage, string> | undefined;
42
+
43
+ if (devPaths.oasPath && devPaths.configPath) {
44
+ [oasStr, configStr] = await Promise.all([
45
+ readFile(devPaths.oasPath, 'utf-8'),
46
+ readFile(devPaths.configPath, 'utf-8'),
47
+ ]);
48
+ } else {
49
+ const client = new Stainless({ apiKey });
50
+ const configs = await client.projects.configs.retrieve({
51
+ project: version.stainlessProject,
52
+ branch: version.branch,
53
+ include: 'openapi',
54
+ });
55
+
56
+ versions = await versionInfo(version.stainlessProject, apiKey);
57
+
58
+ const configYML = Object.values(configs)[0] as { content: any };
59
+ const oasJson = Object.values(configs)[1] as { content: any };
60
+ oasStr = oasJson['content'];
61
+ configStr = configYML['content'];
62
+ }
63
+
64
+ const { oas, config } = await parseInputs({
65
+ oas: oasStr,
66
+ config: configStr,
67
+ });
68
+
69
+ const transformedOAS = await transformOAS({ oas, config });
70
+
71
+ const languages =
72
+ config.docs?.languages ??
73
+ (Object.entries(config.targets)
74
+ // @ts-expect-error we don't have the actual Stainless config type here
75
+ .filter(([name, target]) => Languages.includes(name) && !target.skip)
76
+ .map(([name]) => name) as SDKJSON.SpecLanguage[]);
77
+
78
+ const sdkJson = await createSDKJSON({
79
+ oas: transformedOAS,
80
+ config,
81
+ languages,
82
+ });
83
+
84
+ if (versions) {
85
+ for (const [lang, version] of Object.entries(versions)) {
86
+ const meta = sdkJson.metadata[lang as DocsLanguage];
87
+ if (meta?.version) meta.version = version;
88
+ }
89
+ }
90
+
91
+ const id = crypto.randomUUID();
92
+
93
+ return {
94
+ data: sdkJson,
95
+ id,
96
+ };
97
+ }
98
+
99
+ class Spec {
100
+ private specPromise: Promise<{ id: string; data: SDKJSON.Spec }>;
101
+ private devPaths: InputFilePaths;
102
+ private apiKey: string;
103
+ private version: VersionUserConfig;
104
+
105
+ reload() {
106
+ this.specPromise = loadSpec({
107
+ apiKey: this.apiKey,
108
+ devPaths: this.devPaths,
109
+ version: this.version,
110
+ });
111
+ }
112
+
113
+ async forceGetSpec() {
114
+ const spec = await this.specPromise;
115
+ return spec;
116
+ }
117
+
118
+ async getSpec(currentId: string | undefined | null) {
119
+ const spec = await this.specPromise;
120
+
121
+ if (currentId === spec.id) {
122
+ return {
123
+ id: spec.id,
124
+ data: null,
125
+ };
126
+ }
127
+
128
+ return spec;
129
+ }
130
+
131
+ constructor(apiKey: string, version: VersionUserConfig, devPaths: InputFilePaths) {
132
+ this.specPromise = loadSpec({
133
+ apiKey,
134
+ devPaths,
135
+ version,
136
+ });
137
+ this.devPaths = devPaths;
138
+ this.apiKey = apiKey;
139
+ this.version = version;
140
+ }
141
+ }
142
+
143
+ export type SpecResp = Awaited<ReturnType<Spec['getSpec']>>;
144
+
145
+ // will be necessary once we add POST methods
146
+ function readJsonBody(req: IncomingMessage): Promise<any> {
147
+ return new Promise((resolve, reject) => {
148
+ let body = '';
149
+ req.on('data', (chunk) => {
150
+ body += chunk.toString();
151
+ });
152
+ req.on('end', () => {
153
+ try {
154
+ const data = JSON.parse(body);
155
+ resolve(data);
156
+ } catch (error) {
157
+ reject(error);
158
+ }
159
+ });
160
+ });
161
+ }
162
+
163
+ export function startDevServer({
164
+ port,
165
+ version,
166
+ devPaths,
167
+ apiKey,
168
+ getGeneratedSidebarConfig,
169
+ }: {
170
+ port: number;
171
+ version: VersionUserConfig;
172
+ devPaths: InputFilePaths;
173
+ apiKey: string;
174
+ getGeneratedSidebarConfig: (id: number) => GeneratedSidebarConfig | null;
175
+ }) {
176
+ const spec = new Spec(apiKey, version, devPaths);
177
+
178
+ const server = createServer(async (req, res) => {
179
+ // Add CORS headers
180
+ res.setHeader('Access-Control-Allow-Origin', '*');
181
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
182
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
183
+
184
+ // Handle preflight requests
185
+ if (req.method === 'OPTIONS') {
186
+ res.writeHead(204);
187
+ res.end();
188
+ return;
189
+ }
190
+
191
+ function respond(code: number, body: any) {
192
+ res.setHeader('Content-Type', 'application/json');
193
+ res.writeHead(code);
194
+ res.end(JSON.stringify(body));
195
+ }
196
+
197
+ try {
198
+ if (req.method === 'POST' && req.url === '/retrieve_spec') {
199
+ const body = await readJsonBody(req);
200
+
201
+ const currentSpec = await spec.getSpec(body.currentId);
202
+
203
+ return respond(200, currentSpec);
204
+ }
205
+
206
+ if (req.method === 'POST' && req.url === '/build_sidebar') {
207
+ const currentSpec = await spec.forceGetSpec();
208
+ const body = await readJsonBody(req);
209
+ const sidebarId: number = body.sidebarId;
210
+
211
+ const sidebarConfig = getGeneratedSidebarConfig(sidebarId);
212
+
213
+ const sidebarGenerateOptions = sidebarConfig?.options;
214
+
215
+ const { stainlessPath: currentStainlessPath, language } = parseRoute(body.basePath, body.currentSlug);
216
+
217
+ const configItemsBuilder = new SidebarConfigItemsBuilder(
218
+ currentSpec.data,
219
+ language,
220
+ sidebarGenerateOptions,
221
+ );
222
+
223
+ let userSidebarConfig = configItemsBuilder.generateItems();
224
+
225
+ if (sidebarConfig && sidebarConfig.transformFn) {
226
+ const transformedSidebarConfig = sidebarConfig.transformFn(userSidebarConfig, language);
227
+ // the user may not have returned, but they may have mutated the config in place
228
+ // if they did return, we use that
229
+ if (transformedSidebarConfig) {
230
+ userSidebarConfig = transformedSidebarConfig;
231
+ }
232
+ }
233
+
234
+ const starlightSidebar = toStarlightSidebar({
235
+ basePath: body.basePath,
236
+ currentSlug: body.currentSlug,
237
+ spec: currentSpec.data,
238
+ entries: userSidebarConfig,
239
+ currentStainlessPath,
240
+ currentLanguage: language,
241
+ });
242
+
243
+ return respond(200, { data: starlightSidebar });
244
+ }
245
+
246
+ return respond(404, { message: 'Not found' });
247
+ } catch (e) {
248
+ return respond(500, { message: 'Unexpected error', error: e });
249
+ }
250
+ });
251
+
252
+ server.listen(port, () => {
253
+ console.log(`Server is running on port ${port}`);
254
+ });
255
+
256
+ return {
257
+ invalidate: () => {
258
+ spec.reload();
259
+ },
260
+ destroy: function destroy() {
261
+ return new Promise<void>((resolve) => {
262
+ server.close(() => {
263
+ resolve();
264
+ });
265
+ });
266
+ },
267
+ };
268
+ }