@stainless-api/docs 1.0.0-beta.141 → 1.0.0-beta.143

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,26 @@
1
1
  # @stainless-api/docs
2
2
 
3
+ ## 1.0.0-beta.143
4
+
5
+ ### Minor Changes
6
+
7
+ - d358f02: Bugfix
8
+ - d358f02: Bug fixes + new API to override package versions etc.
9
+
10
+ ## 1.0.0-beta.142
11
+
12
+ ### Minor Changes
13
+
14
+ - 28bb924: updated API for SDKJSON
15
+
16
+ ### Patch Changes
17
+
18
+ - 65c8fa0: Uses CSS for sidebar http icons instead of inline svg
19
+ - Updated dependencies [65c8fa0]
20
+ - Updated dependencies [28bb924]
21
+ - @stainless-api/docs-ui@1.0.0-beta.98
22
+ - @stainless-api/docs-search@1.0.0-beta.52
23
+
3
24
  ## 1.0.0-beta.141
4
25
 
5
26
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stainless-api/docs",
3
- "version": "1.0.0-beta.141",
3
+ "version": "1.0.0-beta.143",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -76,8 +76,8 @@
76
76
  "remend": "^1.3.0",
77
77
  "shiki": "^4.0.2",
78
78
  "unified": "^11.0.5",
79
- "@stainless-api/docs-search": "1.0.0-beta.51",
80
- "@stainless-api/docs-ui": "1.0.0-beta.97",
79
+ "@stainless-api/docs-search": "1.0.0-beta.52",
80
+ "@stainless-api/docs-ui": "1.0.0-beta.98",
81
81
  "@stainless-api/ui-primitives": "1.0.0-beta.55",
82
82
  "@stainless/sdk-json": "^0.1.0-beta.11"
83
83
  },
@@ -92,7 +92,7 @@
92
92
  "react": "^19.2.6",
93
93
  "react-dom": "^19.2.6",
94
94
  "typescript": "6.0.3",
95
- "vite": "^7.3.2",
95
+ "vite": "^7.3.3",
96
96
  "vitest": "^4.1.5",
97
97
  "zod": "^4.4.3",
98
98
  "@stainless/eslint-config": "0.1.0-beta.2"
package/plugin/index.ts CHANGED
@@ -39,6 +39,7 @@ import { buildAlgoliaIndex } from './buildAlgoliaIndex';
39
39
  import { flatSpecsList, loadAllSpecs, LoadedSpecs } from './specs/utils';
40
40
 
41
41
  export { generateAPILink } from './generateAPIReferenceLink';
42
+ export { fileSystemSDKJSONLoader } from './specs/fileSystemSDKJSONLoader';
42
43
  export type { ReferenceSidebarConfigItem };
43
44
 
