@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 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
@@ -1,9 +1,4 @@
1
1
  {
2
- "plugin/buildAlgoliaIndex.ts": {
3
- "@typescript-eslint/no-explicit-any": {
4
- "count": 2
5
- }
6
- },
7
2
  "plugin/components/SnippetCode.tsx": {
8
3
  "@typescript-eslint/no-explicit-any": {
9
4
  "count": 1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stainless-api/docs",
3
- "version": "0.1.0-beta.93",
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.23",
60
- "@stainless-api/docs-ui": "0.1.0-beta.70",
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
- stainlessProject,
25
- branch,
26
- apiKey,
22
+ specComposite,
27
23
  logger,
28
24
  }: {
29
- stainlessProject: string;
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
- await buildIndex(appId, indexName, algoliaWriteKey, sdkJson, renderMarkdown);
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, EXCLUDE_LANGUAGES } from 'virtual:stl-starlight-virtual-module';
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 { getSDKJSONInSSR } from '../specs/fetchSpecSSR';
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 spec = await getSDKJSONInSSR();
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 getSpec() {
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 { spec, auth } = await getSpec();
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 langs = getDocsLanguages(spec, pluginConfig.excludeLanguages);
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
- const { spec } = await getSpec();
202
- const languages = getDocsLanguages(spec, pluginConfig.excludeLanguages);
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
- languages.map((language) => ({
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), // TODO, is this right?
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.vitePlugin,
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.specInputs.project,
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
- const { spec } = await getSpec();
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, command);
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
- if ('apiKey' in config.specInputs) {
423
- if (!config.specInputs.apiKey) {
424
- logger.info(`Stainless credentials not loaded`);
425
- } else if (config.specInputs.apiKey.source === 'explicit-config') {
426
- logger.info(`Stainless credentials loaded from user config`);
427
- } else if (config.specInputs.apiKey.source === 'environment-variable') {
428
- logger.info('Stainless credentials loaded from `STAINLESS_API_KEY` environment variable');
429
- } else if (config.specInputs.apiKey.source === 'cli') {
430
- logger.info('Stainless credentials loaded from `stl` CLI');
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
- export type ExternalSpecServerUserConfig = Omit<StainlessStarlightUserConfig, 'stainlessProject'> & {
151
- externalSpecServerUrl: string;
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 | ExternalSpecServerUserConfig;
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 type SDKJSONInputs =
235
- | {
236
- kind: 'file_inputs';
237
- project: string;
238
- oasPath: string;
239
- configPath: string;
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
- kind: 'stainless_api_inputs';
243
- project: string;
244
- branch: string;
245
- apiKey: LoadedApiKey;
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
- kind: 'string_inputs';
249
- oasStr: string;
250
- configStr: string;
251
- project: string;
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, command: AstroCommand) {
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
- function getSpecInputs(): SDKJSONInputs {
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
- specInputs,
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(partial: SomeStainlessStarlightUserConfig, command: AstroCommand) {
410
+ export function parseStarlightPluginConfig(
411
+ partial: SomeStainlessStarlightUserConfig,
412
+ astroOptions: AstroOptions,
413
+ ) {
345
414
  try {
346
- const config = normalizeConfig(partial, command);
415
+ const config = normalizeConfig(partial, astroOptions);
347
416
  return {
348
417
  result: 'success' as const,
349
418
  config,