@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,420 @@
1
+ import type * as SDKJSON from '~/lib/json-spec-v2/types';
2
+ import { generateRoute, walkTree, type DocsLanguage } from '@stainless-api/docs-ui/src/routing';
3
+ import type { StarlightRouteData } from '@astrojs/starlight/route-data';
4
+
5
+ function isResourceNonEmpty(resource: SDKJSON.Resource) {
6
+ return (
7
+ Object.keys(resource.methods ?? {}).length > 0 ||
8
+ Object.keys(resource.subresources ?? {}).length > 0 ||
9
+ Object.keys(resource.models ?? {}).length > 0
10
+ );
11
+ }
12
+
13
+ interface UserSidebarConfigItem {
14
+ label: string;
15
+ badge: StarlightRouteData['sidebar'][number]['badge'];
16
+ }
17
+
18
+ type MethodMetadata = {
19
+ deprecated: boolean;
20
+ methodName: string;
21
+ title: string;
22
+ summary: string | undefined;
23
+ description: string | undefined;
24
+ httpMethod: string;
25
+ };
26
+ interface UserSidebarMethodPage extends UserSidebarConfigItem {
27
+ /**
28
+ * The kind is the type of the item that will be generated.
29
+ */
30
+ kind: 'method_page';
31
+ /**
32
+ * The key + kind is how Stainless identifies the item to build the final sidebar.
33
+ * DO NOT modify the key or kind of the item.
34
+ * You may change any other properties like the label. You may also completely remove the item.
35
+ *
36
+ * Example: post v1/{accounts}/charges
37
+ * The key is identical to the endpoint specified in your Stainless config file.
38
+ */
39
+ key: string;
40
+ metadata: MethodMetadata;
41
+ }
42
+
43
+ interface UserSidebarResourceOverviewPage extends UserSidebarConfigItem {
44
+ /**
45
+ * The kind is the type of the item that will be generated.
46
+ */
47
+ kind: 'resource_overview_page';
48
+ /**
49
+ * The key + kind is how Stainless identifies the item to build the final sidebar.
50
+ * DO NOT modify the key or kind of the item.
51
+ * You may change any other properties like the label. You may also completely remove the item.
52
+ *
53
+ * Example: #/resources/accounts/subresources/charges
54
+ * The key is the path to the resource specified in your Stainless config file.
55
+ */
56
+ key: string;
57
+ metadata: {
58
+ subResourceCount: number;
59
+ methodCount: number;
60
+ modelCount: number;
61
+ };
62
+ }
63
+
64
+ interface UserSidebarAPIOverviewPage extends UserSidebarConfigItem {
65
+ /**
66
+ * The kind is the type of the item that will be generated.
67
+ */
68
+ kind: 'api_overview_page';
69
+ /**
70
+ * The key + kind is how Stainless identifies the item to build the final sidebar.
71
+ * DO NOT modify the key or kind of the item.
72
+ * You may change any other properties like the label. You may also completely remove the item.
73
+ *
74
+ * Example: #/resources/accounts/subresources/charges
75
+ * The key is the path to the resource specified in your Stainless config file.
76
+ */
77
+ key: 'api_overview_page';
78
+ }
79
+
80
+ interface ReferenceSidebarGroup extends UserSidebarConfigItem {
81
+ kind: 'group';
82
+ label: string;
83
+ /**
84
+ * Whether the group should be collapsed by default.
85
+ */
86
+ collapsed: boolean;
87
+ /**
88
+ * If this group is a group for a resource, this will be the key of the resource.
89
+ */
90
+ resourceGroupKey?: string;
91
+ entries: ReferenceSidebarConfigItem[];
92
+ }
93
+
94
+ export type ReferenceSidebarConfigItem =
95
+ | UserSidebarMethodPage
96
+ | UserSidebarResourceOverviewPage
97
+ | UserSidebarAPIOverviewPage
98
+ | ReferenceSidebarGroup;
99
+
100
+ export type ReferenceSidebarConfigGenerateOptions = {
101
+ /**
102
+ * Whether to include a "shared" page for models defined at the client level in the sidebar.
103
+ * Defaults to `false`.
104
+ */
105
+ includeSharedModels?: boolean;
106
+
107
+ /**
108
+ * Whether to exclude the resource overview page for each resource in the sidebar.
109
+ * Defaults to `false`.
110
+ */
111
+ excludeResourceOverviewPages?: boolean;
112
+ };
113
+
114
+ export type GeneratedSidebarConfig = {
115
+ options?: ReferenceSidebarConfigGenerateOptions | undefined;
116
+ transformFn?: (
117
+ sidebar: ReferenceSidebarConfigItem[],
118
+ language: DocsLanguage,
119
+ ) => ReferenceSidebarConfigItem[] | void;
120
+ };
121
+
122
+ function countKeys(obj?: Record<string, any>) {
123
+ let o = obj ?? {};
124
+ return Object.keys(o).length;
125
+ }
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>;
139
+
140
+ function makeAPIOverviewPage(): UserSidebarAPIOverviewPage {
141
+ return {
142
+ kind: 'api_overview_page',
143
+ label: 'Overview',
144
+ key: 'api_overview_page',
145
+ badge: undefined,
146
+ };
147
+ }
148
+
149
+ function pullOutSharedModelsResource(resources: SDKJSON.Resource[]): {
150
+ resources: SDKJSON.Resource[];
151
+ sharedModelsResource: SDKJSON.Resource | null;
152
+ } {
153
+ let sharedModelsResource: SDKJSON.Resource | null = null;
154
+ const remainingResources = resources.filter((r) => {
155
+ if (r.configRef === '#/resources/$shared') {
156
+ sharedModelsResource = r;
157
+ return false;
158
+ }
159
+ return true;
160
+ });
161
+
162
+ return {
163
+ resources: remainingResources,
164
+ sharedModelsResource,
165
+ };
166
+ }
167
+
168
+ export class SidebarConfigItemsBuilder {
169
+ private getMethodDeclForLanguage(entry: SDKJSON.Method) {
170
+ const decls = this.spec.decls[this.language] ?? {};
171
+ const decl = decls[entry.stainlessPath];
172
+ if (decl !== undefined) {
173
+ if ('ident' in decl) {
174
+ return decl;
175
+ }
176
+ }
177
+ return null;
178
+ }
179
+
180
+ private toResourceOverviewPage(entry: SDKJSON.Resource): UserSidebarResourceOverviewPage {
181
+ return {
182
+ kind: 'resource_overview_page',
183
+ label: 'Overview',
184
+ key: entry.configRef,
185
+ badge: undefined,
186
+ metadata: {
187
+ subResourceCount: countKeys(entry.subresources),
188
+ methodCount: countKeys(entry.methods),
189
+ modelCount: countKeys(entry.models),
190
+ },
191
+ };
192
+ }
193
+
194
+ private toMethodPage(entry: SDKJSON.Method, decl: MethodDecl): UserSidebarMethodPage {
195
+ return {
196
+ kind: 'method_page',
197
+ label: entry.title,
198
+ key: entry.endpoint,
199
+ badge: undefined,
200
+ metadata: {
201
+ deprecated: Boolean(entry.deprecated),
202
+ methodName: this.language === 'http' ? entry.name : decl.ident,
203
+ title: entry.title,
204
+ summary: entry.summary,
205
+ description: entry.description,
206
+ httpMethod: entry.httpMethod,
207
+ },
208
+ };
209
+ }
210
+
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
+ private generateResourceGroup(resource: SDKJSON.Resource, collapsed: boolean): ReferenceSidebarGroup {
219
+ const entries: ReferenceSidebarConfigItem[] = [];
220
+ if (!this.options?.excludeResourceOverviewPages) {
221
+ entries.push(this.toResourceOverviewPage(resource));
222
+ }
223
+ const methods = Object.values(resource.methods ?? {});
224
+ const methodPages: UserSidebarMethodPage[] = [];
225
+ for (const m of methods) {
226
+ const langDecl = this.getMethodDeclForLanguage(m);
227
+ if (langDecl) {
228
+ methodPages.push(this.toMethodPage(m, langDecl));
229
+ }
230
+ }
231
+ this.sortByLabel(methodPages);
232
+ entries.push(...methodPages);
233
+
234
+ const subresources = Object.values(resource.subresources ?? {});
235
+ const subresourceGroups: ReferenceSidebarGroup[] = [];
236
+ for (const sub of subresources) {
237
+ if (isResourceNonEmpty(sub)) {
238
+ subresourceGroups.push(this.generateResourceGroup(sub, true));
239
+ }
240
+ }
241
+ this.sortByLabel(subresourceGroups);
242
+ entries.push(...subresourceGroups);
243
+
244
+ return {
245
+ kind: 'group',
246
+ badge: undefined,
247
+ label: resource.title,
248
+ resourceGroupKey: resource.configRef,
249
+ entries,
250
+ collapsed,
251
+ };
252
+ }
253
+
254
+ public generateItems(): ReferenceSidebarConfigItem[] {
255
+ const resourceMap = this.spec.resources;
256
+ let { resources, sharedModelsResource } = pullOutSharedModelsResource(Object.values(resourceMap ?? {}));
257
+
258
+ const entries: ReferenceSidebarConfigItem[] = resources.filter(isResourceNonEmpty).map((r) => {
259
+ return this.generateResourceGroup(r, false);
260
+ });
261
+
262
+ const includeSharedModels = this.options?.includeSharedModels ?? false;
263
+ if (includeSharedModels && sharedModelsResource) {
264
+ entries.unshift(this.toResourceOverviewPage(sharedModelsResource));
265
+ }
266
+
267
+ entries.unshift(makeAPIOverviewPage());
268
+
269
+ return entries;
270
+ }
271
+
272
+ constructor(
273
+ private spec: SDKJSON.Spec,
274
+ private language: DocsLanguage,
275
+ private options?: ReferenceSidebarConfigGenerateOptions,
276
+ ) {}
277
+ }
278
+
279
+ export function walkSidebarConfigItems(
280
+ sidebar: ReferenceSidebarConfigItem[],
281
+ fn: (item: ReferenceSidebarConfigItem) => void,
282
+ ) {
283
+ for (const item of sidebar) {
284
+ fn(item);
285
+ if (item.kind === 'group') {
286
+ walkSidebarConfigItems(item.entries, fn);
287
+ }
288
+ }
289
+ }
290
+
291
+ type SidebarEntry = StarlightRouteData['sidebar'][number];
292
+
293
+ // This allows us to be a bit more forgiving to the user.
294
+ // As long as they don't modify the key, we can still find the item.
295
+ function getResourceOrMethod(spec: SDKJSON.Spec, endpointOrConfigRef: string) {
296
+ for (const entry of walkTree(spec, false)) {
297
+ if (entry.data.kind === 'resource' && entry.data.configRef === endpointOrConfigRef) {
298
+ return entry;
299
+ }
300
+ if (entry.data.kind === 'http_method' && entry.data.endpoint === endpointOrConfigRef) {
301
+ return entry;
302
+ }
303
+ }
304
+ return null;
305
+ }
306
+
307
+ export function forceGenerateRoute({
308
+ basePath,
309
+ stainlessPath,
310
+ language,
311
+ }: {
312
+ basePath: string;
313
+ stainlessPath: string;
314
+ language: DocsLanguage;
315
+ }) {
316
+ const route = generateRoute(basePath, language, stainlessPath);
317
+ if (!route) {
318
+ throw new Error(`Route for path ${stainlessPath} failed to generate`);
319
+ }
320
+ return route;
321
+ }
322
+
323
+ export type BuildSidebarParams = {
324
+ basePath: string;
325
+ currentSlug: string;
326
+ };
327
+
328
+ type ToStarlightSidebarParams = BuildSidebarParams & {
329
+ spec: SDKJSON.Spec;
330
+ entries: ReferenceSidebarConfigItem[];
331
+ currentStainlessPath: string;
332
+ currentLanguage: DocsLanguage;
333
+ };
334
+
335
+ export function toStarlightSidebar({
336
+ basePath,
337
+ currentSlug,
338
+ spec,
339
+ entries,
340
+ currentStainlessPath,
341
+ currentLanguage,
342
+ }: ToStarlightSidebarParams): SidebarEntry[] {
343
+ const starlightEntries: SidebarEntry[] = [];
344
+
345
+ for (const entry of entries) {
346
+ if (entry.kind === 'api_overview_page') {
347
+ const readmeSlug = currentLanguage === 'http' ? basePath : `${basePath}/${currentLanguage}`;
348
+
349
+ starlightEntries.push({
350
+ type: 'link',
351
+ href: readmeSlug,
352
+ label: entry.label,
353
+ isCurrent: currentSlug === readmeSlug,
354
+ badge: entry.badge,
355
+ attrs: {
356
+ 'data-stldocs-overview': 'readme',
357
+ },
358
+ });
359
+ } else if (entry.kind === 'resource_overview_page' || entry.kind === 'method_page') {
360
+ const resourceOrMethod = getResourceOrMethod(spec, entry.key);
361
+ if (!resourceOrMethod) {
362
+ throw new Error(
363
+ `Resource or method not found for key ${entry.key} Make sure you don't modify the key.`,
364
+ );
365
+ }
366
+ const route = forceGenerateRoute({
367
+ basePath,
368
+ stainlessPath: resourceOrMethod.data.stainlessPath,
369
+ language: currentLanguage,
370
+ });
371
+
372
+ const isCurrent = resourceOrMethod.data.stainlessPath === currentStainlessPath;
373
+
374
+ if (resourceOrMethod.data.kind === 'http_method') {
375
+ starlightEntries.push({
376
+ type: 'link',
377
+ href: route,
378
+ label: entry.label,
379
+ isCurrent,
380
+ badge: entry.badge,
381
+ attrs: {
382
+ 'data-stldocs-method': resourceOrMethod.data.httpMethod,
383
+ },
384
+ });
385
+ } else if (resourceOrMethod.data.kind === 'resource') {
386
+ starlightEntries.push({
387
+ type: 'link',
388
+ href: route,
389
+ label: entry.label,
390
+ isCurrent,
391
+ badge: entry.badge,
392
+ attrs: {
393
+ // TODO: @Ryan: is data.name unique? This is what we used before so I'm not changing, but I am curious.
394
+ 'data-stldocs-overview': resourceOrMethod.data.name,
395
+ },
396
+ });
397
+ } else {
398
+ throw new Error(`Unknown entry kind ${JSON.stringify(entry)}`);
399
+ }
400
+ } else if (entry.kind === 'group') {
401
+ starlightEntries.push({
402
+ type: 'group',
403
+ label: entry.label,
404
+ entries: toStarlightSidebar({
405
+ basePath,
406
+ currentSlug,
407
+ spec,
408
+ currentStainlessPath,
409
+ entries: entry.entries,
410
+ currentLanguage,
411
+ }),
412
+ collapsed: entry.collapsed,
413
+ badge: entry.badge,
414
+ });
415
+ } else {
416
+ throw new Error(`Unknown entry kind ${JSON.stringify(entry)}`);
417
+ }
418
+ }
419
+ return starlightEntries;
420
+ }
@@ -0,0 +1,122 @@
1
+ import Worker from 'web-worker';
2
+ import { Languages, type DocsLanguage } from '@stainless-api/docs-ui/src/routing';
3
+ import type * as SDKJSON from '~/lib/json-spec-v2/types';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { dirname, resolve } from 'node:path';
6
+ import fs from 'fs/promises';
7
+ import pathutils from 'path';
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = dirname(__filename);
11
+
12
+ const workerPath = resolve(__dirname, '..', 'vendor', 'preview.worker.docs.js');
13
+
14
+ type OpenAPIDocument = Record<string, any>;
15
+ type ParsedConfig = Record<string, any>;
16
+
17
+ function runJob({ type, signal, data }: { type: string; signal?: AbortSignal; data: any }) {
18
+ const stainlessWorker = new Worker(workerPath, {
19
+ type: 'module',
20
+ name: 'Preview server',
21
+ });
22
+
23
+ return new Promise<any>((resolve, reject) => {
24
+ stainlessWorker.addEventListener('error', (e) => {
25
+ reject(e);
26
+ });
27
+
28
+ stainlessWorker.addEventListener('messageerror', (e) => {
29
+ reject(e);
30
+ });
31
+
32
+ stainlessWorker.addEventListener('message', (message) => {
33
+ if (message.data.type === `${type}_done`) {
34
+ resolve(message.data);
35
+ } else if (message.data.type === `${type}_failed`) {
36
+ const { name, message: errorMessage } = message.data;
37
+ const err = new Error(errorMessage);
38
+ err.name = name;
39
+ reject(err);
40
+ }
41
+ });
42
+
43
+ if (signal) {
44
+ signal.onabort = () => reject({ type: 'abort' });
45
+ }
46
+
47
+ if (signal?.aborted) {
48
+ reject({ type: 'abort' });
49
+ }
50
+
51
+ stainlessWorker.postMessage({ ...data, type });
52
+ }).finally(() => {
53
+ stainlessWorker.terminate();
54
+ });
55
+ }
56
+
57
+ export async function parseInputs({ oas, config }: { oas: string; config: string }) {
58
+ const result = await runJob({
59
+ type: 'parse',
60
+ data: {
61
+ oas,
62
+ config,
63
+ },
64
+ });
65
+
66
+ return result as {
67
+ oas: OpenAPIDocument;
68
+ config: ParsedConfig;
69
+ };
70
+ }
71
+
72
+ export async function transformOAS({ oas, config }: { oas: OpenAPIDocument; config: ParsedConfig }) {
73
+ const result = await runJob({
74
+ type: 'transform',
75
+ data: {
76
+ oas,
77
+ config,
78
+ },
79
+ });
80
+
81
+ return result.transformedOAS;
82
+ }
83
+
84
+ export async function createSDKJSON({
85
+ oas,
86
+ config,
87
+ languages,
88
+ }: {
89
+ oas: OpenAPIDocument;
90
+ config: ParsedConfig;
91
+ languages: DocsLanguage[];
92
+ }) {
93
+ const templatePath = resolve(__dirname, '../vendor/templates');
94
+ const readmeLoader = await Promise.all(
95
+ Languages.map(async (language) => {
96
+ const mdfile = pathutils.join(templatePath, `${language}.md`);
97
+
98
+ try {
99
+ const content = await fs.readFile(mdfile);
100
+ return [language, content.toString()];
101
+ } catch (err) {
102
+ return [language, null];
103
+ }
104
+ }),
105
+ );
106
+
107
+ const readmeTemplates = Object.fromEntries(readmeLoader);
108
+
109
+ const result = await runJob({
110
+ type: 'preview',
111
+ data: {
112
+ oas,
113
+ config,
114
+ languages,
115
+ transform: false,
116
+ projectName: '',
117
+ readmeTemplates,
118
+ },
119
+ });
120
+
121
+ return result.spec as SDKJSON.Spec;
122
+ }
@@ -0,0 +1,154 @@
1
+ ---
2
+ import type { DocsLanguage } from '@stainless-api/docs-ui/src/routing';
3
+ import { parseRoute } from '@stainless-api/docs-ui/src/routing';
4
+ import { cmsClient } from '../cms/client';
5
+ import { BASE_PATH, DEFAULT_LANGUAGE, EXCLUDE_LANGUAGES } from 'virtual:stl-starlight-virtual-module';
6
+ import { Languages } from '../languages';
7
+ import { SDKSelectReactComponent } from '../react/Routing';
8
+
9
+ const slug = `/${Astro.locals.starlightRoute.id}`;
10
+
11
+ const basePath = BASE_PATH;
12
+ const defaultLanguage = DEFAULT_LANGUAGE;
13
+ const { language, stainlessPath } = parseRoute(basePath, slug);
14
+
15
+ const data = {
16
+ language,
17
+ basePath,
18
+ stainlessPath,
19
+ defaultLanguage,
20
+ };
21
+
22
+ const spec = await cmsClient.getSpec();
23
+
24
+ // TODO: should not force unwrap this
25
+ const languages: DocsLanguage[] = spec.docs!.languages ?? ['http'];
26
+ const options = languages
27
+ .filter((language) => language !== 'terraform')
28
+ .filter((language) => !EXCLUDE_LANGUAGES.includes(language))
29
+ .map((value) => ({
30
+ value,
31
+ label: Languages[value].name,
32
+ selected: data.language === value,
33
+ }));
34
+
35
+ const readmeSlug = language === 'http' ? BASE_PATH : `${BASE_PATH}/${language}`;
36
+ ---
37
+
38
+ <span
39
+ hidden
40
+ id="stldocs-data"
41
+ data-stldocs-basepath={data.basePath}
42
+ data-stldocs-defaultLanguage={data.defaultLanguage}></span>
43
+
44
+ {
45
+ (data.stainlessPath || slug === readmeSlug) && (
46
+ <div class="stldocs-root stl-sdk-select">
47
+ <SDKSelectReactComponent
48
+ selected={data.language}
49
+ languages={options.map(({ value }) => value)}
50
+ id="sidebar-sdk-select"
51
+ />
52
+ </div>
53
+ )
54
+ }
55
+
56
+ <style>
57
+ @layer starlight.core {
58
+ label {
59
+ --sl-label-icon-size: 16px;
60
+ --sl-caret-size: 1.25rem;
61
+ --sl-inline-padding: 0.5rem;
62
+ position: relative;
63
+ display: flex;
64
+ align-items: center;
65
+ gap: 0.25rem;
66
+ color: var(--sl-color-gray-1);
67
+ }
68
+
69
+ label:hover {
70
+ color: var(--sl-color-gray-2);
71
+ }
72
+
73
+ .icon {
74
+ position: absolute;
75
+ top: 50%;
76
+ transform: translateY(-50%);
77
+ pointer-events: none;
78
+ width: 16px;
79
+ }
80
+
81
+ select {
82
+ padding-block: 0.3rem;
83
+ padding-inline: calc(var(--sl-label-icon-size) + var(--sl-inline-padding) + 0.5rem)
84
+ calc(var(--sl-caret-size) + var(--sl-inline-padding) + 0.25rem);
85
+ margin-inline: calc(var(--sl-inline-padding) * -1);
86
+ width: calc(var(--sl-select-width) + var(--sl-inline-padding) * 2);
87
+ text-overflow: ellipsis;
88
+ color: inherit;
89
+ cursor: pointer;
90
+ appearance: none;
91
+ font-weight: 600;
92
+ text-transform: capitalize;
93
+ }
94
+
95
+ select:active {
96
+ font-weight: inherit;
97
+ /* font-family: sans-serif;
98
+ font-weight: 400; */
99
+ }
100
+
101
+ option {
102
+ background-color: var(--sl-color-bg-nav);
103
+ color: var(--sl-color-gray-1);
104
+ }
105
+
106
+ @media (min-width: 50rem) {
107
+ select {
108
+ font-size: var(--sl-text-sm);
109
+ }
110
+ }
111
+ }
112
+
113
+ @layer starlight.components {
114
+ .label-icon {
115
+ font-size: var(--sl-label-icon-size);
116
+ inset-inline-start: 0;
117
+ }
118
+
119
+ .caret {
120
+ font-size: var(--sl-caret-size);
121
+ inset-inline-end: 0;
122
+ }
123
+ }
124
+
125
+ .custom-select-wrapper {
126
+ --sl-inline-padding: 0.5rem;
127
+ position: relative;
128
+ display: inline-block;
129
+ /* These match the padding on the sidebar menu */
130
+ padding-left: var(--sl-inline-padding);
131
+ padding-right: var(--sl-inline-padding);
132
+
133
+ .icon.http path {
134
+ fill: var(--sl-color-text);
135
+ }
136
+ }
137
+ </style>
138
+ <script>
139
+ import { navigate } from 'astro:transitions/client';
140
+ import { updateSelectedLanguage } from '../languages';
141
+ import { initDropdown } from '@stainless-api/docs-ui/src/components/scripts/dropdown';
142
+ import { BASE_PATH } from 'virtual:stl-starlight-virtual-module';
143
+ import { getPageLoadEvent } from '../helpers/getPageLoadEvent';
144
+
145
+ document.addEventListener(getPageLoadEvent(), () => {
146
+ initDropdown({
147
+ dropdownId: 'sidebar-sdk-select',
148
+ onSelect: (value) => {
149
+ const originalLanguage = document.getElementById('sidebar-sdk-select')?.dataset.currentValue;
150
+ navigate(updateSelectedLanguage(BASE_PATH, originalLanguage, value));
151
+ },
152
+ });
153
+ });
154
+ </script>