@stainless-api/docs 0.1.0-beta.93 → 0.1.0-beta.94
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 +13 -0
- package/eslint-suppressions.json +0 -5
- package/package.json +3 -3
- package/plugin/buildAlgoliaIndex.ts +12 -43
- package/plugin/components/SDKSelect.astro +3 -6
- package/plugin/helpers/generateDocsRoutes.ts +32 -0
- package/plugin/helpers/multiSpec.ts +8 -0
- package/plugin/index.ts +53 -46
- package/plugin/loadPluginConfig.ts +131 -62
- package/plugin/react/Routing.tsx +2 -4
- package/plugin/routes/Docs.astro +5 -2
- package/plugin/routes/DocsStatic.astro +2 -4
- package/plugin/routes/Overview.astro +21 -7
- package/plugin/routes/markdown.ts +4 -4
- package/plugin/specs/FileCache.ts +99 -0
- package/plugin/specs/fetchSpecSSR.ts +16 -10
- package/plugin/specs/generateSpec.ts +88 -26
- package/plugin/specs/index.ts +88 -195
- package/plugin/specs/inputResolver.ts +146 -0
- package/virtual-module.d.ts +19 -3
- package/plugin/helpers/getDocsLanguages.ts +0 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @stainless-api/docs
|
|
2
2
|
|
|
3
|
+
## 0.1.0-beta.94
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- beff534: Adds support for multiple sdkjsons
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [cd578b7]
|
|
12
|
+
- Updated dependencies [beff534]
|
|
13
|
+
- @stainless-api/docs-ui@0.1.0-beta.71
|
|
14
|
+
- @stainless-api/docs-search@0.1.0-beta.24
|
|
15
|
+
|
|
3
16
|
## 0.1.0-beta.93
|
|
4
17
|
|
|
5
18
|
### Patch Changes
|
package/eslint-suppressions.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stainless-api/docs",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.94",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -56,8 +56,8 @@
|
|
|
56
56
|
"vite-plugin-prebundle-workers": "^0.2.0",
|
|
57
57
|
"web-worker": "^1.5.0",
|
|
58
58
|
"yaml": "^2.8.2",
|
|
59
|
-
"@stainless-api/docs-search": "0.1.0-beta.
|
|
60
|
-
"@stainless-api/docs-ui": "0.1.0-beta.
|
|
59
|
+
"@stainless-api/docs-search": "0.1.0-beta.24",
|
|
60
|
+
"@stainless-api/docs-ui": "0.1.0-beta.71",
|
|
61
61
|
"@stainless-api/ui-primitives": "0.1.0-beta.47"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import Markdoc from '@markdoc/markdoc';
|
|
2
|
-
import Stainless from '@stainless-api/sdk';
|
|
3
|
-
import { createSDKJSON, parseInputs, transformOAS } from './specs/worker';
|
|
4
|
-
import type * as SDKJSON from '@stainless/sdk-json';
|
|
5
|
-
import { Languages } from '@stainless-api/docs-ui/routing';
|
|
6
2
|
import { buildIndex } from '@stainless-api/docs-search/providers/algolia';
|
|
7
3
|
import type { AstroIntegrationLogger } from 'astro';
|
|
4
|
+
import { generateIndex } from '@stainless-api/docs-search/indexer';
|
|
5
|
+
import { SpecComposite } from './specs';
|
|
8
6
|
|
|
9
7
|
const markdocConfig = {
|
|
10
8
|
nodes: {
|
|
@@ -21,14 +19,10 @@ function renderMarkdown(content?: string) {
|
|
|
21
19
|
}
|
|
22
20
|
|
|
23
21
|
export async function buildAlgoliaIndex({
|
|
24
|
-
|
|
25
|
-
branch,
|
|
26
|
-
apiKey,
|
|
22
|
+
specComposite,
|
|
27
23
|
logger,
|
|
28
24
|
}: {
|
|
29
|
-
|
|
30
|
-
branch: string;
|
|
31
|
-
apiKey: string;
|
|
25
|
+
specComposite: SpecComposite;
|
|
32
26
|
logger?: AstroIntegrationLogger;
|
|
33
27
|
}) {
|
|
34
28
|
function warnLog(message: string) {
|
|
@@ -47,38 +41,6 @@ export async function buildAlgoliaIndex({
|
|
|
47
41
|
}
|
|
48
42
|
}
|
|
49
43
|
|
|
50
|
-
// TODO: this is all redundant with the spec code and should be DRYed
|
|
51
|
-
const client = new Stainless({ apiKey });
|
|
52
|
-
const configs = await client.projects.configs.retrieve({
|
|
53
|
-
project: stainlessProject,
|
|
54
|
-
branch,
|
|
55
|
-
include: 'openapi',
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
const configYML = Object.values(configs)[0] as { content: any };
|
|
59
|
-
const oasJson = Object.values(configs)[1] as { content: any };
|
|
60
|
-
const configStr = configYML['content'];
|
|
61
|
-
const oasStr = oasJson['content'];
|
|
62
|
-
const { oas, config } = await parseInputs({
|
|
63
|
-
oas: oasStr,
|
|
64
|
-
config: configStr,
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
const transformedOAS = await transformOAS({ oas, config });
|
|
68
|
-
|
|
69
|
-
const languages =
|
|
70
|
-
config.docs?.languages ??
|
|
71
|
-
(Object.entries(config.targets)
|
|
72
|
-
.filter(([name, target]) => Languages.includes(name) && !target.skip)
|
|
73
|
-
.map(([name]) => name) as SDKJSON.SpecLanguage[]);
|
|
74
|
-
|
|
75
|
-
const sdkJson = await createSDKJSON({
|
|
76
|
-
oas: transformedOAS,
|
|
77
|
-
config,
|
|
78
|
-
languages,
|
|
79
|
-
projectName: stainlessProject,
|
|
80
|
-
});
|
|
81
|
-
|
|
82
44
|
const {
|
|
83
45
|
PUBLIC_ALGOLIA_APP_ID: appId,
|
|
84
46
|
PUBLIC_ALGOLIA_INDEX: indexName,
|
|
@@ -94,6 +56,13 @@ export async function buildAlgoliaIndex({
|
|
|
94
56
|
warnLog(`Skipping Algolia indexing due to missing environment variables: ${missing.join(', ')}`);
|
|
95
57
|
return;
|
|
96
58
|
}
|
|
97
|
-
|
|
59
|
+
|
|
60
|
+
const indexEntries = specComposite
|
|
61
|
+
.listUniqueSpecs()
|
|
62
|
+
.flatMap((spec) =>
|
|
63
|
+
Array.from(generateIndex(spec.data.sdkJson, renderMarkdown, true, spec.data.languages)),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
await buildIndex(appId, indexName, algoliaWriteKey, indexEntries, renderMarkdown);
|
|
98
67
|
infoLog('Indexing complete.');
|
|
99
68
|
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { parseRoute } from '@stainless-api/docs-ui/routing';
|
|
3
|
-
import { DEFAULT_LANGUAGE
|
|
3
|
+
import { DEFAULT_LANGUAGE } from 'virtual:stl-starlight-virtual-module';
|
|
4
4
|
import { Languages } from '../languages';
|
|
5
5
|
import { SDKSelectReactComponent } from '../react/Routing';
|
|
6
|
-
import {
|
|
7
|
-
import { getDocsLanguages } from '../helpers/getDocsLanguages';
|
|
6
|
+
import { getDocsLanguages } from '../helpers/multiSpec';
|
|
8
7
|
import { API_REFERENCE_BASE_PATH } from 'virtual:stl-docs-virtual-module';
|
|
9
8
|
|
|
10
9
|
const slug = `/${Astro.locals.starlightRoute.id}`;
|
|
@@ -20,9 +19,7 @@ const data = {
|
|
|
20
19
|
defaultLanguage,
|
|
21
20
|
};
|
|
22
21
|
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
const options = getDocsLanguages(spec, EXCLUDE_LANGUAGES).map((value) => ({
|
|
22
|
+
const options = getDocsLanguages().map((value) => ({
|
|
26
23
|
value,
|
|
27
24
|
label: Languages[value].name,
|
|
28
25
|
selected: data.language === value,
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type * as SDKJSON from '@stainless/sdk-json';
|
|
2
2
|
import { DocsLanguage, generateRouteList } from '@stainless-api/docs-ui/routing';
|
|
3
3
|
import { EXCLUDE_LANGUAGES } from 'virtual:stl-starlight-virtual-module';
|
|
4
|
+
import { api } from 'virtual:stainless-apis-manifest';
|
|
5
|
+
import { getSDKJSONInSSR } from '../specs/fetchSpecSSR';
|
|
4
6
|
|
|
5
7
|
export function generateDocsRoutes(spec: SDKJSON.Spec, excludeLanguages: DocsLanguage[] = EXCLUDE_LANGUAGES) {
|
|
6
8
|
const paths = generateRouteList({
|
|
@@ -25,3 +27,33 @@ export function generateDocsRoutes(spec: SDKJSON.Spec, excludeLanguages: DocsLan
|
|
|
25
27
|
};
|
|
26
28
|
});
|
|
27
29
|
}
|
|
30
|
+
|
|
31
|
+
export async function generateAllDocsRoutes() {
|
|
32
|
+
const uniquePaths = new Set<string>();
|
|
33
|
+
|
|
34
|
+
const allRoutes = (
|
|
35
|
+
await Promise.all(
|
|
36
|
+
api.languages.map(async (entry) => {
|
|
37
|
+
const spec = await getSDKJSONInSSR(entry.language);
|
|
38
|
+
|
|
39
|
+
// this is super annoying, but preview worker _always_ generates HTTP routes
|
|
40
|
+
// so, we exclude them unless the we're explicitly told to include them
|
|
41
|
+
const excludeLanguages = [...EXCLUDE_LANGUAGES];
|
|
42
|
+
if (entry.language !== 'http') {
|
|
43
|
+
excludeLanguages.push('http');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const routes = generateDocsRoutes(spec, excludeLanguages);
|
|
47
|
+
return routes.filter((route) => {
|
|
48
|
+
if (uniquePaths.has(route.params.slug)) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
uniquePaths.add(route.params.slug);
|
|
52
|
+
return true;
|
|
53
|
+
});
|
|
54
|
+
}),
|
|
55
|
+
)
|
|
56
|
+
).flat();
|
|
57
|
+
|
|
58
|
+
return allRoutes;
|
|
59
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { api } from 'virtual:stainless-apis-manifest';
|
|
2
|
+
import { EXCLUDE_LANGUAGES } from 'virtual:stl-starlight-virtual-module';
|
|
3
|
+
|
|
4
|
+
export function getDocsLanguages() {
|
|
5
|
+
return api.languages
|
|
6
|
+
.map((language) => language.language)
|
|
7
|
+
.filter((language) => !EXCLUDE_LANGUAGES.includes(language));
|
|
8
|
+
}
|
package/plugin/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { StarlightPlugin } from '@astrojs/starlight/types';
|
|
|
3
3
|
import type { AstroIntegration } from 'astro';
|
|
4
4
|
import type { BundledTheme } from 'shiki';
|
|
5
5
|
import { config } from 'dotenv';
|
|
6
|
-
import { buildAlgoliaIndex } from './buildAlgoliaIndex';
|
|
6
|
+
// import { buildAlgoliaIndex } from './buildAlgoliaIndex';
|
|
7
7
|
import {
|
|
8
8
|
getAPIReferencePlaceholderItemFromSidebarConfig,
|
|
9
9
|
makePlaceholderItems,
|
|
@@ -32,8 +32,8 @@ import prebundleWorkers from 'vite-plugin-prebundle-workers';
|
|
|
32
32
|
import { SpecLoader, startSpecLoader } from './specs';
|
|
33
33
|
|
|
34
34
|
import type * as ReferenceSidebarsVirtualModule from 'virtual:stl-starlight-reference-sidebars';
|
|
35
|
-
import { getDocsLanguages } from './helpers/getDocsLanguages';
|
|
36
35
|
import { generateMissingRouteList } from '@stainless-api/docs-ui/routing';
|
|
36
|
+
import { buildAlgoliaIndex } from './buildAlgoliaIndex';
|
|
37
37
|
|
|
38
38
|
export { generateAPILink } from './generateAPIReferenceLink';
|
|
39
39
|
export type { ReferenceSidebarConfigItem };
|
|
@@ -102,13 +102,10 @@ async function stlStarlightAstroIntegration(
|
|
|
102
102
|
let astroBase = '/';
|
|
103
103
|
|
|
104
104
|
let specLoader: SpecLoader | undefined;
|
|
105
|
-
async function
|
|
105
|
+
async function resolveSpecs() {
|
|
106
106
|
if (!specLoader) throw new Error('Expected spec loader to exist');
|
|
107
107
|
const result = await specLoader.specPromise;
|
|
108
|
-
return
|
|
109
|
-
spec: result.spec.data,
|
|
110
|
-
auth: result.spec.auth,
|
|
111
|
-
};
|
|
108
|
+
return result.specComposite;
|
|
112
109
|
}
|
|
113
110
|
|
|
114
111
|
let building: Promise<void> | undefined;
|
|
@@ -119,9 +116,16 @@ async function stlStarlightAstroIntegration(
|
|
|
119
116
|
if (!pluginConfig.experimentalPlaygrounds) return;
|
|
120
117
|
if (building) return building;
|
|
121
118
|
return (building = (async () => {
|
|
122
|
-
const
|
|
119
|
+
const specComposite = await resolveSpecs();
|
|
120
|
+
|
|
121
|
+
if (specComposite.listUniqueSpecs().length > 1) {
|
|
122
|
+
throw new Error('Multiple specs found. This is not supported for Playgrounds.');
|
|
123
|
+
}
|
|
123
124
|
|
|
124
|
-
const
|
|
125
|
+
const spec = specComposite.listUniqueSpecs()[0]!.data.sdkJson;
|
|
126
|
+
const auth = specComposite.listUniqueSpecs()[0]!.data.auth;
|
|
127
|
+
|
|
128
|
+
const langs = specComposite.getLanguages();
|
|
125
129
|
|
|
126
130
|
await buildPlaygrounds!({
|
|
127
131
|
spec,
|
|
@@ -150,14 +154,9 @@ async function stlStarlightAstroIntegration(
|
|
|
150
154
|
const projectDir = astroConfig.root.pathname;
|
|
151
155
|
astroBase = astroConfig.base;
|
|
152
156
|
|
|
157
|
+
logger.warn('Starting spec loader!!!');
|
|
158
|
+
|
|
153
159
|
specLoader = await startSpecLoader(pluginConfig, logger, createCodegenDir());
|
|
154
|
-
specLoader.specPromise.then((specResult) => {
|
|
155
|
-
if (specResult.result === 'generated') {
|
|
156
|
-
logger.info(`Generated new SDKJSON`);
|
|
157
|
-
} else if (specResult.result === 'exists') {
|
|
158
|
-
logger.info(`Loaded SDKJSON`);
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
160
|
|
|
162
161
|
reportError = (message: string) => logger.error(message);
|
|
163
162
|
|
|
@@ -198,22 +197,23 @@ async function stlStarlightAstroIntegration(
|
|
|
198
197
|
makeAsyncVirtualModPlugin<typeof ReferenceSidebarsVirtualModule>(
|
|
199
198
|
'virtual:stl-starlight-reference-sidebars',
|
|
200
199
|
async () => {
|
|
201
|
-
|
|
202
|
-
const
|
|
200
|
+
// we know specLoader exists here
|
|
201
|
+
const { specComposite } = await specLoader!.specPromise;
|
|
203
202
|
|
|
204
203
|
const sidebars = [...sidebarConfigs.entries()]
|
|
205
204
|
// produce all { id, language } combos with the attached config
|
|
206
205
|
// flattens to one item per language * id combo
|
|
207
206
|
.flatMap(([id, config]) =>
|
|
208
|
-
|
|
207
|
+
specComposite.listAllLanguagesAndIncludeSpecs().map((res) => ({
|
|
209
208
|
id,
|
|
210
209
|
config,
|
|
211
|
-
language,
|
|
210
|
+
language: res.language,
|
|
211
|
+
spec: res.spec.data.sdkJson,
|
|
212
212
|
})),
|
|
213
213
|
)
|
|
214
214
|
// produce a sidebar for each
|
|
215
215
|
// later we will .find() the sidebar that matches the (id, language)
|
|
216
|
-
.map(({ id, config, language }) => {
|
|
216
|
+
.map(({ id, config, language, spec }) => {
|
|
217
217
|
const configItemsBuilder = new SidebarConfigItemsBuilder(
|
|
218
218
|
spec,
|
|
219
219
|
language,
|
|
@@ -232,7 +232,7 @@ async function stlStarlightAstroIntegration(
|
|
|
232
232
|
// this has to run multpile times because it depends on the
|
|
233
233
|
// userSidebarConfig (which is per-id) and the language
|
|
234
234
|
entries: toStarlightSidebar({
|
|
235
|
-
basePath: path.posix.join(astroBase, pluginConfig.basePath),
|
|
235
|
+
basePath: path.posix.join(astroBase, pluginConfig.basePath),
|
|
236
236
|
spec,
|
|
237
237
|
entries: userSidebarConfig,
|
|
238
238
|
currentLanguage: language,
|
|
@@ -243,7 +243,7 @@ async function stlStarlightAstroIntegration(
|
|
|
243
243
|
return { sidebars };
|
|
244
244
|
},
|
|
245
245
|
),
|
|
246
|
-
specLoader.
|
|
246
|
+
...specLoader.vitePlugins,
|
|
247
247
|
{
|
|
248
248
|
name: 'stl-starlight-vite',
|
|
249
249
|
buildStart() {
|
|
@@ -314,7 +314,7 @@ async function stlStarlightAstroIntegration(
|
|
|
314
314
|
ENABLE_CONTEXT_MENU: pluginConfig.contextMenu,
|
|
315
315
|
EXPERIMENTAL_PLAYGROUNDS: !!pluginConfig.experimentalPlaygrounds,
|
|
316
316
|
EXPERIMENTAL_REQUEST_BUILDER: pluginConfig.experimentalRequestBuilder,
|
|
317
|
-
STAINLESS_PROJECT: pluginConfig.
|
|
317
|
+
STAINLESS_PROJECT: pluginConfig.stainlessProject,
|
|
318
318
|
} satisfies Omit<typeof StlStarlightVirtualModule, 'MIDDLEWARE'>),
|
|
319
319
|
vmMiddlewareExport,
|
|
320
320
|
].join('\n');
|
|
@@ -363,12 +363,22 @@ async function stlStarlightAstroIntegration(
|
|
|
363
363
|
};
|
|
364
364
|
await writeFile(path.join(stainlessDir, 'stl-manifest.json'), JSON.stringify(manifest, null, 2));
|
|
365
365
|
|
|
366
|
+
const specComposite = await resolveSpecs();
|
|
367
|
+
|
|
368
|
+
await buildAlgoliaIndex({
|
|
369
|
+
specComposite,
|
|
370
|
+
logger,
|
|
371
|
+
});
|
|
372
|
+
|
|
366
373
|
// Generate a list of missing API routes to enable graceful handling of unimplemented SDK methods.
|
|
367
374
|
// When users switch languages in the docs, some API methods may not be implemented in the target SDK.
|
|
368
375
|
// Instead of showing a generic 404, we statically generate pages for these routes and mark them
|
|
369
376
|
// in this file so Cloudflare can serve them with a 404 status. These pages display helpful information
|
|
370
377
|
// about the missing method and provide links to SDKs where it is available.
|
|
371
|
-
|
|
378
|
+
|
|
379
|
+
// TODO: (multi-spec) support multiple specs
|
|
380
|
+
const spec = specComposite.listUniqueSpecs()[0]!.data.sdkJson;
|
|
381
|
+
|
|
372
382
|
const missingRoutes = generateMissingRouteList({
|
|
373
383
|
spec,
|
|
374
384
|
basePath: path.posix.join(astroBase, pluginConfig.basePath),
|
|
@@ -402,7 +412,10 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
|
|
|
402
412
|
|
|
403
413
|
const logger = getSharedLogger({ fallback: localLogger });
|
|
404
414
|
|
|
405
|
-
const configParseResult = parseStarlightPluginConfig(someUserConfig,
|
|
415
|
+
const configParseResult = parseStarlightPluginConfig(someUserConfig, {
|
|
416
|
+
command,
|
|
417
|
+
base: astroConfig.base,
|
|
418
|
+
});
|
|
406
419
|
if (configParseResult.result === 'error') {
|
|
407
420
|
const errorLines = configParseResult.message.split('\n');
|
|
408
421
|
for (const line of errorLines) {
|
|
@@ -419,26 +432,18 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
|
|
|
419
432
|
addIntegration(react());
|
|
420
433
|
}
|
|
421
434
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
if (command === 'build' && config.specInputs.kind === 'stainless_api_inputs') {
|
|
435
|
-
await buildAlgoliaIndex({
|
|
436
|
-
stainlessProject: config.specInputs.project,
|
|
437
|
-
branch: config.specInputs.branch,
|
|
438
|
-
apiKey: config.specInputs.apiKey.value,
|
|
439
|
-
logger,
|
|
440
|
-
});
|
|
441
|
-
}
|
|
435
|
+
// TODO: (multi-spec) re-add this? not strictly necessary to merge
|
|
436
|
+
// if ('apiKey' in config.specInputs) {
|
|
437
|
+
// if (!config.specInputs.apiKey) {
|
|
438
|
+
// logger.info(`Stainless credentials not loaded`);
|
|
439
|
+
// } else if (config.specInputs.apiKey.source === 'explicit-config') {
|
|
440
|
+
// logger.info(`Stainless credentials loaded from user config`);
|
|
441
|
+
// } else if (config.specInputs.apiKey.source === 'environment-variable') {
|
|
442
|
+
// logger.info('Stainless credentials loaded from `STAINLESS_API_KEY` environment variable');
|
|
443
|
+
// } else if (config.specInputs.apiKey.source === 'cli') {
|
|
444
|
+
// logger.info('Stainless credentials loaded from `stl` CLI');
|
|
445
|
+
// }
|
|
446
|
+
// })
|
|
442
447
|
|
|
443
448
|
if (starlightConfig.sidebar) {
|
|
444
449
|
// for pagination (https://starlight.astro.build/reference/configuration/#pagination) to work correctly
|
|
@@ -483,3 +488,5 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
|
|
|
483
488
|
// Additional exports we want for Stainless <-> docs integration.
|
|
484
489
|
export { parseStainlessPath } from '@stainless-api/docs-ui/routing';
|
|
485
490
|
export { renderMarkdown } from '@stainless-api/docs-ui/markdown';
|
|
491
|
+
|
|
492
|
+
export { resolveSpec } from './specs/inputResolver';
|
|
@@ -6,6 +6,13 @@ import type { CreateShikiHighlighterOptions } from '@astrojs/markdown-remark';
|
|
|
6
6
|
import type { DocsLanguage } from '@stainless-api/docs-ui/routing';
|
|
7
7
|
import type { PropertySettingsType } from '@stainless-api/docs-ui/contexts';
|
|
8
8
|
import { bold } from '../shared/terminalUtils';
|
|
9
|
+
import { resolveSpec, SpecInputResolver } from './specs/inputResolver';
|
|
10
|
+
import { specCache, SpecCacheResult } from './specs/generateSpec';
|
|
11
|
+
|
|
12
|
+
export type LanguageGenerateQuery = {
|
|
13
|
+
mode: 'exclude' | 'only';
|
|
14
|
+
list: DocsLanguage[];
|
|
15
|
+
};
|
|
9
16
|
|
|
10
17
|
export type AstroCommand = 'dev' | 'build' | 'preview' | 'sync';
|
|
11
18
|
|
|
@@ -42,6 +49,19 @@ export type StainlessStarlightUserConfig = {
|
|
|
42
49
|
*/
|
|
43
50
|
stainlessProject: string;
|
|
44
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Powerful configuration options for customized use cases
|
|
54
|
+
*/
|
|
55
|
+
advanced?: {
|
|
56
|
+
/**
|
|
57
|
+
*
|
|
58
|
+
* More advanced replacement for versions, basePath, and excludeLanguages.
|
|
59
|
+
*/
|
|
60
|
+
overrideSpecs?: {
|
|
61
|
+
specs: SpecInputResolver[];
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
|
|
45
65
|
/**
|
|
46
66
|
* Optional list of versions to render in the API reference.
|
|
47
67
|
*/
|
|
@@ -147,11 +167,12 @@ export type StainlessStarlightUserConfig = {
|
|
|
147
167
|
experimentalPrerender?: boolean;
|
|
148
168
|
};
|
|
149
169
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
170
|
+
// TODO: eventually? re-add support for external spec servers
|
|
171
|
+
// export type ExternalSpecServerUserConfig = Omit<StainlessStarlightUserConfig, 'stainlessProject'> & {
|
|
172
|
+
// externalSpecServerUrl: string;
|
|
173
|
+
// };
|
|
153
174
|
|
|
154
|
-
export type SomeStainlessStarlightUserConfig = StainlessStarlightUserConfig
|
|
175
|
+
export type SomeStainlessStarlightUserConfig = StainlessStarlightUserConfig;
|
|
155
176
|
|
|
156
177
|
function resolvePath(inputPath: string) {
|
|
157
178
|
return path.resolve(process.cwd(), inputPath);
|
|
@@ -231,29 +252,110 @@ function loadApiKey(configValue: string | undefined): LoadedApiKey | null {
|
|
|
231
252
|
return { value: accessToken, source: 'cli' };
|
|
232
253
|
}
|
|
233
254
|
|
|
234
|
-
export
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
255
|
+
export function forceLoadStainlessCredentials(): LoadedApiKey {
|
|
256
|
+
const v = loadApiKey(undefined);
|
|
257
|
+
if (!v) {
|
|
258
|
+
throw new Error(`Failed to load Stainless credentials.`);
|
|
259
|
+
}
|
|
260
|
+
return v;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
type AstroOptions = {
|
|
264
|
+
command: AstroCommand;
|
|
265
|
+
base: string;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
export type ResolvedAPIConfigEntry = {
|
|
269
|
+
loadSpecs: () => Promise<SpecCacheResult[]>;
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
function makeSimpleAPIConfig(
|
|
273
|
+
partial: SomeStainlessStarlightUserConfig,
|
|
274
|
+
astroOptions: AstroOptions,
|
|
275
|
+
): ResolvedAPIConfigEntry {
|
|
276
|
+
if (!('stainlessProject' in partial)) {
|
|
277
|
+
throw new Error('You must provide a stainlessProject when using Stainless Starlight');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const excludedLanguages = ['php' as const, ...(partial.excludeLanguages ?? [])];
|
|
281
|
+
|
|
282
|
+
const apiKey = loadApiKey(partial.apiKey);
|
|
283
|
+
|
|
284
|
+
function getResolver() {
|
|
285
|
+
const localFilePaths = getLocalFilePaths(astroOptions.command);
|
|
286
|
+
if (localFilePaths) {
|
|
287
|
+
return resolveSpec.fromFiles({
|
|
288
|
+
oasPath: localFilePaths.oasPath,
|
|
289
|
+
configPath: localFilePaths.configPath,
|
|
290
|
+
languageOverrides: {
|
|
291
|
+
mode: 'exclude',
|
|
292
|
+
list: excludedLanguages,
|
|
293
|
+
},
|
|
294
|
+
stainlessProject: partial.stainlessProject,
|
|
295
|
+
});
|
|
240
296
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
297
|
+
|
|
298
|
+
if (!apiKey) {
|
|
299
|
+
throw new Error(
|
|
300
|
+
[
|
|
301
|
+
bold(
|
|
302
|
+
'No Stainless credentials found. Please choose one of the following options to authenticate with Stainless:',
|
|
303
|
+
),
|
|
304
|
+
'- Run `stl auth login` to authenticate via the Stainless CLI',
|
|
305
|
+
'- Provide a Stainless API key via the `STAINLESS_API_KEY` environment variable (eg. in a .env file)',
|
|
306
|
+
'- Set the `apiKey` option in the Stainless Docs config',
|
|
307
|
+
].join('\n'),
|
|
308
|
+
);
|
|
246
309
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
310
|
+
return resolveSpec.fromStainlessApi({
|
|
311
|
+
stainlessProject: partial.stainlessProject,
|
|
312
|
+
branch: partial.versions?.[0]?.branch ?? 'main',
|
|
313
|
+
apiKey: apiKey,
|
|
314
|
+
languageOverrides: {
|
|
315
|
+
mode: 'exclude',
|
|
316
|
+
list: excludedLanguages,
|
|
317
|
+
},
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const resolver = getResolver();
|
|
322
|
+
|
|
323
|
+
return {
|
|
324
|
+
loadSpecs: async function loadSpecs() {
|
|
325
|
+
const inputs = await resolver.resolve({ apiKey });
|
|
326
|
+
|
|
327
|
+
const result = await specCache.get(inputs);
|
|
328
|
+
|
|
329
|
+
return [result];
|
|
330
|
+
},
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function loadAPIConfig(
|
|
335
|
+
partial: SomeStainlessStarlightUserConfig,
|
|
336
|
+
astroOptions: AstroOptions,
|
|
337
|
+
): ResolvedAPIConfigEntry {
|
|
338
|
+
if (partial.advanced?.overrideSpecs) {
|
|
339
|
+
const overrides = partial.advanced.overrideSpecs;
|
|
340
|
+
const apiKey = loadApiKey(partial.apiKey);
|
|
341
|
+
return {
|
|
342
|
+
loadSpecs: async function loadSpecs() {
|
|
343
|
+
const specsPromises = overrides.specs.map(async (spec) => {
|
|
344
|
+
const inputs = await spec.resolve({ apiKey });
|
|
345
|
+
return await specCache.get(inputs);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
return await Promise.all(specsPromises);
|
|
349
|
+
},
|
|
252
350
|
};
|
|
351
|
+
}
|
|
352
|
+
return makeSimpleAPIConfig(partial, astroOptions);
|
|
353
|
+
}
|
|
253
354
|
|
|
254
|
-
function normalizeConfig(partial: SomeStainlessStarlightUserConfig,
|
|
355
|
+
function normalizeConfig(partial: SomeStainlessStarlightUserConfig, astroOptions: AstroOptions) {
|
|
255
356
|
const configWithDefaults = {
|
|
256
357
|
basePath: partial.basePath ?? '/api',
|
|
358
|
+
astroBase: astroOptions.base,
|
|
257
359
|
// TODO: why are we excluding php; do we need to
|
|
258
360
|
excludeLanguages: ['php' as const, ...(partial.excludeLanguages ?? [])],
|
|
259
361
|
defaultLanguage: partial.defaultLanguage ?? 'http',
|
|
@@ -283,50 +385,14 @@ function normalizeConfig(partial: SomeStainlessStarlightUserConfig, command: Ast
|
|
|
283
385
|
experimentalPlaygrounds: partial.experimentalPlaygrounds ?? undefined,
|
|
284
386
|
experimentalRequestBuilder: partial.experimentalRequestBuilder ?? false,
|
|
285
387
|
experimentalPrerender: partial.experimentalPrerender ?? true,
|
|
388
|
+
stainlessProject: partial.stainlessProject,
|
|
286
389
|
};
|
|
287
390
|
|
|
288
|
-
|
|
289
|
-
if (!('stainlessProject' in partial)) {
|
|
290
|
-
throw new Error('You must provide a stainlessProject when using Stainless Starlight');
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
const localFilePaths = getLocalFilePaths(command);
|
|
294
|
-
if (localFilePaths) {
|
|
295
|
-
return {
|
|
296
|
-
kind: 'file_inputs',
|
|
297
|
-
project: partial.stainlessProject,
|
|
298
|
-
oasPath: localFilePaths.oasPath,
|
|
299
|
-
configPath: localFilePaths.configPath,
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
const apiKey = loadApiKey(partial.apiKey);
|
|
304
|
-
|
|
305
|
-
if (!apiKey) {
|
|
306
|
-
throw new Error(
|
|
307
|
-
[
|
|
308
|
-
bold(
|
|
309
|
-
'No Stainless credentials found. Please choose one of the following options to authenticate with Stainless:',
|
|
310
|
-
),
|
|
311
|
-
'- Run `stl auth login` to authenticate via the Stainless CLI',
|
|
312
|
-
'- Provide a Stainless API key via the `STAINLESS_API_KEY` environment variable (eg. in a .env file)',
|
|
313
|
-
'- Set the `apiKey` option in the Stainless Docs config',
|
|
314
|
-
].join('\n'),
|
|
315
|
-
);
|
|
316
|
-
}
|
|
317
|
-
return {
|
|
318
|
-
kind: 'stainless_api_inputs',
|
|
319
|
-
project: partial.stainlessProject,
|
|
320
|
-
branch: partial.versions?.[0]?.branch ?? 'main',
|
|
321
|
-
apiKey,
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
const specInputs = getSpecInputs();
|
|
391
|
+
const api = loadAPIConfig(partial, astroOptions);
|
|
326
392
|
|
|
327
393
|
return {
|
|
328
394
|
...configWithDefaults,
|
|
329
|
-
|
|
395
|
+
api,
|
|
330
396
|
};
|
|
331
397
|
}
|
|
332
398
|
|
|
@@ -341,9 +407,12 @@ export type NormalizedStainlessStarlightConfig = ReturnType<typeof normalizeConf
|
|
|
341
407
|
- If a field is only used in certain contexts, we make each context a discriminated union (see SDKJSONInputs)
|
|
342
408
|
- We prefer empty arrays over undefined/null
|
|
343
409
|
*/
|
|
344
|
-
export function parseStarlightPluginConfig(
|
|
410
|
+
export function parseStarlightPluginConfig(
|
|
411
|
+
partial: SomeStainlessStarlightUserConfig,
|
|
412
|
+
astroOptions: AstroOptions,
|
|
413
|
+
) {
|
|
345
414
|
try {
|
|
346
|
-
const config = normalizeConfig(partial,
|
|
415
|
+
const config = normalizeConfig(partial, astroOptions);
|
|
347
416
|
return {
|
|
348
417
|
result: 'success' as const,
|
|
349
418
|
config,
|