docusaurus-plugin-openapi-docs 1.0.2 → 1.0.3

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.
@@ -13,13 +13,20 @@ import sdk from "@paloaltonetworks/postman-collection";
13
13
  import Collection from "@paloaltonetworks/postman-collection";
14
14
  import chalk from "chalk";
15
15
  import fs from "fs-extra";
16
- import yaml from "js-yaml";
17
16
  import JsonRefs from "json-refs";
18
17
  import { kebabCase } from "lodash";
19
18
 
20
- import { ApiMetadata, ApiPageMetadata, InfoPageMetadata } from "../types";
19
+ import { isURL } from "../index";
20
+ import {
21
+ ApiMetadata,
22
+ ApiPageMetadata,
23
+ InfoPageMetadata,
24
+ SidebarOptions,
25
+ TagPageMetadata,
26
+ } from "../types";
21
27
  import { sampleFromSchema } from "./createExample";
22
28
  import { OpenApiObject, OpenApiObjectWithRef, TagObject } from "./types";
29
+ import { loadAndBundleSpec } from "./utils/loadAndBundleSpec";
23
30
 
24
31
  /**
25
32
  * Finds any reference objects in the OpenAPI definition and resolves them to a finalized value.
@@ -75,13 +82,45 @@ async function createPostmanCollection(
75
82
 
76
83
  type PartialPage<T> = Omit<T, "permalink" | "source" | "sourceDirName">;
77
84
 
78
- function createItems(openapiData: OpenApiObject): ApiMetadata[] {
85
+ function createItems(
86
+ openapiData: OpenApiObject,
87
+ sidebarOptions: SidebarOptions
88
+ ): ApiMetadata[] {
79
89
  // TODO: Find a better way to handle this
80
90
  let items: PartialPage<ApiMetadata>[] = [];
91
+ const infoId = kebabCase(openapiData.info.title);
92
+
93
+ if (sidebarOptions?.categoryLinkSource === "tag") {
94
+ // Only create an tag pages if categoryLinkSource set to tag.
95
+ const tags: TagObject[] = openapiData.tags ?? [];
96
+ // eslint-disable-next-line array-callback-return
97
+ tags
98
+ .filter((tag) => !tag.description?.includes("SchemaDefinition"))
99
+ // eslint-disable-next-line array-callback-return
100
+ .map((tag) => {
101
+ const description = getTagDisplayName(
102
+ tag.name!,
103
+ openapiData.tags ?? []
104
+ );
105
+ const tagId = kebabCase(tag.name);
106
+ const tagPage: PartialPage<TagPageMetadata> = {
107
+ type: "tag",
108
+ id: tagId,
109
+ unversionedId: tagId,
110
+ title: description ?? "",
111
+ description: description ?? "",
112
+ slug: "/" + tagId,
113
+ frontMatter: {},
114
+ tag: {
115
+ ...tag,
116
+ },
117
+ };
118
+ items.push(tagPage);
119
+ });
120
+ }
81
121
 
82
- // Only create an info page if we have a description.
83
122
  if (openapiData.info.description) {
84
- const infoId = kebabCase(openapiData.info.title);
123
+ // Only create an info page if we have a description.
85
124
  const infoPage: PartialPage<InfoPageMetadata> = {
86
125
  type: "info",
87
126
  id: infoId,
@@ -90,6 +129,7 @@ function createItems(openapiData: OpenApiObject): ApiMetadata[] {
90
129
  description: openapiData.info.description,
91
130
  slug: "/" + infoId,
92
131
  frontMatter: {},
132
+ securitySchemes: openapiData.components?.securitySchemes,
93
133
  info: {
94
134
  ...openapiData.info,
95
135
  tags: openapiData.tags?.map((tagName) =>
@@ -145,6 +185,7 @@ function createItems(openapiData: OpenApiObject): ApiMetadata[] {
145
185
  const apiPage: PartialPage<ApiPageMetadata> = {
146
186
  type: "api",
147
187
  id: baseId,
188
+ infoId: infoId ?? "",
148
189
  unversionedId: baseId,
149
190
  title: title,
150
191
  description: description ?? "",
@@ -186,7 +227,7 @@ function bindCollectionToApiItems(
186
227
  .replace(/:([a-z0-9-_]+)/gi, "{$1}"); // replace "/:variableName" with "/{variableName}"
187
228
 
188
229
  const apiItem = items.find((item) => {
189
- if (item.type === "info") {
230
+ if (item.type === "info" || item.type === "tag") {
190
231
  return false;
191
232
  }
192
233
  return item.api.path === path && item.api.method === method;
@@ -208,36 +249,39 @@ export async function readOpenapiFiles(
208
249
  openapiPath: string,
209
250
  _options: {}
210
251
  ): Promise<OpenApiFiles[]> {
211
- const stat = await fs.lstat(openapiPath);
212
- if (stat.isDirectory()) {
213
- console.warn(
214
- chalk.yellow(
215
- "WARNING: Loading a directory of OpenAPI definitions is experimental and subject to unannounced breaking changes."
216
- )
217
- );
218
-
219
- // TODO: Add config for inlcude/ignore
220
- const allFiles = await Globby(["**/*.{json,yaml,yml}"], {
221
- cwd: openapiPath,
222
- ignore: GlobExcludeDefault,
223
- });
224
- const sources = allFiles.filter((x) => !x.includes("_category_")); // todo: regex exclude?
225
- return Promise.all(
226
- sources.map(async (source) => {
227
- // TODO: make a function for this
228
- const fullPath = path.join(openapiPath, source);
229
- const openapiString = await fs.readFile(fullPath, "utf-8");
230
- const data = yaml.load(openapiString) as OpenApiObjectWithRef;
231
- return {
232
- source: fullPath, // This will be aliased in process.
233
- sourceDirName: path.dirname(source),
234
- data,
235
- };
236
- })
237
- );
252
+ if (!isURL(openapiPath)) {
253
+ const stat = await fs.lstat(openapiPath);
254
+ if (stat.isDirectory()) {
255
+ console.warn(
256
+ chalk.yellow(
257
+ "WARNING: Loading a directory of OpenAPI definitions is experimental and subject to unannounced breaking changes."
258
+ )
259
+ );
260
+
261
+ // TODO: Add config for inlcude/ignore
262
+ const allFiles = await Globby(["**/*.{json,yaml,yml}"], {
263
+ cwd: openapiPath,
264
+ ignore: GlobExcludeDefault,
265
+ deep: 1,
266
+ });
267
+ const sources = allFiles.filter((x) => !x.includes("_category_")); // todo: regex exclude?
268
+ return Promise.all(
269
+ sources.map(async (source) => {
270
+ // TODO: make a function for this
271
+ const fullPath = path.join(openapiPath, source);
272
+ const data = (await loadAndBundleSpec(
273
+ fullPath
274
+ )) as OpenApiObjectWithRef;
275
+ return {
276
+ source: fullPath, // This will be aliased in process.
277
+ sourceDirName: path.dirname(source),
278
+ data,
279
+ };
280
+ })
281
+ );
282
+ }
238
283
  }