44
45
  config({
@@ -124,7 +125,7 @@ function stlStarlightAstroIntegration(pluginConfig: NormalizedStainlessStarlight
124
125
  astroBase = astroConfig.base;
125
126
 
126
127
  specsPromise = loadAllSpecs(
127
- pluginConfig.loadSpecs({
128
+ pluginConfig.loadSDKJSONFiles({
128
129
  stainlessProject: pluginConfig.stainlessProject,
129
130
  branch: pluginConfig.branch,
130
131
  apiKey: pluginConfig.apiKey?.value ?? null,
@@ -6,8 +6,8 @@ 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
 
9
- import { defaultSpecLoader } from './specs/defaultSpecLoader';
10
- import { SpecLoaderFn } from './specs/utils';
9
+ import { defaultSDKJSONLoader } from './specs/defaultSDKJSONLoader';
10
+ import { SDKJSONFilesLoaderFn as SDKJSONFilesLoaderFn } from './specs/utils';
11
11
 
12
12
  type ApiKeySource = 'explicit-config' | 'environment-variable' | 'cli';
13
13
 
@@ -54,7 +54,7 @@ export type StainlessStarlightUserConfig = {
54
54
  /**
55
55
  * Optional function to provide your own loader for API reference data.
56
56
  */
57
- loadSpecs?: SpecLoaderFn;
57
+ loadSDKJSONFiles?: SDKJSONFilesLoaderFn;
58
58
 
59
59
  /**
60
60
  * Optional list of versions to render in the API reference.
@@ -286,7 +286,7 @@ function normalizeConfig(partial: SomeStainlessStarlightUserConfig, astroOptions
286
286
  detailThreshold: partial.llmsTxt?.detailThreshold ?? 2000,
287
287
  },
288
288
  apiKey: loadApiKey(partial.apiKey),
289
- loadSpecs: partial.loadSpecs ?? defaultSpecLoader,
289
+ loadSDKJSONFiles: partial.loadSDKJSONFiles ?? defaultSDKJSONLoader,
290
290
  branch: partial.versions?.[0]?.branch ?? 'main',
291
291
  };
292
292
 
@@ -128,7 +128,6 @@ export type SpecMetadata = [
128
128
  {
129
129
  repo_url?: string;
130
130
  code_url?: string;
131
- package_title?: string;
132
131
  version?: string;
133
132
  install?: string;
134
133
  },
@@ -1,11 +1,12 @@
1
1
  import path from 'path';
2
- import type { SpecLoaderFn, SpecLoaderParams } from './utils';
2
+ import type { SDKJSONFilesLoaderFn, SDKJSONFilesLoaderParams } from './utils';
3
+ import { sdkJSONCacheReaderWriter } from './utils';
3
4
  import Stainless, { APIError } from '@stainless-api/sdk';
4
- import { DocsLanguage } from '@stainless-api/docs-ui/routing';
5
+ import { DocsLanguage, isSupportedLanguage } from '@stainless-api/docs-ui/routing';
5
6
  import { bold } from '../../shared/terminalUtils';
6
- import { mkdir, readdir, readFile, rm, writeFile } from 'fs/promises';
7
+ import { mkdir, readdir, readFile, rm } from 'fs/promises';
7
8
  import { generateSpecFromStrings, previewWorkerCode } from '@stainless/sdk-json/spec';
8
- import { Spec, SpecLanguage } from '@stainless/sdk-json';
9
+ import { Spec } from '@stainless/sdk-json';
9
10
  import crypto from 'crypto';
10
11
 
11
12
  function resolvePath(inputPath: string) {
@@ -44,7 +45,7 @@ function redactApiKey(apiKey: string) {
44
45
  .join('');
45
46
  }
46
47
 
47
- async function loadInputs({ apiKey, logger, stainlessProject, branch }: SpecLoaderParams) {
48
+ async function loadInputs({ apiKey, logger, stainlessProject, branch }: SDKJSONFilesLoaderParams) {
48
49
  const localFilePaths = getLocalFilePaths();
49
50
 
50
51
  if (localFilePaths) {
@@ -115,13 +116,11 @@ async function loadInputs({ apiKey, logger, stainlessProject, branch }: SpecLoad
115
116
  }
116
117
  }
117
118
 
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;
119
+ function getLanguagesFromSDKJSON(spec: Spec) {
120
+ if (spec.docs?.languages) {
121
+ return spec.docs.languages;
124
122
  }
123
+ return Object.keys(spec.decls).filter(isSupportedLanguage);
125
124
  }
126
125
 
127
126
  async function cleanupDirectory(directory: string, filesToKeep: string[]) {
@@ -133,12 +132,12 @@ async function cleanupDirectory(directory: string, filesToKeep: string[]) {
133
132
  };
134
133
  }
135
134
 
136
- export const defaultSpecLoader: SpecLoaderFn = async (params) => {
135
+ export const defaultSDKJSONLoader: SDKJSONFilesLoaderFn = async (params) => {
137
136
  const { createCodegenDir } = params;
138
137
 
139
138
  const inputs = await loadInputs(params);
140
139
 
141
- const specsDirectory = path.join(createCodegenDir().pathname, 'specs2');
140
+ const specsDirectory = path.join(createCodegenDir().pathname, 'sdk_json_cache');
142
141
  await mkdir(specsDirectory, { recursive: true });
143
142
 
144
143
  const fileName =
@@ -150,20 +149,28 @@ export const defaultSpecLoader: SpecLoaderFn = async (params) => {
150
149
 
151
150
  const filePath = path.join(specsDirectory, fileName);
152
151
 
153
- const cachedSpec = await maybeLoadJSONFile<{ languages: SpecLanguage[]; sdkJson: Spec }>(filePath);
152
+ const cachedSpec = await (async () => {
153
+ try {
154
+ return await sdkJSONCacheReaderWriter.readFile(filePath);
155
+ } catch {
156
+ return null;
157
+ }
158
+ })();
159
+
154
160
  // skip generation since we already have a cached spec
155
161
  if (cachedSpec) {
162
+ const languages = getLanguagesFromSDKJSON(cachedSpec);
156
163
  params.logger.info(`Loaded cached spec: ${fileName}`);
157
164
  return [
158
165
  {
159
166
  filePath,
160
- languages: cachedSpec.languages,
161
- sdkJson: cachedSpec.sdkJson,
167
+ languages,
168
+ sdkJson: cachedSpec,
162
169
  },
163
170
  ];
164
171
  }
165
172
 
166
- const result = await generateSpecFromStrings({
173
+ const { sdkJson } = await generateSpecFromStrings({
167
174
  oasStr: inputs.oasStr,
168
175
  configStr: inputs.configStr,
169
176
  languageOverrides: {
@@ -174,9 +181,11 @@ export const defaultSpecLoader: SpecLoaderFn = async (params) => {
174
181
  stainlessProject: params.stainlessProject,
175
182
  });
176
183
 
177
- await writeFile(filePath, JSON.stringify(result), 'utf8');
178
- params.logger.info(`Generated: ${fileName}`);
184
+ const languages = getLanguagesFromSDKJSON(sdkJson);
179
185
 
186
+ await sdkJSONCacheReaderWriter.writeFile(filePath, sdkJson);
187
+
188
+ params.logger.info(`Generated: ${fileName}`);
180
189
  const { deletedCount } = await cleanupDirectory(specsDirectory, [fileName]);
181
190
  if (deletedCount > 0) {
182
191
  params.logger.info(`Cleaned up ${deletedCount} unused spec file(s)`);
@@ -185,8 +194,8 @@ export const defaultSpecLoader: SpecLoaderFn = async (params) => {
185
194
  return [
186
195
  {
187
196
  filePath,
188
- languages: result.languages.filter((language) => language !== 'sql' && language !== 'openapi'),
189
- sdkJson: result.sdkJson,
197
+ languages,
198
+ sdkJson,
190
199
  },
191
200
  ];
192
201
  };
@@ -1,20 +1,10 @@
1
1
  import { readFile } from 'fs/promises';
2
2
 
3
3
  import { api } from 'virtual:stainless-apis-manifest';
4
- import type { SpecWithAuth } from '@stainless/sdk-json/spec';
4
+ import { Spec } from '@stainless/sdk-json';
5
5
  import { DocsLanguage } from '@stainless-api/docs-ui/routing';
6
6
 
7
- const cachedSpecWithAuth: Record<string, SpecWithAuth> = {};
8
-
9
- async function getSpecWithAuthInSSR(filePath: string) {
10
- if (cachedSpecWithAuth[filePath]) {
11
- return cachedSpecWithAuth[filePath];
12
- }
13
- const specStr = await readFile(filePath, 'utf8');
14
- const json = JSON.parse(specStr) as SpecWithAuth;
15
- cachedSpecWithAuth[filePath] = json;
16
- return json;
17
- }
7
+ const cachedSpecs: Record<string, Spec> = {};
18
8
 
19
9
  export async function getSDKJSONInSSR(language: DocsLanguage) {
20
10
  const filePath = api.languages.find((l) => l.language === language)?.sdkJSONFilePath;
@@ -22,6 +12,12 @@ export async function getSDKJSONInSSR(language: DocsLanguage) {
22
12
  throw new Error(`No SDK JSON file path for language: ${language}`);
23
13
  }
24
14
 
25
- const specWithAuth = await getSpecWithAuthInSSR(filePath);
26
- return specWithAuth.sdkJson;
15
+ if (cachedSpecs[filePath]) {
16
+ return cachedSpecs[filePath];
17
+ }
18
+ const specStr = await readFile(filePath, 'utf8');
19
+ const json = JSON.parse(specStr) as Spec;
20
+ cachedSpecs[filePath] = json;
21
+
22
+ return json;
27
23
  }
@@ -0,0 +1,126 @@
1
+ import { Spec, SpecLanguage } from '@stainless/sdk-json';
2
+ import { SDKJSONFilesLoaderFn, SDKJSONFilesLoaderParams, sdkJSONCacheReaderWriter } from './utils';
3
+ import { mkdir, readFile } from 'fs/promises';
4
+ import path from 'path';
5
+ import { generateSpecFromStrings } from '@stainless/sdk-json/spec';
6
+
7
+ interface GenerateSDKJSONOptions {
8
+ /** Raw OpenAPI spec contents (JSON or YAML). */
9
+ spec: string;
10
+ /** Raw Stainless config contents (YAML). */
11
+ config: string;
12
+ /** Language to build the SDK JSON spec for. */
13
+ language: SpecLanguage;
14
+ /** The name of the project. */
15
+ stainlessProject: string;
16
+ }
17
+
18
+ type GenerateSDKJSONFn<T> = (options: GenerateSDKJSONOptions) => Promise<{ spec: T }>;
19
+
20
+ type NonHttpSpecLanguage = Exclude<SpecLanguage, 'http'>;
21
+
22
+ type LibraryInformationOverride = Partial<{
23
+ [key in NonHttpSpecLanguage]: {
24
+ repo_url?: string;
25
+ code_url?: string;
26
+ version?: string;
27
+ install?: string;
28
+ };
29
+ }>;
30
+
31
+ type FileSystemSpecLoaderParams<T> = {
32
+ /**
33
+ * The path to the OpenAPI spec file.
34
+ *
35
+ */
36
+ specPath: string;
37
+ /**
38
+ * The path to your Stainless config file.
39
+ */
40
+ configFilePath: string;
41
+ /**
42
+ * The function used to generate your SDKJSON.
43
+ */
44
+ generateSDKJSON?: GenerateSDKJSONFn<T>;
45
+ /**
46
+ * The languages for which you want to render documentation.
47
+ */
48
+ languages: SpecLanguage[];
49
+ /**
50
+ * A key:value map of languages to overrides. Used to manually set things like the install command or SDK version.
51
+ */
52
+ override?: LibraryInformationOverride;
53
+ };
54
+
55
+ const defaultGenerateSDKJSON: GenerateSDKJSONFn<Spec> = async ({
56
+ config,
57
+ spec,
58
+ language,
59
+ stainlessProject,
60
+ }) => {
61
+ const { sdkJson } = await generateSpecFromStrings({
62
+ oasStr: spec,
63
+ configStr: config,
64
+ languageOverrides: {
65
+ mode: 'only',
66
+ list: [language],
67
+ },
68
+ versionInfo: null,
69
+ stainlessProject,
70
+ });
71
+
72
+ return { spec: sdkJson };
73
+ };
74
+
75
+ export function fileSystemSDKJSONLoader<T>({
76
+ specPath,
77
+ configFilePath,
78
+ generateSDKJSON,
79
+ languages,
80
+ override,
81
+ }: FileSystemSpecLoaderParams<T>): SDKJSONFilesLoaderFn {
82
+ const generateFn = generateSDKJSON ?? defaultGenerateSDKJSON;
83
+ return async function fileSystemSpecLoader(opts: SDKJSONFilesLoaderParams) {
84
+ const { createCodegenDir, logger } = opts;
85
+ const [spec, config] = await Promise.all([readFile(specPath, 'utf8'), readFile(configFilePath, 'utf8')]);
86
+
87
+ const specsDirectory = path.join(createCodegenDir().pathname, 'fs_spec_loader_specs');
88
+ await mkdir(specsDirectory, { recursive: true });
89
+
90
+ const r = languages.map(async (language) => {
91
+ const generateResult = await generateFn({
92
+ spec,
93
+ config,
94
+ language,
95
+ stainlessProject: opts.stainlessProject,
96
+ });
97
+
98
+ // type casting here is a little weird
99
+ // it prevents type errors from slightly incompatible SDKJSON types (since generateSDKJSON comes from the user)
100
+ const sdkJson = generateResult.spec as unknown as Spec;
101
+
102
+ if (override && language !== 'http' && override[language]) {
103
+ sdkJson.metadata[language] = {
104
+ ...sdkJson.metadata[language],
105
+ ...override[language],
106
+ };
107
+ }
108
+
109
+ const filePath = path.join(specsDirectory, `${language}.json`);
110
+
111
+ await sdkJSONCacheReaderWriter.writeFile(filePath, sdkJson);
112
+
113
+ logger.info(`Loaded SDKJSON for ${language} to ${filePath}`);
114
+
115
+ return {
116
+ filePath: filePath,
117
+ languages: [language],
118
+ sdkJson,
119
+ };
120
+ });
121
+
122
+ const results = await Promise.all(r);
123
+
124
+ return results;
125
+ };
126
+ }
@@ -1,11 +1,11 @@
1
1
  import { Spec } from '@stainless/sdk-json';
2
- import { readFile } from 'fs/promises';
2
+ import { readFile, writeFile } from 'fs/promises';
3
3
  import { DocsLanguage } from '@stainless-api/docs-ui/routing';
4
4
  import { AstroIntegrationLogger } from 'astro';
5
5
 
6
6
  type PossibleLanguage = NonNullable<NonNullable<NonNullable<Spec['docs']>['languages']>[number]>;
7
7
 
8
- export type SpecLoaderParams = {
8
+ export type SDKJSONFilesLoaderParams = {
9
9
  /**
10
10
  * The slug of your Stainless project.
11
11
  */
@@ -32,7 +32,7 @@ export type SpecLoaderParams = {
32
32
  createCodegenDir: () => URL;
33
33
  };
34
34
 
35
- type SpecLoaderResult = {
35
+ type SDKJSONFilesLoaderResult = {
36
36
  /**
37
37
  * The file path to the loaded spec. The spec MUST be written to a path on disk.
38
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.
@@ -49,7 +49,7 @@ type SpecLoaderResult = {
49
49
  sdkJson?: Spec;
50
50
  };
51
51
 
52
- export type SpecLoaderFn = (opts: SpecLoaderParams) => Promise<SpecLoaderResult[]>;
52
+ export type SDKJSONFilesLoaderFn = (opts: SDKJSONFilesLoaderParams) => Promise<SDKJSONFilesLoaderResult[]>;
53
53
 
54
54
  async function readSpecFromFile(filePath: string) {
55
55
  const txt = await readFile(filePath, 'utf8');
@@ -57,7 +57,7 @@ async function readSpecFromFile(filePath: string) {
57
57
  return json;
58
58
  }
59
59
 
60
- export async function loadAllSpecs(specLoaderResultsPromise: Promise<SpecLoaderResult[]>) {
60
+ export async function loadAllSpecs(specLoaderResultsPromise: Promise<SDKJSONFilesLoaderResult[]>) {
61
61
  const specLoaderResults = await specLoaderResultsPromise;
62
62
  const specs = await Promise.all(
63
63
  specLoaderResults.map(async (result) => {
@@ -84,3 +84,17 @@ export function flatSpecsList(specs: LoadedSpecs) {
84
84
  )
85
85
  .flat();
86
86
  }
87
+
88
+ function typedReaderWriter<T>() {
89
+ return {
90
+ async readFile(filePath: string) {
91
+ const fileContents = await readFile(filePath, 'utf8');
92
+ return JSON.parse(fileContents) as T;
93
+ },
94
+ async writeFile(filePath: string, data: T) {
95
+ await writeFile(filePath, JSON.stringify(data), 'utf8');
96
+ },
97
+ };
98
+ }
99
+
100
+ export const sdkJSONCacheReaderWriter = typedReaderWriter<Spec>();
@@ -1,9 +1,7 @@
1
1
  import { StlSidebarEntry } from '@stainless-api/docs-ui/components';
2
2
  import { SidebarEntry } from '../pagination/util';
3
3
  import { ReactNode } from 'react';
4
- import { Badge, getHttpMethod } from '@stainless-api/ui-primitives';
5
- import { FunctionIcon } from '@stainless-api/ui-primitives/icons';
6
- import { BracesIcon } from 'lucide-react';
4
+ import { getHttpMethod } from '@stainless-api/ui-primitives';
7
5
 
8
6
  function getIcon(entry: SidebarEntry): ReactNode | undefined {
9
7
  if (entry.type !== 'link') {
@@ -11,25 +9,34 @@ function getIcon(entry: SidebarEntry): ReactNode | undefined {
11
9
  }
12
10
  const methodAttr = entry.attrs['data-stldocs-method'];
13
11
  const httpMethod = getHttpMethod(methodAttr);
12
+ const classes = `stl-ui-badge stl-ui-badge--size-sm stl-sidebar-icon`;
13
+
14
14
  if (httpMethod) {
15
- return <Badge.HTTP method={httpMethod} iconOnly size="sm" />;
15
+ const methodClass = `stl-ui-badge--http-${httpMethod.toLowerCase()}`;
16
+ return (
17
+ <span className={`${classes} stl-ui-badge--http ${methodClass}`} role="img" aria-label={httpMethod} />
18
+ );
16
19
  }
17
20
 
18
21
  // special handling for the webhooks resource overview page
19
22
  if (entry.attrs['data-stldocs-overview'] === 'webhooks') {
20
23
  return (
21
- <Badge size="sm" icon={<BracesIcon />} intent="info">
22
- {''}
23
- </Badge>
24
+ <span
25
+ className={`${classes} stl-ui-badge--intent-info stl-sidebar-icon--braces`}
26
+ role="img"
27
+ aria-label="Webhook"
28
+ />
24
29
  );
25
30
  }
26
31
 
27
32
  // Support empty string as method to show generic "Function" badge
28
33
  else if (methodAttr === '') {
29
34
  return (
30
- <Badge size="sm" icon={<FunctionIcon />} intent="info">
31
- {''}
32
- </Badge>
35
+ <span
36
+ className={`${classes} stl-ui-badge--intent-info stl-sidebar-icon--function`}
37
+ role="img"
38
+ aria-label="Method"
39
+ />
33
40
  );
34
41
  }
35
42
  return undefined;