@stainless-api/docs 0.1.0-beta.98 → 1.0.0-beta.140
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.
- package/CHANGELOG.md +404 -0
- package/ambient.d.ts +6 -0
- package/eslint-suppressions.json +22 -6
- package/{eslint.config.js → eslint.config.ts} +1 -7
- package/package.json +57 -40
- package/plugin/assets/languages/php.svg +4 -0
- package/plugin/buildAlgoliaIndex.ts +6 -12
- package/plugin/components/SDKSelect.astro +0 -6
- package/plugin/components/SnippetCode.tsx +6 -37
- package/plugin/components/search/SearchAlgolia.astro +1 -1
- package/plugin/components/search/SearchIsland.tsx +19 -13
- package/plugin/generateAPIReferenceLink.ts +0 -40
- package/plugin/globalJs/ai-dropdown-options.ts +26 -13
- package/plugin/globalJs/code-snippets.ts +5 -5
- package/plugin/globalJs/copy.ts +20 -91
- package/plugin/globalJs/navigation.ts +13 -13
- package/plugin/globalJs/summary-selection-tweak.ts +29 -0
- package/plugin/index.ts +107 -163
- package/plugin/languages.ts +2 -1
- package/plugin/loadPluginConfig.ts +50 -153
- package/plugin/markdown/highlighter.ts +100 -0
- package/plugin/markdown/index.ts +39 -0
- package/plugin/middlewareBuilder/stainlessMiddleware.d.ts +2 -0
- package/plugin/react/Routing.tsx +10 -244
- package/plugin/referencePlaceholderUtils.ts +1 -1
- package/plugin/replaceSidebarPlaceholderMiddleware.ts +1 -1
- package/plugin/routes/Docs.astro +3 -1
- package/plugin/routes/Overview.astro +14 -7
- package/plugin/routes/llms.ts +186 -0
- package/plugin/routes/markdown.ts +62 -13
- package/plugin/sidebar-utils/sidebar-builder.ts +38 -12
- package/plugin/specs/defaultSpecLoader.ts +192 -0
- package/plugin/specs/fetchSpecSSR.ts +1 -1
- package/plugin/specs/utils.ts +86 -0
- package/shared/conditionalIntegration.ts +28 -0
- package/shared/getProsePages.ts +6 -7
- package/shared/virtualModule.ts +1 -26
- package/stl-docs/aiChatExamples.ts +31 -0
- package/stl-docs/chat/docs-chat-handler.ts +17 -0
- package/stl-docs/chat/hook.ts +225 -0
- package/stl-docs/chat/schemas.ts +27 -0
- package/stl-docs/chat/ui/AiChat.module.css +591 -0
- package/stl-docs/chat/ui/AiChat.tsx +175 -0
- package/stl-docs/chat/ui/Trigger.tsx +154 -0
- package/stl-docs/chat/ui/components/ChatControls.tsx +51 -0
- package/stl-docs/chat/ui/components/ChatEmpty.tsx +42 -0
- package/stl-docs/chat/ui/components/ChatLog.tsx +93 -0
- package/stl-docs/chat/ui/components/ChatMessage.tsx +47 -0
- package/stl-docs/chat/ui/components/CodeBlock.tsx +33 -0
- package/stl-docs/chat/ui/components/MessageFeedback.tsx +106 -0
- package/stl-docs/chat/ui/components/Table.tsx +15 -0
- package/stl-docs/chat/ui/components/ToolCall.tsx +34 -0
- package/stl-docs/chat/ui/components/hljs-github.css +81 -0
- package/stl-docs/chat/ui/scroll-manager.ts +86 -0
- package/stl-docs/chat/ui/types.ts +45 -0
- package/stl-docs/components/AiChatIsland.tsx +10 -12
- package/stl-docs/components/ContentPanel.astro +9 -0
- package/stl-docs/components/Footer.astro +89 -0
- package/stl-docs/components/Header.astro +0 -5
- package/stl-docs/components/PageFrame.astro +23 -8
- package/stl-docs/components/PageSidebar.astro +11 -0
- package/stl-docs/components/StainlessLogo.svg +4 -0
- package/stl-docs/components/TwoColumnContent.astro +2 -0
- package/stl-docs/components/headers/DefaultHeader.astro +6 -8
- package/stl-docs/components/headers/StackedHeader.astro +5 -53
- package/stl-docs/components/mintlify-compat/Accordion.astro +2 -2
- package/stl-docs/components/mintlify-compat/AccordionGroup.astro +0 -4
- package/stl-docs/components/mintlify-compat/Columns.astro +2 -2
- package/stl-docs/components/mintlify-compat/Frame.astro +2 -2
- package/stl-docs/components/mintlify-compat/Tab.astro +2 -2
- package/stl-docs/components/mintlify-compat/callouts/Callout.astro +2 -2
- package/stl-docs/components/mintlify-compat/callouts/Check.astro +0 -4
- package/stl-docs/components/mintlify-compat/callouts/Danger.astro +0 -4
- package/stl-docs/components/mintlify-compat/callouts/Info.astro +0 -4
- package/stl-docs/components/mintlify-compat/callouts/Note.astro +0 -4
- package/stl-docs/components/mintlify-compat/callouts/Tip.astro +0 -4
- package/stl-docs/components/mintlify-compat/callouts/Warning.astro +0 -4
- package/stl-docs/components/nav-tabs/NavDropdown.astro +12 -7
- package/stl-docs/components/nav-tabs/NavTabs.astro +5 -3
- package/stl-docs/components/nav-tabs/buildNavLinks.ts +2 -0
- package/stl-docs/components/pagination/Pagination.astro +4 -2
- package/stl-docs/components/pagination/PaginationLinkEmphasized.astro +2 -2
- package/stl-docs/components/pagination/PaginationLinkQuiet.astro +2 -2
- package/stl-docs/components/pagination/util.ts +3 -3
- package/stl-docs/components/sidebars/BaseSidebar.astro +72 -1
- package/stl-docs/disableCalloutSyntax.ts +1 -1
- package/stl-docs/fonts.ts +5 -5
- package/stl-docs/index.ts +76 -53
- package/stl-docs/loadStlDocsConfig.ts +38 -8
- package/stl-docs/og-image/components/OpenGraphFunctionSignature.tsx +64 -0
- package/stl-docs/og-image/components/OpenGraphImage.tsx +126 -0
- package/stl-docs/og-image/config.ts +56 -0
- package/stl-docs/og-image/image-gen/generate-api-reference-og-image.tsx +188 -0
- package/stl-docs/og-image/image-gen/generate-og-image.tsx +119 -0
- package/stl-docs/og-image/image-gen/get-logo-url.ts +47 -0
- package/stl-docs/og-image/index.ts +135 -0
- package/stl-docs/og-image/routes/add-og-image.ts +45 -0
- package/stl-docs/og-image/routes/get-api-reference-og-image.ts +36 -0
- package/stl-docs/og-image/routes/get-og-image.ts +28 -0
- package/stl-docs/og-image/theme.ts +43 -0
- package/stl-docs/og-image/utils.ts +14 -0
- package/stl-docs/proseDocSync.test.ts +74 -0
- package/stl-docs/proseDocSync.ts +344 -0
- package/stl-docs/proseMarkdown/proseMarkdownIntegration.ts +4 -12
- package/stl-docs/schema-extension.ts +12 -0
- package/stl-docs/tabsMiddleware.ts +1 -1
- package/styles/overrides.css +2 -14
- package/styles/page.css +210 -71
- package/styles/sidebar.css +30 -17
- package/styles/sl-variables.css +3 -8
- package/styles/stldocs-variables.css +2 -2
- package/styles/toc.css +8 -0
- package/tsconfig.json +1 -1
- package/virtual-module.d.ts +35 -11
- package/playground-virtual-modules.d.ts +0 -96
- package/plugin/globalJs/create-playground.shim.ts +0 -3
- package/plugin/globalJs/playground-data.shim.ts +0 -1
- package/plugin/globalJs/playground-data.ts +0 -14
- package/plugin/specs/FileCache.ts +0 -99
- package/plugin/specs/generateSpec.ts +0 -112
- package/plugin/specs/index.ts +0 -132
- package/plugin/specs/inputResolver.ts +0 -146
- package/plugin/specs/worker.ts +0 -199
- package/plugin/vendor/preview.worker.docs.js +0 -26108
- package/plugin/vendor/templates/cli.md +0 -1
- package/plugin/vendor/templates/go.md +0 -316
- package/plugin/vendor/templates/java.md +0 -89
- package/plugin/vendor/templates/kotlin.md +0 -89
- package/plugin/vendor/templates/node.md +0 -235
- package/plugin/vendor/templates/python.md +0 -251
- package/plugin/vendor/templates/ruby.md +0 -147
- package/plugin/vendor/templates/terraform.md +0 -60
- package/plugin/vendor/templates/typescript.md +0 -319
- package/scripts/vendor_deps.ts +0 -50
- package/stl-docs/components/ClientRouterHead.astro +0 -41
- package/stl-docs/components/content-panel/ContentPanel.astro +0 -42
- package/stl-docs/components/headers/SplashMobileMenuToggle.astro +0 -65
- package/stl-docs/proseSearchIndexing.ts +0 -606
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
import type { APIRoute, GetStaticPaths } from 'astro';
|
|
2
|
+
import type { Node } from '@markdoc/markdoc';
|
|
2
3
|
import { getReadmeContent } from '../react/Routing';
|
|
3
|
-
import { getResourceFromSpec } from '@stainless-api/docs-ui/utils';
|
|
4
|
+
import { getResourceFromSpec, isResourceEmpty } from '@stainless-api/docs-ui/utils';
|
|
4
5
|
|
|
5
6
|
import { renderMarkdown } from '@stainless-api/docs-ui/markdown';
|
|
7
|
+
import * as md from '@stainless-api/docs-ui/markdown/md';
|
|
6
8
|
|
|
7
|
-
import { parseStainlessPath, type DocsLanguage } from '@stainless-api/docs-ui/routing';
|
|
9
|
+
import { parseStainlessPath, generateRoute, type DocsLanguage } from '@stainless-api/docs-ui/routing';
|
|
8
10
|
import type { EnvironmentType } from '@stainless-api/docs-ui/markdown/utils';
|
|
9
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
PROPERTY_SETTINGS,
|
|
13
|
+
MIDDLEWARE,
|
|
14
|
+
RESOLVED_API_REFERENCE_PATH,
|
|
15
|
+
} from 'virtual:stl-starlight-virtual-module';
|
|
10
16
|
import { generateAllDocsRoutes } from '../helpers/generateDocsRoutes';
|
|
11
17
|
import { getSDKJSONInSSR } from '../specs/fetchSpecSSR';
|
|
18
|
+
import Markdoc from '@markdoc/markdoc';
|
|
19
|
+
import type * as SDKJSON from '@stainless/sdk-json';
|
|
20
|
+
import { API_REFERENCE_BASE_PATH } from 'virtual:stl-docs-virtual-module';
|
|
12
21
|
|
|
13
22
|
type RouteProps = {
|
|
14
23
|
stainlessPath: string;
|
|
@@ -21,20 +30,41 @@ export const getStaticPaths = (async () => {
|
|
|
21
30
|
return paths;
|
|
22
31
|
}) satisfies GetStaticPaths;
|
|
23
32
|
|
|
24
|
-
|
|
25
|
-
const
|
|
33
|
+
function renderLink(stainlessPath: string, title: string) {
|
|
34
|
+
const href = generateRoute(RESOLVED_API_REFERENCE_PATH, 'http', stainlessPath);
|
|
35
|
+
return href ? md.link(`${href}/index.md`, title) : md.text(title);
|
|
36
|
+
}
|
|
26
37
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
38
|
+
function renderOverviewResource(resource: SDKJSON.Resource, topLevel: boolean): Node[] {
|
|
39
|
+
const link = renderLink(resource.stainlessPath, resource.title);
|
|
40
|
+
|
|
41
|
+
const methodItems = Object.values(resource.methods).map((method) =>
|
|
42
|
+
md.item(md.paragraph(renderLink(method.stainlessPath, method.title))),
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const subItems = Object.values(resource.subresources ?? {})
|
|
46
|
+
.filter((sub) => !isResourceEmpty(sub))
|
|
47
|
+
.flatMap((sub) => renderOverviewResource(sub, false));
|
|
48
|
+
|
|
49
|
+
const nestedItems = [...methodItems, ...subItems];
|
|
50
|
+
const list = nestedItems.length > 0 ? [md.list(...nestedItems)] : [];
|
|
51
|
+
|
|
52
|
+
return topLevel ? [md.heading(2, [link]), ...list] : [md.item(md.paragraph(link), ...list)];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function renderOverview({ spec }: EnvironmentType): string {
|
|
56
|
+
const output: Node[] = [md.heading(1, 'API Reference')];
|
|
57
|
+
|
|
58
|
+
for (const resource of Object.values(spec.resources)) {
|
|
59
|
+
if (!isResourceEmpty(resource)) output.push(...renderOverviewResource(resource, true));
|
|
32
60
|
}
|
|
33
61
|
|
|
34
|
-
const
|
|
35
|
-
|
|
62
|
+
const doc = new Markdoc.Ast.Node('document', {}, output);
|
|
63
|
+
return Markdoc.format(doc);
|
|
64
|
+
}
|
|
36
65
|
|
|
37
|
-
|
|
66
|
+
export const GET: APIRoute<RouteProps> = async ({ props, routePattern }) => {
|
|
67
|
+
const spec = await getSDKJSONInSSR(props.language ?? 'http');
|
|
38
68
|
|
|
39
69
|
const env: EnvironmentType = {
|
|
40
70
|
spec,
|
|
@@ -48,6 +78,25 @@ export const GET: APIRoute<RouteProps> = async ({ props }) => {
|
|
|
48
78
|
},
|
|
49
79
|
};
|
|
50
80
|
|
|
81
|
+
if (routePattern === `${API_REFERENCE_BASE_PATH}/index.md`) {
|
|
82
|
+
const content = renderOverview({ ...env, language: 'http' });
|
|
83
|
+
return new Response(content, {
|
|
84
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (props.kind === 'readme') {
|
|
89
|
+
const readmeContent = await getReadmeContent(spec, props.language);
|
|
90
|
+
return new Response(readmeContent, {
|
|
91
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const parsed = parseStainlessPath(props.stainlessPath);
|
|
96
|
+
const resource = getResourceFromSpec(props.stainlessPath, spec);
|
|
97
|
+
|
|
98
|
+
if (!resource) throw new Error('Invalid route');
|
|
99
|
+
|
|
51
100
|
const target = props.kind === 'http_method' && parsed?.method ? resource.methods[parsed.method]! : resource;
|
|
52
101
|
const output = renderMarkdown(env, target);
|
|
53
102
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type * as SDKJSON from '@stainless/sdk-json';
|
|
2
2
|
import { generateRoute, walkTree, type DocsLanguage } from '@stainless-api/docs-ui/routing';
|
|
3
3
|
import type { StarlightRouteData } from '@astrojs/starlight/route-data';
|
|
4
|
+
import { isResourceEntirelyUnsupported } from '@stainless-api/docs-ui/languages/terraform';
|
|
4
5
|
|
|
5
6
|
function makeMethodOrResourceKey(entry: SDKJSON.Method | SDKJSON.Resource): string {
|
|
6
7
|
if (entry.kind === 'http_method') {
|
|
@@ -172,8 +173,8 @@ export class SidebarConfigItemsBuilder {
|
|
|
172
173
|
const decls = this.spec.decls[this.language] ?? {};
|
|
173
174
|
const decl = decls[entry.stainlessPath];
|
|
174
175
|
if (decl !== undefined) {
|
|
175
|
-
if ('ident' in decl) {
|
|
176
|
-
return decl;
|
|
176
|
+
if ('ident' in decl && decl.ident !== undefined) {
|
|
177
|
+
return decl as MethodDecl;
|
|
177
178
|
}
|
|
178
179
|
}
|
|
179
180
|
return null;
|
|
@@ -249,13 +250,43 @@ export class SidebarConfigItemsBuilder {
|
|
|
249
250
|
};
|
|
250
251
|
}
|
|
251
252
|
|
|
253
|
+
private generateTerraformItems(resources: SDKJSON.Resource[]) {
|
|
254
|
+
const entries: ReferenceSidebarConfigItem[] = [];
|
|
255
|
+
|
|
256
|
+
for (const resource of resources) {
|
|
257
|
+
if (isResourceEntirelyUnsupported(resource, this.spec.decls['terraform'])) {
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
entries.push({
|
|
261
|
+
kind: 'resource_overview_page',
|
|
262
|
+
label: resource.title,
|
|
263
|
+
key: makeMethodOrResourceKey(resource),
|
|
264
|
+
badge: undefined,
|
|
265
|
+
metadata: {
|
|
266
|
+
subResourceCount: countKeys(resource.subresources),
|
|
267
|
+
methodCount: countKeys(resource.methods),
|
|
268
|
+
modelCount: countKeys(resource.models),
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
return entries;
|
|
273
|
+
}
|
|
274
|
+
|
|
252
275
|
public generateItems(): ReferenceSidebarConfigItem[] {
|
|
253
276
|
const resourceMap = this.spec.resources;
|
|
254
277
|
const { resources, sharedModelsResource } = pullOutSharedModelsResource(Object.values(resourceMap ?? {}));
|
|
255
278
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
279
|
+
let entries: ReferenceSidebarConfigItem[];
|
|
280
|
+
|
|
281
|
+
if (this.language === 'terraform') {
|
|
282
|
+
// Handle Terraform specifically
|
|
283
|
+
// In TF, we only render the top level resource, not the subresources.
|
|
284
|
+
entries = this.generateTerraformItems(resources);
|
|
285
|
+
} else {
|
|
286
|
+
entries = resources.filter(isResourceNonEmpty).map((r) => {
|
|
287
|
+
return this.generateResourceGroup(r, false);
|
|
288
|
+
});
|
|
289
|
+
}
|
|
259
290
|
|
|
260
291
|
const includeSharedModels = this.options?.includeSharedModels ?? false;
|
|
261
292
|
if (includeSharedModels && sharedModelsResource) {
|
|
@@ -307,11 +338,6 @@ function forceGenerateRoute({
|
|
|
307
338
|
return route;
|
|
308
339
|
}
|
|
309
340
|
|
|
310
|
-
export type BuildSidebarParams = {
|
|
311
|
-
basePath: string;
|
|
312
|
-
currentSlug: string;
|
|
313
|
-
};
|
|
314
|
-
|
|
315
341
|
type ToStarlightSidebarParams = {
|
|
316
342
|
basePath: string;
|
|
317
343
|
spec: SDKJSON.Spec;
|
|
@@ -381,10 +407,10 @@ export function toStarlightSidebar({
|
|
|
381
407
|
throw new Error(`Unknown entry kind ${JSON.stringify(entry)}`);
|
|
382
408
|
}
|
|
383
409
|
} else if (entry.kind === 'group') {
|
|
384
|
-
// Skip pushing the group if if the resource it represents is not available in the current language.
|
|
385
|
-
// This occurs when SDK generation for the current language is skipped in the Stainless config for that resource.
|
|
386
410
|
if (entry.resourceGroupKey) {
|
|
387
411
|
const resourceOrMethod = getResourceOrMethod(spec, entry.resourceGroupKey);
|
|
412
|
+
// Skip pushing the group if if the resource it represents is not available in the current language.
|
|
413
|
+
// This occurs when SDK generation for the current language is skipped in the Stainless config for that resource.
|
|
388
414
|
if (resourceOrMethod?.data?.kind === 'resource' && !resourceOrMethod?.data?.[currentLanguage]) {
|
|
389
415
|
continue;
|
|
390
416
|
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import type { SpecLoaderFn, SpecLoaderParams } from './utils';
|
|
3
|
+
import Stainless, { APIError } from '@stainless-api/sdk';
|
|
4
|
+
import { DocsLanguage } from '@stainless-api/docs-ui/routing';
|
|
5
|
+
import { bold } from '../../shared/terminalUtils';
|
|
6
|
+
import { mkdir, readdir, readFile, rm, writeFile } from 'fs/promises';
|
|
7
|
+
import { generateSpecFromStrings, previewWorkerCode } from '@stainless/sdk-json/spec';
|
|
8
|
+
import { Spec, SpecLanguage } from '@stainless/sdk-json';
|
|
9
|
+
import crypto from 'crypto';
|
|
10
|
+
|
|
11
|
+
function resolvePath(inputPath: string) {
|
|
12
|
+
return path.resolve(process.cwd(), inputPath);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getLocalFilePaths() {
|
|
16
|
+
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
17
|
+
const oasPath = process.env.OPENAPI_PATH;
|
|
18
|
+
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
19
|
+
const configPath = process.env.STAINLESS_CONFIG_PATH;
|
|
20
|
+
|
|
21
|
+
if (!oasPath || !configPath) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
oasPath: resolvePath(oasPath),
|
|
27
|
+
configPath: resolvePath(configPath),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function fetchVersionInfo(project: string, apiKey: string): Promise<Record<DocsLanguage, string>> {
|
|
32
|
+
const data = await fetch(`https://api.stainless.com/api/projects/${project}/package-versions`, {
|
|
33
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const content = await data.text();
|
|
37
|
+
return JSON.parse(content) as Record<DocsLanguage, string>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function redactApiKey(apiKey: string) {
|
|
41
|
+
return apiKey
|
|
42
|
+
.split('')
|
|
43
|
+
.map((char, index) => (index < 10 ? char : '*'))
|
|
44
|
+
.join('');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function loadInputs({ apiKey, logger, stainlessProject, branch }: SpecLoaderParams) {
|
|
48
|
+
const localFilePaths = getLocalFilePaths();
|
|
49
|
+
|
|
50
|
+
if (localFilePaths) {
|
|
51
|
+
try {
|
|
52
|
+
const oasStr = await readFile(localFilePaths.oasPath, 'utf8');
|
|
53
|
+
const configStr = await readFile(localFilePaths.configPath, 'utf8');
|
|
54
|
+
return {
|
|
55
|
+
oasStr,
|
|
56
|
+
configStr,
|
|
57
|
+
versionInfo: null,
|
|
58
|
+
};
|
|
59
|
+
} catch (e) {
|
|
60
|
+
logger.error(bold('Failed to load spec inputs from files:'));
|
|
61
|
+
logger.error(e instanceof Error ? e.message : String(e));
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!apiKey) {
|
|
67
|
+
logger.error(
|
|
68
|
+
[
|
|
69
|
+
bold(
|
|
70
|
+
'No Stainless credentials found. Please choose one of the following options to authenticate with Stainless:',
|
|
71
|
+
),
|
|
72
|
+
'- Run `stl auth login` to authenticate via the Stainless CLI',
|
|
73
|
+
'- Provide a Stainless API key via the `STAINLESS_API_KEY` environment variable (eg. in a .env file)',
|
|
74
|
+
'- Set the `apiKey` option in the Stainless Docs config',
|
|
75
|
+
].join('\n'),
|
|
76
|
+
);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
const client = new Stainless({ apiKey });
|
|
82
|
+
const configs = await client.projects.configs.retrieve({
|
|
83
|
+
project: stainlessProject,
|
|
84
|
+
branch: branch,
|
|
85
|
+
include: 'openapi',
|
|
86
|
+
});
|
|
87
|
+
const versionInfo = await fetchVersionInfo(stainlessProject, apiKey);
|
|
88
|
+
|
|
89
|
+
const configYML = Object.values(configs)[0] as { content: unknown };
|
|
90
|
+
const oasJson = Object.values(configs)[1] as { content: unknown };
|
|
91
|
+
const oasStr = oasJson['content'];
|
|
92
|
+
const configStr = configYML['content'];
|
|
93
|
+
|
|
94
|
+
if (typeof oasStr !== 'string' || typeof configStr !== 'string') {
|
|
95
|
+
logger.error('Received invalid OAS or config from Stainless');
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
oasStr,
|
|
101
|
+
configStr,
|
|
102
|
+
versionInfo,
|
|
103
|
+
};
|
|
104
|
+
} catch (e) {
|
|
105
|
+
if (e instanceof APIError && e.status >= 400 && e.status < 500) {
|
|
106
|
+
logger.error(`Failed to load requested project slug: "${stainlessProject}"`);
|
|
107
|
+
if (apiKey) {
|
|
108
|
+
logger.error(`API key: "${redactApiKey(apiKey)}"`);
|
|
109
|
+
}
|
|
110
|
+
logger.error(
|
|
111
|
+
`This error can usually be corrected by re-authenticating with the Stainless. Use the CLI (stl auth login) or verify that the Stainless API key you're using can access the project mentioned above.`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function maybeLoadJSONFile<T>(filePath: string): Promise<T | null> {
|
|
119
|
+
try {
|
|
120
|
+
const fileContents = await readFile(filePath, 'utf8');
|
|
121
|
+
return JSON.parse(fileContents) as T;
|
|
122
|
+
} catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function cleanupDirectory(directory: string, filesToKeep: string[]) {
|
|
128
|
+
const allFiles = await readdir(directory);
|
|
129
|
+
const unusedFiles = allFiles.filter((file) => !filesToKeep.includes(file));
|
|
130
|
+
await Promise.all(unusedFiles.map((file) => rm(path.join(directory, file))));
|
|
131
|
+
return {
|
|
132
|
+
deletedCount: unusedFiles.length,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export const defaultSpecLoader: SpecLoaderFn = async (params) => {
|
|
137
|
+
const { createCodegenDir } = params;
|
|
138
|
+
|
|
139
|
+
const inputs = await loadInputs(params);
|
|
140
|
+
|
|
141
|
+
const specsDirectory = path.join(createCodegenDir().pathname, 'specs2');
|
|
142
|
+
await mkdir(specsDirectory, { recursive: true });
|
|
143
|
+
|
|
144
|
+
const fileName =
|
|
145
|
+
crypto
|
|
146
|
+
.createHash('sha256')
|
|
147
|
+
.update(JSON.stringify(inputs) + previewWorkerCode)
|
|
148
|
+
.digest('hex')
|
|
149
|
+
.slice(0, 10) + '.json';
|
|
150
|
+
|
|
151
|
+
const filePath = path.join(specsDirectory, fileName);
|
|
152
|
+
|
|
153
|
+
const cachedSpec = await maybeLoadJSONFile<{ languages: SpecLanguage[]; sdkJson: Spec }>(filePath);
|
|
154
|
+
// skip generation since we already have a cached spec
|
|
155
|
+
if (cachedSpec) {
|
|
156
|
+
params.logger.info(`Loaded cached spec: ${fileName}`);
|
|
157
|
+
return [
|
|
158
|
+
{
|
|
159
|
+
filePath,
|
|
160
|
+
languages: cachedSpec.languages,
|
|
161
|
+
sdkJson: cachedSpec.sdkJson,
|
|
162
|
+
},
|
|
163
|
+
];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const result = await generateSpecFromStrings({
|
|
167
|
+
oasStr: inputs.oasStr,
|
|
168
|
+
configStr: inputs.configStr,
|
|
169
|
+
languageOverrides: {
|
|
170
|
+
mode: 'exclude',
|
|
171
|
+
list: params.excludeLanguages ?? [],
|
|
172
|
+
},
|
|
173
|
+
versionInfo: inputs.versionInfo,
|
|
174
|
+
stainlessProject: params.stainlessProject,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
await writeFile(filePath, JSON.stringify(result), 'utf8');
|
|
178
|
+
params.logger.info(`Generated: ${fileName}`);
|
|
179
|
+
|
|
180
|
+
const { deletedCount } = await cleanupDirectory(specsDirectory, [fileName]);
|
|
181
|
+
if (deletedCount > 0) {
|
|
182
|
+
params.logger.info(`Cleaned up ${deletedCount} unused spec file(s)`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return [
|
|
186
|
+
{
|
|
187
|
+
filePath,
|
|
188
|
+
languages: result.languages.filter((language) => language !== 'sql' && language !== 'openapi'),
|
|
189
|
+
sdkJson: result.sdkJson,
|
|
190
|
+
},
|
|
191
|
+
];
|
|
192
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFile } from 'fs/promises';
|
|
2
2
|
|
|
3
3
|
import { api } from 'virtual:stainless-apis-manifest';
|
|
4
|
-
import { SpecWithAuth } from '
|
|
4
|
+
import type { SpecWithAuth } from '@stainless/sdk-json/spec';
|
|
5
5
|
import { DocsLanguage } from '@stainless-api/docs-ui/routing';
|
|
6
6
|
|
|
7
7
|
const cachedSpecWithAuth: Record<string, SpecWithAuth> = {};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Spec } from '@stainless/sdk-json';
|
|
2
|
+
import { readFile } from 'fs/promises';
|
|
3
|
+
import { DocsLanguage } from '@stainless-api/docs-ui/routing';
|
|
4
|
+
import { AstroIntegrationLogger } from 'astro';
|
|
5
|
+
|
|
6
|
+
type PossibleLanguage = NonNullable<NonNullable<NonNullable<Spec['docs']>['languages']>[number]>;
|
|
7
|
+
|
|
8
|
+
export type SpecLoaderParams = {
|
|
9
|
+
/**
|
|
10
|
+
* The slug of your Stainless project.
|
|
11
|
+
*/
|
|
12
|
+
stainlessProject: string;
|
|
13
|
+
/**
|
|
14
|
+
* The branch of your Stainless project.
|
|
15
|
+
*/
|
|
16
|
+
branch: string;
|
|
17
|
+
/**
|
|
18
|
+
* The Stainless API key. This can be used to make requests against the Stainless API.
|
|
19
|
+
*/
|
|
20
|
+
apiKey: string | null;
|
|
21
|
+
/**
|
|
22
|
+
* The languages that the user has explicitly asked to be excluded from the API reference.
|
|
23
|
+
*/
|
|
24
|
+
excludeLanguages: DocsLanguage[] | null;
|
|
25
|
+
/**
|
|
26
|
+
* An Astro logger. This should be used for logging messages.
|
|
27
|
+
*/
|
|
28
|
+
logger: AstroIntegrationLogger;
|
|
29
|
+
/**
|
|
30
|
+
* A function that creates a directory in .astro. See: https://docs.astro.build/en/reference/integrations-reference/#createcodegendir
|
|
31
|
+
*/
|
|
32
|
+
createCodegenDir: () => URL;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type SpecLoaderResult = {
|
|
36
|
+
/**
|
|
37
|
+
* The file path to the loaded spec. The spec MUST be written to a path on disk.
|
|
38
|
+
* If you are not sure where to place it, create a directory in .astro using the `createCodegenDir` function passed in the parameters of the spec loader function.
|
|
39
|
+
*/
|
|
40
|
+
filePath: string;
|
|
41
|
+
/**
|
|
42
|
+
* The languages that are represented in the spec.
|
|
43
|
+
*/
|
|
44
|
+
languages: PossibleLanguage[];
|
|
45
|
+
/**
|
|
46
|
+
* Optionally, if you already have already the spec in memory, you can provide it. If not provided, the contents of the file at `filePath` will be read.
|
|
47
|
+
* IMPORTANT: This should be equivalent to the contents of the file at `filePath`. If not, bugs and inconsistencies will arise.
|
|
48
|
+
*/
|
|
49
|
+
sdkJson?: Spec;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export type SpecLoaderFn = (opts: SpecLoaderParams) => Promise<SpecLoaderResult[]>;
|
|
53
|
+
|
|
54
|
+
async function readSpecFromFile(filePath: string) {
|
|
55
|
+
const txt = await readFile(filePath, 'utf8');
|
|
56
|
+
const json = JSON.parse(txt) as Spec;
|
|
57
|
+
return json;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function loadAllSpecs(specLoaderResultsPromise: Promise<SpecLoaderResult[]>) {
|
|
61
|
+
const specLoaderResults = await specLoaderResultsPromise;
|
|
62
|
+
const specs = await Promise.all(
|
|
63
|
+
specLoaderResults.map(async (result) => {
|
|
64
|
+
return {
|
|
65
|
+
filePath: result.filePath,
|
|
66
|
+
languages: result.languages,
|
|
67
|
+
sdkJson: result.sdkJson ?? (await readSpecFromFile(result.filePath)),
|
|
68
|
+
};
|
|
69
|
+
}),
|
|
70
|
+
);
|
|
71
|
+
return specs;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export type LoadedSpecs = Awaited<ReturnType<typeof loadAllSpecs>>;
|
|
75
|
+
|
|
76
|
+
export function flatSpecsList(specs: LoadedSpecs) {
|
|
77
|
+
return specs
|
|
78
|
+
.map((s) =>
|
|
79
|
+
s.languages.map((language) => ({
|
|
80
|
+
language,
|
|
81
|
+
sdkJson: s.sdkJson,
|
|
82
|
+
filePath: s.filePath,
|
|
83
|
+
})),
|
|
84
|
+
)
|
|
85
|
+
.flat();
|
|
86
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { AstroIntegration, type AstroIntegrationLogger } from 'astro';
|
|
2
|
+
import { getSharedLogger } from './getSharedLogger';
|
|
3
|
+
|
|
4
|
+
export default function conditionalIntegration({
|
|
5
|
+
condition,
|
|
6
|
+
integration,
|
|
7
|
+
reason,
|
|
8
|
+
}: {
|
|
9
|
+
condition: boolean;
|
|
10
|
+
integration: AstroIntegration;
|
|
11
|
+
reason?: string | undefined;
|
|
12
|
+
}): AstroIntegration {
|
|
13
|
+
if (condition) {
|
|
14
|
+
return integration;
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
name: integration.name,
|
|
18
|
+
hooks: Object.fromEntries(
|
|
19
|
+
Object.keys(integration.hooks).map((hookName) => [
|
|
20
|
+
hookName,
|
|
21
|
+
({ logger: localLogger }: { logger: AstroIntegrationLogger }) => {
|
|
22
|
+
const logger = getSharedLogger({ fallback: localLogger });
|
|
23
|
+
logger.info(`Skipping ${integration.name} integration. Reason: ${reason ?? 'not provided'}`);
|
|
24
|
+
},
|
|
25
|
+
]),
|
|
26
|
+
),
|
|
27
|
+
};
|
|
28
|
+
}
|
package/shared/getProsePages.ts
CHANGED
|
@@ -27,15 +27,14 @@ export async function getProsePages({
|
|
|
27
27
|
.filter((file) => file.isFile() && file.name.endsWith('.html'))
|
|
28
28
|
.map((file) => join(file.parentPath, file.name));
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
if (!apiReferenceBasePath) return htmlFiles;
|
|
31
|
+
// Normalize by removing leading/trailing slashes from apiReferenceBasePath
|
|
32
|
+
const normalizedApiPath = apiReferenceBasePath.replace(/^\/+/g, '').replace(/\/+$/g, '');
|
|
31
33
|
const pagesToRender = htmlFiles.filter((absPath) => {
|
|
32
|
-
if (!apiReferenceBasePath) {
|
|
33
|
-
return true;
|
|
34
|
-
}
|
|
35
34
|
const relPath = relative(outputBasePath, absPath);
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
return
|
|
35
|
+
// Filter out API reference pages
|
|
36
|
+
if (relPath === normalizedApiPath || relPath.startsWith(`${normalizedApiPath}/`)) return false;
|
|
37
|
+
return true;
|
|
39
38
|
});
|
|
40
39
|
|
|
41
40
|
return pagesToRender;
|
package/shared/virtualModule.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ViteUserConfig } from 'astro';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
type VitePlugin = NonNullable<ViteUserConfig['plugins']>[number];
|
|
4
4
|
|
|
5
5
|
export function buildVirtualModuleString<T extends Record<string, unknown>>(vars: T) {
|
|
6
6
|
return Object.entries(vars)
|
|
@@ -10,23 +10,6 @@ export function buildVirtualModuleString<T extends Record<string, unknown>>(vars
|
|
|
10
10
|
.join('\n');
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export function makeVirtualModPlugin(bareId: string, content: string): VitePlugin {
|
|
14
|
-
return {
|
|
15
|
-
name: `stl-virtual-module-loader-${bareId}`,
|
|
16
|
-
resolveId(id) {
|
|
17
|
-
// The '\0' prefix tells Vite “this is a virtual module” and prevents it from being resolved again.
|
|
18
|
-
if (id === bareId) {
|
|
19
|
-
return `\0${bareId}`;
|
|
20
|
-
}
|
|
21
|
-
},
|
|
22
|
-
load(id) {
|
|
23
|
-
if (id === `\0${bareId}`) {
|
|
24
|
-
return content;
|
|
25
|
-
}
|
|
26
|
-
},
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
13
|
export function makeAsyncVirtualModPlugin<T extends Record<string, unknown>>(
|
|
31
14
|
bareId: string,
|
|
32
15
|
contentLoader: () => Promise<T | string>,
|
|
@@ -50,11 +33,3 @@ export function makeAsyncVirtualModPlugin<T extends Record<string, unknown>>(
|
|
|
50
33
|
},
|
|
51
34
|
};
|
|
52
35
|
}
|
|
53
|
-
|
|
54
|
-
export function virtualModule(id: string) {
|
|
55
|
-
return {
|
|
56
|
-
fromString: (content: string) => makeVirtualModPlugin(id, content),
|
|
57
|
-
fromObject: <T extends Record<string, unknown>>(content: T) =>
|
|
58
|
-
makeVirtualModPlugin(id, buildVirtualModuleString(content)),
|
|
59
|
-
};
|
|
60
|
-
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { buildVirtualModuleString } from '../shared/virtualModule';
|
|
2
|
+
import type * as virtualExampleModule from 'virtual:stl-docs-ai-chat-examples';
|
|
3
|
+
type VirtualExampleModule = typeof virtualExampleModule;
|
|
4
|
+
|
|
5
|
+
export type ExamplePromptResponse = {
|
|
6
|
+
shortPrompt: string;
|
|
7
|
+
longPrompt: string;
|
|
8
|
+
icon: string;
|
|
9
|
+
}[];
|
|
10
|
+
|
|
11
|
+
export function generateExamplesVirtualModule(exampleOverrides: ExamplePromptResponse | undefined): string {
|
|
12
|
+
if (!exampleOverrides) {
|
|
13
|
+
return buildVirtualModuleString({ examples: undefined } satisfies VirtualExampleModule);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Generate icon imports
|
|
17
|
+
// prettier-ignore
|
|
18
|
+
const pascalToKebab = (str: string) => str.split(/(?=[A-Z])/).join('-').toLowerCase();
|
|
19
|
+
const iconImportPath = (iconName: string) =>
|
|
20
|
+
import.meta.resolve(`lucide-react/dist/esm/icons/${pascalToKebab(iconName)}.js`);
|
|
21
|
+
const iconImports = exampleOverrides.map(
|
|
22
|
+
({ icon }) => `import ${icon} from ${JSON.stringify(iconImportPath(icon))}`,
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
// Reference icon imports in `examples` exported object
|
|
26
|
+
// "icon":"Sparkles" -> "icon":Sparkles
|
|
27
|
+
const iconStringsToIdents = (jsonBlob: string) => jsonBlob.replace(/"icon":\s*"(\w+)"/g, '"icon":$1');
|
|
28
|
+
const exportBody = `export const examples = ${iconStringsToIdents(JSON.stringify(exampleOverrides))};`;
|
|
29
|
+
|
|
30
|
+
return [...iconImports, exportBody].join('\n');
|
|
31
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ResponseChunk } from './schemas';
|
|
2
|
+
export { responseChunk, type ResponseChunk } from './schemas';
|
|
3
|
+
|
|
4
|
+
export type DocsChatHandler = {
|
|
5
|
+
generateResponse: (
|
|
6
|
+
{
|
|
7
|
+
query,
|
|
8
|
+
priorMessages,
|
|
9
|
+
}: {
|
|
10
|
+
query: string;
|
|
11
|
+
priorMessages: { role: 'user' | 'assistant'; content: string }[];
|
|
12
|
+
},
|
|
13
|
+
abortSignal: AbortSignal,
|
|
14
|
+
) => AsyncGenerator<ResponseChunk>;
|
|
15
|
+
|
|
16
|
+
onRate?: (spanId: string, score: 0 | 1) => Promise<unknown>;
|
|
17
|
+
};
|