239
- const openapiString = await fs.readFile(openapiPath, "utf-8");
240
- const data = yaml.load(openapiString) as OpenApiObjectWithRef;
284
+ const data = (await loadAndBundleSpec(openapiPath)) as OpenApiObjectWithRef;
241
285
  return [
242
286
  {
243
287
  source: openapiPath, // This will be aliased in process.
@@ -248,10 +292,11 @@ export async function readOpenapiFiles(
248
292
  }
249
293
 
250
294
  export async function processOpenapiFiles(
251
- files: OpenApiFiles[]
295
+ files: OpenApiFiles[],
296
+ sidebarOptions: SidebarOptions
252
297
  ): Promise<[ApiMetadata[], TagObject[]]> {
253
298
  const promises = files.map(async (file) => {
254
- const processedFile = await processOpenapiFile(file.data);
299
+ const processedFile = await processOpenapiFile(file.data, sidebarOptions);
255
300
  const itemsObjectsArray = processedFile[0].map((item) => ({
256
301
  ...item,
257
302
  }));
@@ -271,11 +316,12 @@ export async function processOpenapiFiles(
271
316
  }
272
317
 
273
318
  export async function processOpenapiFile(
274
- openapiDataWithRefs: OpenApiObjectWithRef
319
+ openapiDataWithRefs: OpenApiObjectWithRef,
320
+ sidebarOptions: SidebarOptions
275
321
  ): Promise<[ApiMetadata[], TagObject[]]> {
276
322
  const openapiData = await resolveRefs(openapiDataWithRefs);
277
323
  const postmanCollection = await createPostmanCollection(openapiData);
278
- const items = createItems(openapiData);
324
+ const items = createItems(openapiData, sidebarOptions);
279
325
 
280
326
  bindCollectionToApiItems(items, postmanCollection);
281
327
 
@@ -0,0 +1,62 @@
1
+ /* ============================================================================
2
+ * Copyright (c) Palo Alto Networks
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ * ========================================================================== */
7
+
8
+ // @ts-nocheck
9
+
10
+ import type { Source, Document } from "@redocly/openapi-core";
11
+ import { bundle } from "@redocly/openapi-core/lib/bundle";
12
+ import type { ResolvedConfig } from "@redocly/openapi-core/lib/config";
13
+ import { Config } from "@redocly/openapi-core/lib/config/config";
14
+ import { convertObj } from "swagger2openapi";
15
+
16
+ import { OpenAPISpec } from "./types";
17
+
18
+ export async function loadAndBundleSpec(
19
+ specUrlOrObject: object | string
20
+ ): Promise<OpenAPISpec> {
21
+ const config = new Config({} as ResolvedConfig);
22
+ const bundleOpts = {
23
+ config,
24
+ base: process.cwd(),
25
+ };
26
+
27
+ if (typeof specUrlOrObject === "object" && specUrlOrObject !== null) {
28
+ bundleOpts["doc"] = {
29
+ source: { absoluteRef: "" } as Source,
30
+ parsed: specUrlOrObject,
31
+ } as Document;
32
+ } else {
33
+ bundleOpts["ref"] = specUrlOrObject;
34
+ }
35
+
36
+ // Force dereference ?
37
+ // bundleOpts["dereference"] = true;
38
+
39
+ const {
40
+ bundle: { parsed },
41
+ } = await bundle(bundleOpts);
42
+ return parsed.swagger !== undefined ? convertSwagger2OpenAPI(parsed) : parsed;
43
+ }
44
+
45
+ export function convertSwagger2OpenAPI(spec: any): Promise<OpenAPISpec> {
46
+ console.warn(
47
+ "[ReDoc Compatibility mode]: Converting OpenAPI 2.0 to OpenAPI 3.0"
48
+ );
49
+ return new Promise<OpenAPISpec>((resolve, reject) =>
50
+ convertObj(
51
+ spec,
52
+ { patch: true, warnOnly: true, text: "{}", anchors: true },
53
+ (err, res) => {
54
+ // TODO: log any warnings
55
+ if (err) {
56
+ return reject(err);
57
+ }
58
+ resolve(res && (res.openapi as any));
59
+ }
60
+ )
61
+ );
62
+ }
@@ -0,0 +1,303 @@
1
+ /* ============================================================================
2
+ * Copyright (c) Palo Alto Networks
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ * ========================================================================== */
7
+
8
+ type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
9
+
10
+ export interface OpenAPISpec {
11
+ openapi: string;
12
+ info: OpenAPIInfo;
13
+ servers?: OpenAPIServer[];
14
+ paths: OpenAPIPaths;
15
+ components?: OpenAPIComponents;
16
+ security?: OpenAPISecurityRequirement[];
17
+ tags?: OpenAPITag[];
18
+ externalDocs?: OpenAPIExternalDocumentation;
19
+ "x-webhooks"?: OpenAPIPaths;
20
+ webhooks?: OpenAPIPaths;
21
+ }
22
+
23
+ export interface OpenAPIInfo {
24
+ title: string;
25
+ version: string;
26
+
27
+ description?: string;
28
+ summary?: string;
29
+ termsOfService?: string;
30
+ contact?: OpenAPIContact;
31
+ license?: OpenAPILicense;
32
+ }
33
+
34
+ export interface OpenAPIServer {
35
+ url: string;
36
+ description?: string;
37
+ variables?: { [name: string]: OpenAPIServerVariable };
38
+ }
39
+
40
+ export interface OpenAPIServerVariable {
41
+ enum?: string[];
42
+ default: string;
43
+ description?: string;
44
+ }
45
+
46
+ export interface OpenAPIPaths {
47
+ [path: string]: OpenAPIPath;
48
+ }
49
+ export interface OpenAPIRef {
50
+ $ref: string;
51
+ }
52
+
53
+ export type Referenced<T> = OpenAPIRef | T;
54
+
55
+ export interface OpenAPIPath {
56
+ summary?: string;
57
+ description?: string;
58
+ get?: OpenAPIOperation;
59
+ put?: OpenAPIOperation;
60
+ post?: OpenAPIOperation;
61
+ delete?: OpenAPIOperation;
62
+ options?: OpenAPIOperation;
63
+ head?: OpenAPIOperation;
64
+ patch?: OpenAPIOperation;
65
+ trace?: OpenAPIOperation;
66
+ servers?: OpenAPIServer[];
67
+ parameters?: Array<Referenced<OpenAPIParameter>>;
68
+ $ref?: string;
69
+ }
70
+
71
+ export interface OpenAPIXCodeSample {
72
+ lang: string;
73
+ label?: string;
74
+ source: string;
75
+ }
76
+
77
+ export interface OpenAPIOperation {
78
+ tags?: string[];
79
+ summary?: string;
80
+ description?: string;
81
+ externalDocs?: OpenAPIExternalDocumentation;
82
+ operationId?: string;
83
+ parameters?: Array<Referenced<OpenAPIParameter>>;
84
+ requestBody?: Referenced<OpenAPIRequestBody>;
85
+ responses: OpenAPIResponses;
86
+ callbacks?: { [name: string]: Referenced<OpenAPICallback> };
87
+ deprecated?: boolean;
88
+ security?: OpenAPISecurityRequirement[];
89
+ servers?: OpenAPIServer[];
90
+ "x-codeSamples"?: OpenAPIXCodeSample[];
91
+ "x-code-samples"?: OpenAPIXCodeSample[]; // deprecated
92
+ }
93
+
94
+ export interface OpenAPIParameter {
95
+ name: string;
96
+ in?: OpenAPIParameterLocation;
97
+ description?: string;
98
+ required?: boolean;
99
+ deprecated?: boolean;
100
+ allowEmptyValue?: boolean;
101
+ style?: OpenAPIParameterStyle;
102
+ explode?: boolean;
103
+ allowReserved?: boolean;
104
+ schema?: Referenced<OpenAPISchema>;
105
+ example?: any;
106
+ examples?: { [media: string]: Referenced<OpenAPIExample> };
107
+ content?: { [media: string]: OpenAPIMediaType };
108
+ encoding?: Record<string, OpenAPIEncoding>;
109
+ const?: any;
110
+ }
111
+
112
+ export interface OpenAPIExample {
113
+ value: any;
114
+ summary?: string;
115
+ description?: string;
116
+ externalValue?: string;
117
+ }
118
+
119
+ export interface OpenAPISchema {
120
+ $ref?: string;
121
+ type?: string | string[];
122
+ properties?: { [name: string]: OpenAPISchema };
123
+ patternProperties?: { [name: string]: OpenAPISchema };
124
+ additionalProperties?: boolean | OpenAPISchema;
125
+ unevaluatedProperties?: boolean | OpenAPISchema;
126
+ description?: string;
127
+ default?: any;
128
+ items?: OpenAPISchema | OpenAPISchema[] | boolean;
129
+ required?: string[];
130
+ readOnly?: boolean;
131
+ writeOnly?: boolean;
132
+ deprecated?: boolean;
133
+ format?: string;
134
+ externalDocs?: OpenAPIExternalDocumentation;
135
+ discriminator?: OpenAPIDiscriminator;
136
+ nullable?: boolean;
137
+ oneOf?: OpenAPISchema[];
138
+ anyOf?: OpenAPISchema[];
139
+ allOf?: OpenAPISchema[];
140
+ not?: OpenAPISchema;
141
+
142
+ title?: string;
143
+ multipleOf?: number;
144
+ maximum?: number;
145
+ exclusiveMaximum?: boolean | number;
146
+ minimum?: number;
147
+ exclusiveMinimum?: boolean | number;
148
+ maxLength?: number;
149
+ minLength?: number;
150
+ pattern?: string;
151
+ maxItems?: number;
152
+ minItems?: number;
153
+ uniqueItems?: boolean;
154
+ maxProperties?: number;
155
+ minProperties?: number;
156
+ enum?: any[];
157
+ example?: any;
158
+
159
+ if?: OpenAPISchema;
160
+ else?: OpenAPISchema;
161
+ then?: OpenAPISchema;
162
+ examples?: any[];
163
+ const?: string;
164
+ contentEncoding?: string;
165
+ contentMediaType?: string;
166
+ prefixItems?: OpenAPISchema[];
167
+ additionalItems?: OpenAPISchema | boolean;
168
+ }
169
+
170
+ export interface OpenAPIDiscriminator {
171
+ propertyName: string;
172
+ mapping?: { [name: string]: string };
173
+ "x-explicitMappingOnly"?: boolean;
174
+ }
175
+
176
+ export interface OpenAPIMediaType {
177
+ schema?: Referenced<OpenAPISchema>;
178
+ example?: any;
179
+ examples?: { [name: string]: Referenced<OpenAPIExample> };
180
+ encoding?: { [field: string]: OpenAPIEncoding };
181
+ }
182
+
183
+ export interface OpenAPIEncoding {
184
+ contentType: string;
185
+ headers?: { [name: string]: Referenced<OpenAPIHeader> };
186
+ style: OpenAPIParameterStyle;
187
+ explode: boolean;
188
+ allowReserved: boolean;
189
+ }
190
+
191
+ export type OpenAPIParameterLocation = "query" | "header" | "path" | "cookie";
192
+ export type OpenAPIParameterStyle =
193
+ | "matrix"
194
+ | "label"
195
+ | "form"
196
+ | "simple"
197
+ | "spaceDelimited"
198
+ | "pipeDelimited"
199
+ | "deepObject";
200
+
201
+ export interface OpenAPIRequestBody {
202
+ description?: string;
203
+ required?: boolean;
204
+ content: { [mime: string]: OpenAPIMediaType };
205
+
206
+ "x-examples"?: {
207
+ [mime: string]: { [name: string]: Referenced<OpenAPIExample> };
208
+ };
209
+ "x-example"?: { [mime: string]: any };
210
+ }
211
+
212
+ export interface OpenAPIResponses {
213
+ [code: string]: Referenced<OpenAPIResponse>;
214
+ }
215
+
216
+ export interface OpenAPIResponse
217
+ extends Pick<OpenAPIRequestBody, "description" | "x-examples" | "x-example"> {
218
+ headers?: { [name: string]: Referenced<OpenAPIHeader> };
219
+ links?: { [name: string]: Referenced<OpenAPILink> };
220
+ content?: { [mime: string]: OpenAPIMediaType };
221
+ }
222
+
223
+ export interface OpenAPILink {
224
+ $ref?: string;
225
+ }
226
+
227
+ export type OpenAPIHeader = Omit<OpenAPIParameter, "in" | "name">;
228
+
229
+ export interface OpenAPICallback {
230
+ [name: string]: OpenAPIPath;
231
+ }
232
+
233
+ export interface OpenAPIComponents {
234
+ schemas?: { [name: string]: Referenced<OpenAPISchema> };
235
+ responses?: { [name: string]: Referenced<OpenAPIResponse> };
236
+ parameters?: { [name: string]: Referenced<OpenAPIParameter> };
237
+ examples?: { [name: string]: Referenced<OpenAPIExample> };
238
+ requestBodies?: { [name: string]: Referenced<OpenAPIRequestBody> };
239
+ headers?: { [name: string]: Referenced<OpenAPIHeader> };
240
+ securitySchemes?: { [name: string]: Referenced<OpenAPISecurityScheme> };
241
+ links?: { [name: string]: Referenced<OpenAPILink> };
242
+ callbacks?: { [name: string]: Referenced<OpenAPICallback> };
243
+ }
244
+
245
+ export interface OpenAPISecurityRequirement {
246
+ [name: string]: string[];
247
+ }
248
+
249
+ export interface OpenAPISecurityScheme {
250
+ type: "apiKey" | "http" | "oauth2" | "openIdConnect";
251
+ description?: string;
252
+ name?: string;
253
+ in?: "query" | "header" | "cookie";
254
+ scheme?: string;
255
+ bearerFormat: string;
256
+ flows: {
257
+ implicit?: {
258
+ refreshUrl?: string;
259
+ scopes: Record<string, string>;
260
+ authorizationUrl: string;
261
+ };
262
+ password?: {
263
+ refreshUrl?: string;
264
+ scopes: Record<string, string>;
265
+ tokenUrl: string;
266
+ };
267
+ clientCredentials?: {
268
+ refreshUrl?: string;
269
+ scopes: Record<string, string>;
270
+ tokenUrl: string;
271
+ };
272
+ authorizationCode?: {
273
+ refreshUrl?: string;
274
+ scopes: Record<string, string>;
275
+ tokenUrl: string;
276
+ };
277
+ };
278
+ openIdConnectUrl?: string;
279
+ }
280
+
281
+ export interface OpenAPITag {
282
+ name: string;
283
+ description?: string;
284
+ externalDocs?: OpenAPIExternalDocumentation;
285
+ "x-displayName"?: string;
286
+ }
287
+
288
+ export interface OpenAPIExternalDocumentation {
289
+ description?: string;
290
+ url: string;
291
+ }
292
+
293
+ export interface OpenAPIContact {
294
+ name?: string;
295
+ url?: string;
296
+ email?: string;
297
+ }
298
+
299
+ export interface OpenAPILicense {
300
+ name: string;
301
+ url?: string;
302
+ identifier?: string;
303
+ }
@@ -7,6 +7,7 @@
7
7
 
8
8
  import {
9
9
  ProcessedSidebar,
10
+ SidebarItemCategory,
10
11
  SidebarItemCategoryLinkConfig,
11
12
  SidebarItemDoc,
12
13
  } from "@docusaurus/plugin-content-docs/src/sidebars/types";
@@ -99,7 +100,9 @@ function groupByTags(
99
100
  const tagged = apiTags
100
101
  .map((tag) => {
101
102
  // Map info object to tag
102
- const infoObject = intros.find((i) => i.tags.includes(tag));
103
+ const taggedInfoObject = intros.find((i) =>
104
+ i.tags ? i.tags.includes(tag) : undefined
105
+ );
103
106
  const tagObject = tags.flat().find(
104
107
  (t) =>
105
108
  (tag === t.name || tag === t["x-displayName"]) ?? {
@@ -110,21 +113,19 @@ function groupByTags(
110
113
 
111
114
  // TODO: perhaps move this into a getLinkConfig() function
112
115
  let linkConfig = undefined;
113
- if (infoObject !== undefined && categoryLinkSource === "info") {
116
+ if (taggedInfoObject !== undefined && categoryLinkSource === "info") {
114
117
  linkConfig = {
115
118
  type: "doc",
116
- id: `${basePath}/${infoObject.id}`,
119
+ id: `${basePath}/${taggedInfoObject.id}`,
117
120
  } as SidebarItemCategoryLinkConfig;
118
121
  }
119
122
 
120
123
  // TODO: perhaps move this into a getLinkConfig() function
121
124
  if (tagObject !== undefined && categoryLinkSource === "tag") {
122
- const linkDescription = tagObject?.description;
125
+ const tagId = kebabCase(tagObject.name);
123
126
  linkConfig = {
124
- type: "generated-index" as "generated-index",
125
- title: tag,
126
- description: linkDescription,
127
- slug: "/category/" + kebabCase(tag),
127
+ type: "doc",
128
+ id: `${basePath}/${tagId}`,
128
129
  } as SidebarItemCategoryLinkConfig;
129
130
  }
130
131
 
@@ -150,18 +151,24 @@ function groupByTags(
150
151
  })
151
152
  .filter((item) => item.items.length > 0); // Filter out any categories with no items.
152
153
 
153
- // TODO: determine how we want to handle these
154
- // const untagged = [
155
- // {
156
- // type: "category" as const,
157
- // label: "UNTAGGED",
158
- // collapsible: sidebarCollapsible,
159
- // collapsed: sidebarCollapsed,
160
- // items: apiItems
161
- // .filter(({ api }) => api.tags === undefined || api.tags.length === 0)
162
- // .map(createDocItem),
163
- // },
164
- // ];
154
+ // Handle items with no tag
155
+ const untaggedItems = apiItems
156
+ .filter(({ api }) => api.tags === undefined || api.tags.length === 0)
157
+ .map(createDocItem);
158
+ let untagged: SidebarItemCategory[] = [];
159
+ if (untaggedItems.length > 0) {
160
+ untagged = [
161
+ {
162
+ type: "category" as const,
163
+ label: "UNTAGGED",
164
+ collapsible: sidebarCollapsible!,
165
+ collapsed: sidebarCollapsed!,
166
+ items: apiItems
167
+ .filter(({ api }) => api.tags === undefined || api.tags.length === 0)
168
+ .map(createDocItem),
169
+ },
170
+ ];
171
+ }
165
172
 
166
173
  // Shift root intro doc to top of sidebar
167
174
  // TODO: Add input validation for categoryLinkSource options
@@ -169,7 +176,7 @@ function groupByTags(
169
176
  tagged.unshift(rootIntroDoc as any);
170
177
  }
171
178
 
172
- return [...tagged];
179
+ return [...tagged, ...untagged];
173
180
  }
174
181
 
175
182
  export default function generateSidebarSlice(
@@ -179,7 +186,7 @@ export default function generateSidebarSlice(
179
186
  tags: TagObject[]
180
187
  ) {
181
188
  let sidebarSlice: ProcessedSidebar = [];
182
- if (sidebarOptions.groupPathsBy === "tags") {
189
+ if (sidebarOptions.groupPathsBy === "tag") {
183
190
  sidebarSlice = groupByTags(
184
191
  api as ApiPageMetadata[],
185
192
  sidebarOptions,
package/src/types.ts CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  InfoObject,
12
12
  OperationObject,
13
13
  SecuritySchemeObject,
14
+ TagObject,
14
15
  } from "./openapi/types";
15
16
 
16
17
  export type {
@@ -38,7 +39,7 @@ export interface LoadedContent {
38
39
  // loadedDocs: DocPageMetadata[]; TODO: cleanup
39
40
  }
40
41
 
41
- export type ApiMetadata = ApiPageMetadata | InfoPageMetadata;
42
+ export type ApiMetadata = ApiPageMetadata | InfoPageMetadata | TagPageMetadata;
42
43
 
43
44
  export interface ApiMetadataBase {
44
45
  sidebar?: string;
@@ -47,6 +48,8 @@ export interface ApiMetadataBase {
47
48
  //
48
49
  id: string; // TODO legacy versioned id => try to remove
49
50
  unversionedId: string; // TODO new unversioned id => try to rename to "id"
51
+ infoId?: string;
52
+ infoPath?: string;
50
53
  title: string;
51
54
  description: string;
52
55
  source: string; // @site aliased source => "@site/docs/folder/subFolder/subSubFolder/myDoc.md"
@@ -79,6 +82,15 @@ export interface InfoPageMetadata extends ApiMetadataBase {
79
82
  type: "info";
80
83
  info: ApiInfo;
81
84
  markdown?: string;
85
+ securitySchemes?: {
86
+ [key: string]: SecuritySchemeObject;
87
+ };
88
+ }
89
+
90
+ export interface TagPageMetadata extends ApiMetadataBase {
91
+ type: "tag";
92
+ tag: TagObject;
93
+ markdown?: string;
82
94
  }
83
95
 
84
96
  export type ApiInfo = InfoObject;