@stainless-api/docs 0.1.0-beta.138 → 0.1.0-beta.139

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,17 @@
1
1
  # @stainless-api/docs
2
2
 
3
+ ## 0.1.0-beta.139
4
+
5
+ ### Minor Changes
6
+
7
+ - d068123: bring-your-own spec loader
8
+ - e5367b0: Move preview worker to @stainless/sdk-json package
9
+
10
+ ### Patch Changes
11
+
12
+ - Updated dependencies [e5367b0]
13
+ - @stainless/sdk-json@0.1.0-beta.11
14
+
3
15
  ## 0.1.0-beta.138
4
16
 
5
17
  ### Patch Changes
@@ -27,31 +27,6 @@
27
27
  "count": 1
28
28
  }
29
29
  },
30
- "plugin/specs/generateSpec.ts": {
31
- "@typescript-eslint/no-unsafe-assignment": {
32
- "count": 2
33
- }
34
- },
35
- "plugin/specs/worker.ts": {
36
- "@typescript-eslint/no-explicit-any": {
37
- "count": 3
38
- },
39
- "@typescript-eslint/no-unsafe-argument": {
40
- "count": 1
41
- },
42
- "@typescript-eslint/no-unsafe-assignment": {
43
- "count": 7
44
- },
45
- "@typescript-eslint/no-unsafe-member-access": {
46
- "count": 4
47
- },
48
- "@typescript-eslint/no-unsafe-return": {
49
- "count": 1
50
- },
51
- "@typescript-eslint/prefer-promise-reject-errors": {
52
- "count": 4
53
- }
54
- },
55
30
  "stl-docs/components/sidebars/convertAstroSidebarToStl.tsx": {
56
31
  "@typescript-eslint/no-unsafe-argument": {
57
32
  "count": 1
package/eslint.config.ts CHANGED
@@ -1,8 +1,4 @@
1
1
  import { defineConfig } from 'eslint/config';
2
2
  import { config } from '@stainless/eslint-config/astro';
3
3
 
4
- export default defineConfig(
5
- config,
6
-
7
- { ignores: ['plugin/vendor/**'] },
8
- );
4
+ export default defineConfig(config);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stainless-api/docs",
3
- "version": "0.1.0-beta.138",
3
+ "version": "0.1.0-beta.139",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -71,10 +71,10 @@
71
71
  "remend": "^1.3.0",
72
72
  "shiki": "^4.0.2",
73
73
  "unified": "^11.0.5",
74
- "web-worker": "^1.5.0",
75
74
  "@stainless-api/docs-search": "0.1.0-beta.49",
76
75
  "@stainless-api/docs-ui": "0.1.0-beta.95",
77
- "@stainless-api/ui-primitives": "0.1.0-beta.53"
76
+ "@stainless-api/ui-primitives": "0.1.0-beta.53",
77
+ "@stainless/sdk-json": "^0.1.0-beta.11"
78
78
  },
79
79
  "devDependencies": {
80
80
  "@astrojs/check": "^0.9.9",
@@ -83,18 +83,16 @@
83
83
  "@types/react": "19.2.14",
84
84
  "@types/react-dom": "^19.2.3",
85
85
  "@types/react-syntax-highlighter": "^15.5.13",
86
- "astro": "^6.2.2",
87
- "react": "^19.2.5",
88
- "react-dom": "^19.2.5",
86
+ "astro": "^6.3.1",
87
+ "react": "^19.2.6",
88
+ "react-dom": "^19.2.6",
89
89
  "typescript": "6.0.3",
90
90
  "vite": "^7.3.2",
91
91
  "vitest": "^4.1.5",
92
92
  "zod": "^4.4.3",
93
- "@stainless/eslint-config": "0.1.0-beta.2",
94
- "@stainless/sdk-json": "^0.1.0-beta.10"
93
+ "@stainless/eslint-config": "0.1.0-beta.2"
95
94
  },
96
95
  "scripts": {
97
- "vendor-deps": "node scripts/vendor_deps.ts",
98
96
  "lint": "eslint --flag unstable_native_nodejs_ts_config . --max-warnings 0",
99
97
  "sync": "astro sync",
100
98
  "check:types": "astro check",
@@ -2,7 +2,8 @@ import Markdoc from '@markdoc/markdoc';
2
2
  import { buildIndex } from '@stainless-api/docs-search/providers/algolia';
3
3
  import type { AstroIntegrationLogger } from 'astro';
4
4
  import { generateIndex } from '@stainless-api/docs-search/indexer';
5
- import { SpecComposite } from './specs';
5
+ import type { LoadedSpecs } from './specs/utils';
6
+ import { isSupportedLanguage } from '@stainless-api/docs-ui/routing';
6
7
 
7
8
  const markdocConfig = {
8
9
  nodes: {
@@ -19,10 +20,10 @@ function renderMarkdown(content?: string) {
19
20
  }
20
21
 
21
22
  export async function buildAlgoliaIndex({
22
- specComposite,
23
+ loadedSpecs,
23
24
  logger,
24
25
  }: {
25
- specComposite: SpecComposite;
26
+ loadedSpecs: LoadedSpecs;
26
27
  logger?: AstroIntegrationLogger;
27
28
  }) {
28
29
  function warnLog(message: string) {
@@ -57,15 +58,8 @@ export async function buildAlgoliaIndex({
57
58
  return;
58
59
  }
59
60
 
60
- const indexEntries = specComposite.listUniqueSpecs().flatMap((spec) =>
61
- Array.from(
62
- generateIndex(
63
- spec.data.sdkJson,
64
- renderMarkdown,
65
- true,
66
- spec.data.languages.filter((l) => l !== 'sql' && l !== 'openapi'),
67
- ),
68
- ),
61
+ const indexEntries = loadedSpecs.flatMap((spec) =>
62
+ Array.from(generateIndex(spec.sdkJson, renderMarkdown, true, spec.languages.filter(isSupportedLanguage))),
69
63
  );
70
64
 
71
65
  await buildIndex(appId, indexName, algoliaWriteKey, indexEntries, renderMarkdown);
package/plugin/index.ts CHANGED
@@ -26,11 +26,17 @@ import { getSharedLogger } from '../shared/getSharedLogger';
26
26
  import { resolveSrcFile } from '../resolveSrcFile';
27
27
  import { mkdir, writeFile } from 'fs/promises';
28
28
  import { fileURLToPath } from 'url';
29
- import { SpecLoader, startSpecLoader } from './specs';
30
29
 
31
30
  import type * as ReferenceSidebarsVirtualModule from 'virtual:stl-starlight-reference-sidebars';
32
- import { generateMissingRouteList } from '@stainless-api/docs-ui/routing';
31
+ import type * as VirtualManifestModule from 'virtual:stainless-apis-manifest';
32
+
33
+ import {
34
+ generateMissingRouteList,
35
+ isSupportedLanguage,
36
+ type DocsLanguage,
37
+ } from '@stainless-api/docs-ui/routing';
33
38
  import { buildAlgoliaIndex } from './buildAlgoliaIndex';
39
+ import { flatSpecsList, loadAllSpecs, LoadedSpecs } from './specs/utils';
34
40
 
35
41
  export { generateAPILink } from './generateAPIReferenceLink';
36
42
  export type { ReferenceSidebarConfigItem };
@@ -94,17 +100,18 @@ function stlStarlightAstroIntegration(pluginConfig: NormalizedStainlessStarlight
94
100
  const resolvedId = `\0${virtualId}`;
95
101
  let astroBase = '/';
96
102
 
97
- let specLoader: SpecLoader | undefined;
98
- async function resolveSpecs() {
99
- if (!specLoader) throw new Error('Expected spec loader to exist');
100
- const result = await specLoader.specPromise;
101
- return result.specComposite;
103
+ let specsPromise: Promise<LoadedSpecs> | undefined;
104
+ async function specsPromiseResolved() {
105
+ if (!specsPromise) throw new Error('Expected spec promise to exist');
106
+ const result = await specsPromise;
107
+
108
+ return result;
102
109
  }
103
110
 
104
111
  return {
105
112
  name: 'stl-starlight-astro',
106
113
  hooks: {
107
- 'astro:config:setup': async ({
114
+ 'astro:config:setup': ({
108
115
  injectRoute,
109
116
  updateConfig,
110
117
  logger: localLogger,
@@ -116,7 +123,16 @@ function stlStarlightAstroIntegration(pluginConfig: NormalizedStainlessStarlight
116
123
  const projectDir = astroConfig.root.pathname;
117
124
  astroBase = astroConfig.base;
118
125
 
119
- specLoader = await startSpecLoader(pluginConfig, logger, createCodegenDir());
126
+ specsPromise = loadAllSpecs(
127
+ pluginConfig.loadSpecs({
128
+ stainlessProject: pluginConfig.stainlessProject,
129
+ branch: pluginConfig.branch,
130
+ apiKey: pluginConfig.apiKey?.value ?? null,
131
+ excludeLanguages: pluginConfig.excludeLanguages,
132
+ logger,
133
+ createCodegenDir,
134
+ }),
135
+ );
120
136
 
121
137
  const middlewareFileBase = path.join(projectDir, 'middleware.stainless');
122
138
  const middlewareFile = ['.tsx', '.ts']
@@ -171,24 +187,28 @@ function stlStarlightAstroIntegration(pluginConfig: NormalizedStainlessStarlight
171
187
  'virtual:stl-starlight-reference-sidebars',
172
188
  async () => {
173
189
  // we know specLoader exists here
174
- const { specComposite } = await specLoader!.specPromise;
190
+ const specs = await specsPromiseResolved();
175
191
 
176
192
  const sidebars = [...sidebarConfigs.entries()]
177
193
  // produce all { id, language } combos with the attached config
178
194
  // flattens to one item per language * id combo
179
195
  .flatMap(([id, config]) =>
180
- specComposite.listAllLanguagesAndIncludeSpecs().map((res) => ({
181
- id,
182
- config,
183
- language: res.language,
184
- spec: res.spec.data.sdkJson,
185
- })),
196
+ flatSpecsList(specs)
197
+ .filter((res): res is typeof res & { language: DocsLanguage } =>
198
+ isSupportedLanguage(res.language),
199
+ )
200
+ .map((res) => ({
201
+ id,
202
+ config,
203
+ language: res.language,
204
+ sdkJson: res.sdkJson,
205
+ })),
186
206
  )
187
207
  // produce a sidebar for each
188
208
  // later we will .find() the sidebar that matches the (id, language)
189
- .map(({ id, config, language, spec }) => {
209
+ .map(({ id, config, language, sdkJson }) => {
190
210
  const configItemsBuilder = new SidebarConfigItemsBuilder(
191
- spec,
211
+ sdkJson,
192
212
  language,
193
213
  config.options,
194
214
  );
@@ -202,11 +222,11 @@ function stlStarlightAstroIntegration(pluginConfig: NormalizedStainlessStarlight
202
222
  return {
203
223
  id,
204
224
  language,
205
- // this has to run multpile times because it depends on the
225
+ // this has to run multiple times because it depends on the
206
226
  // userSidebarConfig (which is per-id) and the language
207
227
  entries: toStarlightSidebar({
208
228
  basePath: path.posix.join(astroBase, pluginConfig.basePath),
209
- spec,
229
+ spec: sdkJson,
210
230
  entries: userSidebarConfig,
211
231
  currentLanguage: language,
212
232
  }),
@@ -216,7 +236,26 @@ function stlStarlightAstroIntegration(pluginConfig: NormalizedStainlessStarlight
216
236
  return { sidebars };
217
237
  },
218
238
  ),
219
- ...specLoader.vitePlugins,
239
+ makeAsyncVirtualModPlugin<typeof VirtualManifestModule>(
240
+ 'virtual:stainless-apis-manifest',
241
+ async () => {
242
+ // this virtual module only resolves when the specs are loaded
243
+ // this prevents the SSR module from trying to read the spec file before it's generated
244
+ const specs = await specsPromiseResolved();
245
+
246
+ return {
247
+ api: {
248
+ languages: specs
249
+ .map((s) =>
250
+ s.languages
251
+ .filter(isSupportedLanguage)
252
+ .map((lang) => ({ language: lang, sdkJSONFilePath: s.filePath })),
253
+ )
254
+ .flat(),
255
+ },
256
+ };
257
+ },
258
+ ),
220
259
  {
221
260
  name: 'stl-starlight-vite',
222
261
  // TODO: eventually - re-add support for watching local input changes (eg. reloading when OAS/config files change)
@@ -269,10 +308,10 @@ function stlStarlightAstroIntegration(pluginConfig: NormalizedStainlessStarlight
269
308
  };
270
309
  await writeFile(path.join(stainlessDir, 'stl-manifest.json'), JSON.stringify(manifest, null, 2));
271
310
 
272
- const specComposite = await resolveSpecs();
311
+ const loadedSpecs = await specsPromiseResolved();
273
312
 
274
313
  await buildAlgoliaIndex({
275
- specComposite,
314
+ loadedSpecs,
276
315
  logger,
277
316
  });
278
317
 
@@ -283,7 +322,7 @@ function stlStarlightAstroIntegration(pluginConfig: NormalizedStainlessStarlight
283
322
  // about the missing method and provide links to SDKs where it is available.
284
323
 
285
324
  // TODO: (multi-spec) support multiple specs
286
- const spec = specComposite.listUniqueSpecs()[0]!.data.sdkJson;
325
+ const spec = loadedSpecs[0]!.sdkJson;
287
326
 
288
327
  const missingRoutes = generateMissingRouteList({
289
328
  spec,
@@ -338,18 +377,17 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
338
377
  addIntegration(react());
339
378
  }
340
379
 
341
- // TODO: (multi-spec) re-add this? not strictly necessary to merge
342
- // if ('apiKey' in config.specInputs) {
343
- // if (!config.specInputs.apiKey) {
344
- // logger.info(`Stainless credentials not loaded`);
345
- // } else if (config.specInputs.apiKey.source === 'explicit-config') {
346
- // logger.info(`Stainless credentials loaded from user config`);
347
- // } else if (config.specInputs.apiKey.source === 'environment-variable') {
348
- // logger.info('Stainless credentials loaded from `STAINLESS_API_KEY` environment variable');
349
- // } else if (config.specInputs.apiKey.source === 'cli') {
350
- // logger.info('Stainless credentials loaded from `stl` CLI');
351
- // }
352
- // })
380
+ if ('apiKey' in config) {
381
+ if (!config.apiKey) {
382
+ logger.info(`Stainless credentials not loaded`);
383
+ } else if (config.apiKey.source === 'explicit-config') {
384
+ logger.info(`Stainless credentials loaded from user config`);
385
+ } else if (config.apiKey.source === 'environment-variable') {
386
+ logger.info('Stainless credentials loaded from `STAINLESS_API_KEY` environment variable');
387
+ } else if (config.apiKey.source === 'cli') {
388
+ logger.info('Stainless credentials loaded from `stl` CLI');
389
+ }
390
+ }
353
391
 
354
392
  if (starlightConfig.sidebar) {
355
393
  // for pagination (https://starlight.astro.build/reference/configuration/#pagination) to work correctly
@@ -394,5 +432,3 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
394
432
  // Additional exports we want for Stainless <-> docs integration.
395
433
  export { parseStainlessPath } from '@stainless-api/docs-ui/routing';
396
434
  export { renderMarkdown } from '@stainless-api/docs-ui/markdown';
397
-
398
- export { resolveSpec } from './specs/inputResolver';
@@ -5,13 +5,15 @@ import { existsSync, readFileSync } from 'fs';
5
5
  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
- import { bold } from '../shared/terminalUtils';
9
- import { resolveSpec, SpecInputResolver } from './specs/inputResolver';
10
- import { specCache, SpecCacheResult } from './specs/generateSpec';
11
8
 
12
- export type LanguageGenerateQuery = {
13
- mode: 'exclude' | 'only';
14
- list: DocsLanguage[];
9
+ import { defaultSpecLoader } from './specs/defaultSpecLoader';
10
+ import { SpecLoaderFn } from './specs/utils';
11
+
12
+ type ApiKeySource = 'explicit-config' | 'environment-variable' | 'cli';
13
+
14
+ type LoadedApiKey = {
15
+ value: string;
16
+ source: ApiKeySource;
15
17
  };
16
18
 
17
19
  type AstroCommand = 'dev' | 'build' | 'preview' | 'sync';
@@ -50,17 +52,9 @@ export type StainlessStarlightUserConfig = {
50
52
  stainlessProject: string;
51
53
 
52
54
  /**
53
- * Powerful configuration options for customized use cases
55
+ * Optional function to provide your own loader for API reference data.
54
56
  */
55
- advanced?: {
56
- /**
57
- *
58
- * More advanced replacement for versions, basePath, and excludeLanguages.
59
- */
60
- overrideSpecs?: {
61
- specs: SpecInputResolver[];
62
- };
63
- };
57
+ loadSpecs?: SpecLoaderFn;
64
58
 
65
59
  /**
66
60
  * Optional list of versions to render in the API reference.
@@ -193,33 +187,6 @@ export type StainlessStarlightUserConfig = {
193
187
 
194
188
  export type SomeStainlessStarlightUserConfig = StainlessStarlightUserConfig;
195
189
 
196
- function resolvePath(inputPath: string) {
197
- return path.resolve(process.cwd(), inputPath);
198
- }
199
-
200
- function getLocalFilePaths() {
201
- // eslint-disable-next-line turbo/no-undeclared-env-vars
202
- const oasPath = process.env.OPENAPI_PATH;
203
- // eslint-disable-next-line turbo/no-undeclared-env-vars
204
- const configPath = process.env.STAINLESS_CONFIG_PATH;
205
-
206
- if (!oasPath || !configPath) {
207
- return null;
208
- }
209
-
210
- return {
211
- oasPath: resolvePath(oasPath),
212
- configPath: resolvePath(configPath),
213
- };
214
- }
215
-
216
- type ApiKeySource = 'explicit-config' | 'environment-variable' | 'cli';
217
-
218
- export type LoadedApiKey = {
219
- value: string;
220
- source: ApiKeySource;
221
- };
222
-
223
190
  function parseAuthJson(authJsonStr: string) {
224
191
  let json: unknown;
225
192
  try {
@@ -281,87 +248,6 @@ type AstroOptions = {
281
248
  base: string;
282
249
  };
283
250
 
284
- type ResolvedAPIConfigEntry = {
285
- loadSpecs: () => Promise<SpecCacheResult[]>;
286
- };
287
-
288
- function makeSimpleAPIConfig(partial: SomeStainlessStarlightUserConfig): ResolvedAPIConfigEntry {
289
- if (!('stainlessProject' in partial)) {
290
- throw new Error('You must provide a stainlessProject when using Stainless Starlight');
291
- }
292
-
293
- const excludedLanguages = partial.excludeLanguages ?? [];
294
-
295
- const apiKey = loadApiKey(partial.apiKey);
296
-
297
- function getResolver() {
298
- const localFilePaths = getLocalFilePaths();
299
- if (localFilePaths) {
300
- return resolveSpec.fromFiles({
301
- oasPath: localFilePaths.oasPath,
302
- configPath: localFilePaths.configPath,
303
- languageOverrides: {
304
- mode: 'exclude',
305
- list: excludedLanguages,
306
- },
307
- stainlessProject: partial.stainlessProject,
308
- });
309
- }
310
-
311
- if (!apiKey) {
312
- throw new Error(
313
- [
314
- bold(
315
- 'No Stainless credentials found. Please choose one of the following options to authenticate with Stainless:',
316
- ),
317
- '- Run `stl auth login` to authenticate via the Stainless CLI',
318
- '- Provide a Stainless API key via the `STAINLESS_API_KEY` environment variable (eg. in a .env file)',
319
- '- Set the `apiKey` option in the Stainless Docs config',
320
- ].join('\n'),
321
- );
322
- }
323
- return resolveSpec.fromStainlessApi({
324
- stainlessProject: partial.stainlessProject,
325
- branch: partial.versions?.[0]?.branch ?? 'main',
326
- apiKey: apiKey,
327
- languageOverrides: {
328
- mode: 'exclude',
329
- list: excludedLanguages,
330
- },
331
- });
332
- }
333
-
334
- const resolver = getResolver();
335
-
336
- return {
337
- loadSpecs: async function loadSpecs() {
338
- const inputs = await resolver.resolve({ apiKey });
339
-
340
- const result = await specCache.get(inputs);
341
-
342
- return [result];
343
- },
344
- };
345
- }
346
-
347
- function loadAPIConfig(partial: SomeStainlessStarlightUserConfig): ResolvedAPIConfigEntry {
348
- if (partial.advanced?.overrideSpecs) {
349
- const overrides = partial.advanced.overrideSpecs;
350
- const apiKey = loadApiKey(partial.apiKey);
351
- return {
352
- loadSpecs: async function loadSpecs() {
353
- const specsPromises = overrides.specs.map(async (spec) => {
354
- const inputs = await spec.resolve({ apiKey });
355
- return await specCache.get(inputs);
356
- });
357
-
358
- return await Promise.all(specsPromises);
359
- },
360
- };
361
- }
362
- return makeSimpleAPIConfig(partial);
363
- }
364
-
365
251
  function normalizeConfig(partial: SomeStainlessStarlightUserConfig, astroOptions: AstroOptions) {
366
252
  const configWithDefaults = {
367
253
  basePath: partial.basePath ?? '/api',
@@ -399,14 +285,12 @@ function normalizeConfig(partial: SomeStainlessStarlightUserConfig, astroOptions
399
285
  description: partial.llmsTxt?.description ?? null,
400
286
  detailThreshold: partial.llmsTxt?.detailThreshold ?? 2000,
401
287
  },
288
+ apiKey: loadApiKey(partial.apiKey),
289
+ loadSpecs: partial.loadSpecs ?? defaultSpecLoader,
290
+ branch: partial.versions?.[0]?.branch ?? 'main',
402
291
  };
403
292
 
404
- const api = loadAPIConfig(partial);
405
-
406
- return {
407
- ...configWithDefaults,
408
- api,
409
- };
293
+ return configWithDefaults;
410
294
  }
411
295
 
412
296
  export type NormalizedStainlessStarlightConfig = ReturnType<typeof normalizeConfig>;