@stainless-api/docs 0.1.0-beta.86 → 0.1.0-beta.88
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 +26 -0
- package/eslint-suppressions.json +2 -22
- package/package.json +5 -6
- package/playground-virtual-modules.d.ts +96 -0
- package/plugin/buildAlgoliaIndex.ts +9 -7
- package/plugin/components/SDKSelect.astro +8 -12
- package/plugin/helpers/getDocsLanguages.ts +9 -0
- package/plugin/index.ts +111 -135
- package/plugin/loadPluginConfig.ts +36 -52
- package/plugin/referencePlaceholderUtils.ts +17 -14
- package/plugin/replaceSidebarPlaceholderMiddleware.ts +33 -29
- package/plugin/routes/Docs.astro +2 -2
- package/plugin/routes/DocsStatic.astro +2 -2
- package/plugin/routes/Overview.astro +2 -2
- package/plugin/routes/markdown.ts +3 -3
- package/plugin/{cms → sidebar-utils}/sidebar-builder.ts +3 -16
- package/plugin/specs/fetchSpecSSR.ts +21 -0
- package/plugin/specs/generateSpec.ts +50 -0
- package/plugin/specs/index.ts +238 -0
- package/shared/virtualModule.ts +54 -1
- package/stl-docs/components/sidebars/convertAstroSidebarToStl.tsx +11 -1
- package/stl-docs/loadStlDocsConfig.ts +1 -1
- package/virtual-module.d.ts +10 -98
- package/plugin/cms/client.ts +0 -62
- package/plugin/cms/generate-spec.ts +0 -112
- package/plugin/cms/server.ts +0 -364
- /package/plugin/{cms → specs}/worker.ts +0 -0
package/plugin/cms/server.ts
DELETED
|
@@ -1,364 +0,0 @@
|
|
|
1
|
-
import type { AstroIntegrationLogger } from 'astro';
|
|
2
|
-
import { createServer, IncomingMessage } from 'http';
|
|
3
|
-
import { readFile } from 'fs/promises';
|
|
4
|
-
|
|
5
|
-
import type * as SDKJSON from '@stainless/sdk-json';
|
|
6
|
-
import { parseRoute, type DocsLanguage } from '@stainless-api/docs-ui/routing';
|
|
7
|
-
import Stainless, { APIError } from '@stainless-api/sdk';
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
toStarlightSidebar,
|
|
11
|
-
type GeneratedSidebarConfig,
|
|
12
|
-
SidebarConfigItemsBuilder,
|
|
13
|
-
} from './sidebar-builder';
|
|
14
|
-
import type { VersionUserConfig } from '../loadPluginConfig';
|
|
15
|
-
import { bold } from '../../shared/terminalUtils';
|
|
16
|
-
import { generateSpecFromStrings } from './generate-spec';
|
|
17
|
-
export type InputFilePaths = {
|
|
18
|
-
oasPath?: string;
|
|
19
|
-
configPath?: string;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
async function versionInfo(project: string, apiKey: string): Promise<Record<DocsLanguage, string>> {
|
|
23
|
-
const data = await fetch(`https://api.stainless.com/api/projects/${project}/package-versions`, {
|
|
24
|
-
headers: { Authorization: `Bearer ${apiKey}` },
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
const content = await data.text();
|
|
28
|
-
return JSON.parse(content) as Record<DocsLanguage, string>;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function redactApiKey(apiKey: string) {
|
|
32
|
-
return apiKey
|
|
33
|
-
.split('')
|
|
34
|
-
.map((char, index) => (index < 10 ? char : '*'))
|
|
35
|
-
.join('');
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export type Auth = Array<{
|
|
39
|
-
type: 'http_bearer' | 'query' | 'header' | 'oauth2' | 'http_basic' | 'http_digest';
|
|
40
|
-
description?: string;
|
|
41
|
-
name: string;
|
|
42
|
-
title: string;
|
|
43
|
-
header: string | undefined;
|
|
44
|
-
example: string | undefined;
|
|
45
|
-
opts: {
|
|
46
|
-
type: 'string' | 'number' | 'boolean' | 'null' | 'integer';
|
|
47
|
-
nullable: boolean;
|
|
48
|
-
description?: string | undefined;
|
|
49
|
-
example?: unknown;
|
|
50
|
-
default?: unknown;
|
|
51
|
-
read_env?: string | undefined;
|
|
52
|
-
auth?:
|
|
53
|
-
| {
|
|
54
|
-
security_scheme: string;
|
|
55
|
-
role?: 'value' | 'password' | 'username' | 'client_id' | 'client_secret' | undefined;
|
|
56
|
-
}
|
|
57
|
-
| undefined;
|
|
58
|
-
name: string;
|
|
59
|
-
}[];
|
|
60
|
-
}>;
|
|
61
|
-
|
|
62
|
-
type LoadedSpec = { data: SDKJSON.Spec; auth: Auth; id: string };
|
|
63
|
-
|
|
64
|
-
async function loadSpec({
|
|
65
|
-
apiKey,
|
|
66
|
-
devPaths,
|
|
67
|
-
version,
|
|
68
|
-
logger,
|
|
69
|
-
specCacheDirectory,
|
|
70
|
-
}: {
|
|
71
|
-
apiKey: string;
|
|
72
|
-
devPaths: InputFilePaths;
|
|
73
|
-
version: VersionUserConfig;
|
|
74
|
-
logger: AstroIntegrationLogger;
|
|
75
|
-
specCacheDirectory: string | null;
|
|
76
|
-
}): Promise<LoadedSpec> {
|
|
77
|
-
async function unsafeLoad(): Promise<LoadedSpec> {
|
|
78
|
-
let oasStr: string;
|
|
79
|
-
let configStr: string;
|
|
80
|
-
let versions: Record<DocsLanguage, string> | undefined;
|
|
81
|
-
|
|
82
|
-
if (devPaths.oasPath && devPaths.configPath) {
|
|
83
|
-
[oasStr, configStr] = await Promise.all([
|
|
84
|
-
readFile(devPaths.oasPath, 'utf-8'),
|
|
85
|
-
readFile(devPaths.configPath, 'utf-8'),
|
|
86
|
-
]);
|
|
87
|
-
} else {
|
|
88
|
-
const client = new Stainless({ apiKey });
|
|
89
|
-
const configs = await client.projects.configs.retrieve({
|
|
90
|
-
project: version.stainlessProject,
|
|
91
|
-
branch: version.branch,
|
|
92
|
-
include: 'openapi',
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
versions = await versionInfo(version.stainlessProject, apiKey);
|
|
96
|
-
|
|
97
|
-
const configYML = Object.values(configs)[0] as { content: any };
|
|
98
|
-
const oasJson = Object.values(configs)[1] as { content: any };
|
|
99
|
-
oasStr = oasJson['content'];
|
|
100
|
-
configStr = configYML['content'];
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const sdkJson = await generateSpecFromStrings({
|
|
104
|
-
oasStr,
|
|
105
|
-
configStr,
|
|
106
|
-
projectName: version.stainlessProject,
|
|
107
|
-
specCacheDirectory,
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
if (versions) {
|
|
111
|
-
for (const [lang, version] of Object.entries(versions)) {
|
|
112
|
-
const meta = sdkJson.data.metadata[lang as DocsLanguage];
|
|
113
|
-
if (meta?.version) meta.version = version;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const id = crypto.randomUUID();
|
|
118
|
-
|
|
119
|
-
return {
|
|
120
|
-
data: sdkJson.data,
|
|
121
|
-
auth: sdkJson.auth,
|
|
122
|
-
id,
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
try {
|
|
127
|
-
const result = await unsafeLoad();
|
|
128
|
-
return result;
|
|
129
|
-
} catch (error) {
|
|
130
|
-
logger.error(bold('Failed to fetch API reference information from Stainless:'));
|
|
131
|
-
if (error instanceof APIError && error.status >= 400 && error.status < 500) {
|
|
132
|
-
logger.error(`Requested project slug: "${version.stainlessProject}"`);
|
|
133
|
-
logger.error(`API key: "${redactApiKey(apiKey)}"`);
|
|
134
|
-
logger.error(
|
|
135
|
-
`This error can usually be corrected by re-authenticating with the Stainless. Use the CLI (stl auth login) or verify that the Stainless API key you're using can access the project mentioned above.`,
|
|
136
|
-
);
|
|
137
|
-
} else {
|
|
138
|
-
logger.error(error instanceof Error ? error.message : 'Unknown error');
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
process.exit(1);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
class Spec {
|
|
146
|
-
private specPromise: Promise<{ id: string; data: SDKJSON.Spec; auth: Auth }>;
|
|
147
|
-
private devPaths: InputFilePaths;
|
|
148
|
-
private apiKey: string;
|
|
149
|
-
private version: VersionUserConfig;
|
|
150
|
-
|
|
151
|
-
reload() {
|
|
152
|
-
this.specPromise = loadSpec({
|
|
153
|
-
specCacheDirectory: this.specCacheDirectory,
|
|
154
|
-
apiKey: this.apiKey,
|
|
155
|
-
devPaths: this.devPaths,
|
|
156
|
-
version: this.version,
|
|
157
|
-
logger: this.logger,
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
async forceGetSpec() {
|
|
162
|
-
const spec = await this.specPromise;
|
|
163
|
-
return spec;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
async getSpec(currentId: string | undefined | null) {
|
|
167
|
-
const spec = await this.specPromise;
|
|
168
|
-
|
|
169
|
-
if (currentId === spec.id) {
|
|
170
|
-
return {
|
|
171
|
-
id: spec.id,
|
|
172
|
-
data: null,
|
|
173
|
-
auth: null,
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
return spec;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
constructor(
|
|
181
|
-
apiKey: string,
|
|
182
|
-
version: VersionUserConfig,
|
|
183
|
-
devPaths: InputFilePaths,
|
|
184
|
-
private logger: AstroIntegrationLogger,
|
|
185
|
-
private specCacheDirectory: string | null,
|
|
186
|
-
) {
|
|
187
|
-
this.specPromise = loadSpec({
|
|
188
|
-
specCacheDirectory,
|
|
189
|
-
apiKey,
|
|
190
|
-
devPaths,
|
|
191
|
-
version,
|
|
192
|
-
logger,
|
|
193
|
-
});
|
|
194
|
-
this.devPaths = devPaths;
|
|
195
|
-
this.apiKey = apiKey;
|
|
196
|
-
this.version = version;
|
|
197
|
-
this.logger = logger;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
export type SpecResp = Awaited<ReturnType<Spec['getSpec']>>;
|
|
202
|
-
|
|
203
|
-
// will be necessary once we add POST methods
|
|
204
|
-
function readJsonBody<T>(req: IncomingMessage): Promise<T> {
|
|
205
|
-
return new Promise((resolve, reject) => {
|
|
206
|
-
let body = '';
|
|
207
|
-
req.on('data', (chunk) => {
|
|
208
|
-
body += chunk.toString();
|
|
209
|
-
});
|
|
210
|
-
req.on('end', () => {
|
|
211
|
-
try {
|
|
212
|
-
const data = JSON.parse(body);
|
|
213
|
-
resolve(data as T);
|
|
214
|
-
} catch (error) {
|
|
215
|
-
reject(error);
|
|
216
|
-
}
|
|
217
|
-
});
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function markCurrentItems(sidebar: SidebarEntry[], currentSlug: string) {
|
|
222
|
-
// IMPORTANT: we need to clone the sidebar to avoid mutating the original sidebar
|
|
223
|
-
const mutableSidebarInstance = structuredClone(sidebar);
|
|
224
|
-
|
|
225
|
-
function recursiveMarkCurrent(entries: SidebarEntry[]) {
|
|
226
|
-
for (const entry of entries) {
|
|
227
|
-
if (entry.type === 'link') {
|
|
228
|
-
entry.isCurrent = entry.href === currentSlug;
|
|
229
|
-
if (entry.isCurrent) {
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
if (entry.type === 'group') {
|
|
234
|
-
recursiveMarkCurrent(entry.entries);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
recursiveMarkCurrent(mutableSidebarInstance);
|
|
239
|
-
|
|
240
|
-
return mutableSidebarInstance;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
export function startDevServer({
|
|
244
|
-
port,
|
|
245
|
-
version,
|
|
246
|
-
devPaths,
|
|
247
|
-
apiKey,
|
|
248
|
-
getGeneratedSidebarConfig,
|
|
249
|
-
logger,
|
|
250
|
-
specCacheDirectory,
|
|
251
|
-
}: {
|
|
252
|
-
port: number;
|
|
253
|
-
version: VersionUserConfig;
|
|
254
|
-
devPaths: InputFilePaths;
|
|
255
|
-
apiKey: string;
|
|
256
|
-
specCacheDirectory: string | null;
|
|
257
|
-
getGeneratedSidebarConfig: (id: number) => GeneratedSidebarConfig | null;
|
|
258
|
-
logger: AstroIntegrationLogger;
|
|
259
|
-
}) {
|
|
260
|
-
const spec = new Spec(apiKey, version, devPaths, logger, specCacheDirectory);
|
|
261
|
-
|
|
262
|
-
const sidebarCache = new Map<string, SidebarEntry[]>();
|
|
263
|
-
|
|
264
|
-
const server = createServer(async (req, res) => {
|
|
265
|
-
// Add CORS headers
|
|
266
|
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
267
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
268
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
269
|
-
|
|
270
|
-
// Handle preflight requests
|
|
271
|
-
if (req.method === 'OPTIONS') {
|
|
272
|
-
res.writeHead(204);
|
|
273
|
-
res.end();
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
function respond(code: number, body: any) {
|
|
278
|
-
res.setHeader('Content-Type', 'application/json');
|
|
279
|
-
res.writeHead(code);
|
|
280
|
-
res.end(JSON.stringify(body));
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
try {
|
|
284
|
-
if (req.method === 'POST' && req.url === '/retrieve_spec') {
|
|
285
|
-
const body = await readJsonBody<{ currentId: string }>(req);
|
|
286
|
-
|
|
287
|
-
const currentSpec = await spec.getSpec(body.currentId);
|
|
288
|
-
|
|
289
|
-
return respond(200, currentSpec);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
if (req.method === 'POST' && req.url === '/build_sidebar') {
|
|
293
|
-
const currentSpec = await spec.forceGetSpec();
|
|
294
|
-
const body = await readJsonBody<{ sidebarId: number; basePath: string; currentSlug: string }>(req);
|
|
295
|
-
const sidebarId: number = body.sidebarId;
|
|
296
|
-
|
|
297
|
-
const sidebarConfig = getGeneratedSidebarConfig(sidebarId);
|
|
298
|
-
|
|
299
|
-
const sidebarGenerateOptions = sidebarConfig?.options;
|
|
300
|
-
|
|
301
|
-
const { language } = parseRoute(body.basePath, body.currentSlug);
|
|
302
|
-
|
|
303
|
-
const sidebarCacheKey = `${body.basePath}-${language}-${sidebarId}`;
|
|
304
|
-
|
|
305
|
-
const cachedSidebar = sidebarCache.get(sidebarCacheKey);
|
|
306
|
-
let starlightSidebar: SidebarEntry[] | null = null;
|
|
307
|
-
if (cachedSidebar) {
|
|
308
|
-
starlightSidebar = cachedSidebar;
|
|
309
|
-
} else {
|
|
310
|
-
const configItemsBuilder = new SidebarConfigItemsBuilder(
|
|
311
|
-
currentSpec.data,
|
|
312
|
-
language,
|
|
313
|
-
sidebarGenerateOptions,
|
|
314
|
-
);
|
|
315
|
-
|
|
316
|
-
let userSidebarConfig = configItemsBuilder.generateItems();
|
|
317
|
-
|
|
318
|
-
if (sidebarConfig && sidebarConfig.transformFn) {
|
|
319
|
-
const transformedSidebarConfig = sidebarConfig.transformFn(userSidebarConfig, language);
|
|
320
|
-
// the user may not have returned, but they may have mutated the config in place
|
|
321
|
-
// if they did return, we use that
|
|
322
|
-
if (transformedSidebarConfig) {
|
|
323
|
-
userSidebarConfig = transformedSidebarConfig;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
starlightSidebar = toStarlightSidebar({
|
|
327
|
-
basePath: body.basePath,
|
|
328
|
-
spec: currentSpec.data,
|
|
329
|
-
entries: userSidebarConfig,
|
|
330
|
-
currentLanguage: language,
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
sidebarCache.set(sidebarCacheKey, starlightSidebar);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
return respond(200, { data: markCurrentItems(starlightSidebar, body.currentSlug) });
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
return respond(404, { message: 'Not found' });
|
|
340
|
-
} catch (e) {
|
|
341
|
-
return respond(500, { message: 'Unexpected error', error: e });
|
|
342
|
-
}
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
server.listen(port, () => {
|
|
346
|
-
logger.debug(`Stainless spec server is running on port: ${port}`);
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
return {
|
|
350
|
-
invalidate: () => {
|
|
351
|
-
spec.reload();
|
|
352
|
-
sidebarCache.clear();
|
|
353
|
-
},
|
|
354
|
-
destroy: function destroy() {
|
|
355
|
-
return new Promise<void>((resolve) => {
|
|
356
|
-
server.close(() => {
|
|
357
|
-
resolve();
|
|
358
|
-
});
|
|
359
|
-
});
|
|
360
|
-
},
|
|
361
|
-
};
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
export type DevSpecServer = Awaited<ReturnType<typeof startDevServer>>;
|
|
File without changes
|