@mintlify/scraping 4.0.70 → 4.0.72

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mintlify/scraping",
3
- "version": "4.0.70",
3
+ "version": "4.0.72",
4
4
  "description": "Scrape documentation frameworks to Mintlify docs",
5
5
  "engines": {
6
6
  "node": ">=18.0.0"
@@ -38,7 +38,7 @@
38
38
  "format:check": "prettier . --check"
39
39
  },
40
40
  "dependencies": {
41
- "@mintlify/common": "1.0.225",
41
+ "@mintlify/common": "1.0.227",
42
42
  "@mintlify/openapi-parser": "^0.0.7",
43
43
  "fs-extra": "^11.1.1",
44
44
  "hast-util-to-mdast": "^10.1.0",
@@ -61,7 +61,7 @@
61
61
  "@mintlify/models": "0.0.160",
62
62
  "@mintlify/prettier-config": "1.0.4",
63
63
  "@mintlify/ts-config": "2.0.2",
64
- "@mintlify/validation": "0.1.251",
64
+ "@mintlify/validation": "0.1.252",
65
65
  "@trivago/prettier-plugin-sort-imports": "^4.2.1",
66
66
  "@tsconfig/recommended": "1.x",
67
67
  "@types/hast": "^3.0.4",
@@ -78,5 +78,5 @@
78
78
  "typescript": "^5.5.3",
79
79
  "vitest": "^2.0.4"
80
80
  },
81
- "gitHead": "c7f9ee676d859e5b3077b78d25f23303064ccf42"
81
+ "gitHead": "c3fd918eaf9d5311d70091042a1c2b208766281b"
82
82
  }
package/src/index.ts CHANGED
@@ -1 +1,2 @@
1
1
  export { generateOpenApiPages } from './openapi/generateOpenApiPages.js';
2
+ export { generateOpenApiPagesForDocsConfig } from './openapi/generateOpenApiPagesForDocsConfig.js';
@@ -0,0 +1,168 @@
1
+ import {
2
+ getOpenApiTitleAndDescription,
3
+ optionallyAddLeadingSlash,
4
+ slugToTitle,
5
+ } from '@mintlify/common';
6
+ import type { DecoratedNavigationPage, NavigationEntry } from '@mintlify/models';
7
+ import { outputFile } from 'fs-extra';
8
+ import fse from 'fs-extra';
9
+ import fs from 'fs/promises';
10
+ import yaml from 'js-yaml';
11
+ import { OpenAPI, OpenAPIV3 } from 'openapi-types';
12
+ import path, { join, parse, resolve } from 'path';
13
+
14
+ import { fetchOpenApi } from '../utils/network.js';
15
+
16
+ export const getOpenApiDefinition = async (
17
+ pathOrDocumentOrUrl: string | OpenAPI.Document | URL
18
+ ): Promise<{ document: OpenAPI.Document; isUrl: boolean }> => {
19
+ if (typeof pathOrDocumentOrUrl === 'string') {
20
+ if (pathOrDocumentOrUrl.startsWith('http://')) {
21
+ // This is an invalid location either for a file or a URL
22
+ throw new Error('Only HTTPS URLs are supported. Please provide an HTTPS URL');
23
+ } else {
24
+ try {
25
+ const url = new URL(pathOrDocumentOrUrl);
26
+ pathOrDocumentOrUrl = url;
27
+ } catch {
28
+ const pathname = path.join(process.cwd(), pathOrDocumentOrUrl.toString());
29
+ const file = await fs.readFile(pathname, 'utf-8');
30
+ pathOrDocumentOrUrl = yaml.load(file) as OpenAPI.Document;
31
+ }
32
+ }
33
+ }
34
+ const isUrl = pathOrDocumentOrUrl instanceof URL;
35
+ if (pathOrDocumentOrUrl instanceof URL) {
36
+ if (pathOrDocumentOrUrl.protocol !== 'https:') {
37
+ throw new Error('Only HTTPS URLs are supported. Please provide an HTTPS URL');
38
+ }
39
+ pathOrDocumentOrUrl = await fetchOpenApi(pathOrDocumentOrUrl);
40
+ }
41
+
42
+ return { document: pathOrDocumentOrUrl, isUrl };
43
+ };
44
+
45
+ // returns a filename that is unique within the given array of pages
46
+ export const generateUniqueFilenameWithoutExtension = (
47
+ pages: NavigationEntry[],
48
+ base: string
49
+ ): string => {
50
+ let filename = base;
51
+ if (pages.includes(filename)) {
52
+ let extension = 1;
53
+ filename = `${base}-${extension}`;
54
+ while (pages.includes(filename)) {
55
+ extension += 1;
56
+ filename = `${base}-${extension}`;
57
+ }
58
+ }
59
+ return filename;
60
+ };
61
+
62
+ export const createOpenApiFrontmatter = async (
63
+ filename: string,
64
+ openApiMetaTag: string,
65
+ version?: string
66
+ ) => {
67
+ const data = `---
68
+ openapi: ${openApiMetaTag}${version ? `\nversion: ${version}` : ''}
69
+ ---`;
70
+
71
+ await outputFile(filename, data);
72
+ };
73
+
74
+ export const prepareStringToBeValidFilename = (str?: string) =>
75
+ str
76
+ ? str
77
+ .replaceAll(' ', '-')
78
+ .replace(/\{.*?\}/g, '-') // remove path parameters
79
+ .replace(/^-/, '')
80
+ .replace(/-$/, '')
81
+ .replace(/[{}(),.'\n\/]/g, '') // remove special characters
82
+ .replaceAll(/--/g, '-') // replace double hyphens
83
+ .toLowerCase()
84
+ : undefined;
85
+
86
+ export type GenerateOpenApiPagesOptions = {
87
+ openApiFilePath?: string;
88
+ version?: string;
89
+ writeFiles?: boolean;
90
+ outDir?: string;
91
+ outDirBasePath?: string;
92
+ overwrite?: boolean;
93
+ };
94
+
95
+ export type OpenApiPageGenerationResult<N, DN> = {
96
+ nav: N;
97
+ decoratedNav: DN;
98
+ spec: OpenAPI.Document;
99
+ pagesAcc: Record<string, DecoratedNavigationPage>;
100
+ isUrl: boolean;
101
+ };
102
+
103
+ export function processOpenApiPath<N, DN>(
104
+ path: string,
105
+ pathItemObject: OpenAPIV3.PathItemObject,
106
+ schema: OpenAPI.Document,
107
+ nav: N,
108
+ decoratedNav: DN,
109
+ writePromises: Promise<void>[],
110
+ pagesAcc: Record<string, DecoratedNavigationPage>,
111
+ options: GenerateOpenApiPagesOptions,
112
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
113
+ findNavGroup: (nav: any, groupName?: string) => any
114
+ ) {
115
+ const openApiFilePathFromRoot = options.openApiFilePath
116
+ ? optionallyAddLeadingSlash(options.openApiFilePath)
117
+ : undefined;
118
+
119
+ Object.values(OpenAPIV3.HttpMethods).forEach((method) => {
120
+ if (method in pathItemObject) {
121
+ const operation = pathItemObject[method];
122
+ const groupName = operation?.tags?.[0];
123
+ const title =
124
+ prepareStringToBeValidFilename(operation?.summary) ??
125
+ `${method}-${prepareStringToBeValidFilename(path)}`;
126
+ const folder = prepareStringToBeValidFilename(groupName) ?? '';
127
+ const base = join(options.outDir ?? '', folder, title);
128
+
129
+ const navGroup = findNavGroup(nav, groupName);
130
+ const decoratedNavGroup = findNavGroup(decoratedNav, groupName);
131
+
132
+ const filenameWithoutExtension = generateUniqueFilenameWithoutExtension(navGroup, base);
133
+ const openapiMetaTag = `${
134
+ openApiFilePathFromRoot ? `${openApiFilePathFromRoot} ` : ''
135
+ }${method} ${path}`;
136
+ const { title: titleTag, description } = getOpenApiTitleAndDescription(
137
+ [
138
+ {
139
+ filename: options.openApiFilePath
140
+ ? parse(options.openApiFilePath).name
141
+ : 'filler-filename',
142
+ spec: schema,
143
+ originalFileLocation: options.openApiFilePath,
144
+ },
145
+ ],
146
+ openapiMetaTag
147
+ );
148
+ navGroup.push(filenameWithoutExtension);
149
+ const page: DecoratedNavigationPage = {
150
+ openapi: openapiMetaTag,
151
+ href: resolve('/', filenameWithoutExtension),
152
+ title: titleTag ?? slugToTitle(filenameWithoutExtension),
153
+ description,
154
+ version: options.version,
155
+ };
156
+ decoratedNavGroup.push(page);
157
+ pagesAcc[filenameWithoutExtension] = page;
158
+ const targetPath = options.outDirBasePath
159
+ ? join(options.outDirBasePath, `${filenameWithoutExtension}.mdx`)
160
+ : `${filenameWithoutExtension}.mdx`;
161
+ if (options.writeFiles && (!fse.pathExistsSync(targetPath) || options.overwrite)) {
162
+ writePromises.push(createOpenApiFrontmatter(targetPath, openapiMetaTag, options.version));
163
+ }
164
+ }
165
+ });
166
+ }
167
+
168
+ export const DEFAULT_API_GROUP_NAME = 'API Reference';
@@ -1,80 +1,26 @@
1
- import {
2
- getOpenApiTitleAndDescription,
3
- optionallyAddLeadingSlash,
4
- slugToTitle,
5
- validate,
6
- } from '@mintlify/common';
1
+ import { validate } from '@mintlify/common';
7
2
  import type {
8
3
  DecoratedNavigation,
9
4
  DecoratedNavigationGroup,
10
5
  Navigation,
11
- NavigationEntry,
12
6
  NavigationGroup,
13
7
  DecoratedNavigationPage,
14
8
  } from '@mintlify/models';
15
- import fse, { outputFile } from 'fs-extra';
16
- import fs from 'fs/promises';
17
- import yaml from 'js-yaml';
18
- import { OpenAPI, OpenAPIV3 } from 'openapi-types';
19
- import { join, resolve, parse } from 'path';
20
- import path from 'path';
21
-
22
- import { fetchOpenApi } from '../utils/network.js';
23
-
24
- export const getOpenApiDefinition = async (
25
- pathOrDocumentOrUrl: string | OpenAPI.Document | URL
26
- ): Promise<{ document: OpenAPI.Document; isUrl: boolean }> => {
27
- if (typeof pathOrDocumentOrUrl === 'string') {
28
- if (pathOrDocumentOrUrl.startsWith('http://')) {
29
- // This is an invalid location either for a file or a URL
30
- throw new Error('Only HTTPS URLs are supported. Please provide an HTTPS URL');
31
- } else {
32
- try {
33
- const url = new URL(pathOrDocumentOrUrl);
34
- pathOrDocumentOrUrl = url;
35
- } catch {
36
- const pathname = path.join(process.cwd(), pathOrDocumentOrUrl.toString());
37
- const file = await fs.readFile(pathname, 'utf-8');
38
- pathOrDocumentOrUrl = yaml.load(file) as OpenAPI.Document;
39
- }
40
- }
41
- }
42
-
43
- const isUrl = pathOrDocumentOrUrl instanceof URL;
44
- if (pathOrDocumentOrUrl instanceof URL) {
45
- if (pathOrDocumentOrUrl.protocol !== 'https:') {
46
- throw new Error('Only HTTPS URLs are supported. Please provide an HTTPS URL');
47
- }
48
- pathOrDocumentOrUrl = await fetchOpenApi(pathOrDocumentOrUrl);
49
- }
50
-
51
- return { document: pathOrDocumentOrUrl as OpenAPI.Document, isUrl };
52
- };
9
+ import { OpenAPI } from 'openapi-types';
53
10
 
54
- export const generateOpenApiPages = async (
11
+ import {
12
+ getOpenApiDefinition,
13
+ GenerateOpenApiPagesOptions,
14
+ OpenApiPageGenerationResult,
15
+ processOpenApiPath,
16
+ DEFAULT_API_GROUP_NAME,
17
+ } from './common.js';
18
+
19
+ export async function generateOpenApiPages(
55
20
  pathOrDocumentOrUrl: string | OpenAPI.Document | URL,
56
- opts?: {
57
- openApiFilePath?: string | undefined;
58
- version?: string | undefined;
59
- writeFiles?: boolean;
60
- outDir?: string;
61
- outDirBasePath?: string;
62
- overwrite?: boolean;
63
- }
64
- ): Promise<{
65
- nav: Navigation;
66
- decoratedNav: DecoratedNavigation;
67
- spec: OpenAPI.Document;
68
- pagesAcc: Record<string, DecoratedNavigationPage>;
69
- isUrl: boolean;
70
- }> => {
71
- const { openApiFilePath, version, writeFiles, outDir, outDirBasePath } = opts ?? {};
72
- const openApiFilePathFromRoot = openApiFilePath
73
- ? optionallyAddLeadingSlash(openApiFilePath)
74
- : undefined;
75
-
21
+ opts?: GenerateOpenApiPagesOptions
22
+ ): Promise<OpenApiPageGenerationResult<Navigation, DecoratedNavigation>> {
76
23
  const { document, isUrl } = await getOpenApiDefinition(pathOrDocumentOrUrl);
77
-
78
24
  const { schema } = await validate(document);
79
25
 
80
26
  if (schema?.paths === undefined || Object.keys(schema.paths).length === 0) {
@@ -85,56 +31,22 @@ export const generateOpenApiPages = async (
85
31
  const decoratedNav: DecoratedNavigation = [];
86
32
  const writePromises: Promise<void>[] = [];
87
33
  const pagesAcc: Record<string, DecoratedNavigationPage> = {};
34
+
88
35
  Object.entries(schema.paths).forEach(([path, pathItemObject]) => {
89
36
  if (!pathItemObject || typeof pathItemObject !== 'object') {
90
37
  return;
91
38
  }
92
- const typedPathItemObject = pathItemObject as OpenAPIV3.PathItemObject;
93
- Object.values(OpenAPIV3.HttpMethods).forEach((method) => {
94
- if (method in typedPathItemObject) {
95
- const operation = typedPathItemObject[method];
96
- const groupName = operation?.tags?.[0];
97
- const title =
98
- prepareStringToBeValidFilename(operation?.summary) ??
99
- `${method}-${prepareStringToBeValidFilename(path)}`;
100
- const folder = prepareStringToBeValidFilename(groupName) ?? '';
101
- const base = join(outDir ?? '', folder, title);
102
-
103
- const navGroup = findNavGroup<NavigationGroup>(nav, groupName);
104
- const decoratedNavGroup = findNavGroup<DecoratedNavigationGroup>(decoratedNav, groupName);
105
-
106
- const filenameWithoutExtension = generateUniqueFilenameWithoutExtension(navGroup, base);
107
- const openapiMetaTag = `${
108
- openApiFilePathFromRoot ? `${openApiFilePathFromRoot} ` : ''
109
- }${method} ${path}`;
110
- const { title: titleTag, description } = getOpenApiTitleAndDescription(
111
- [
112
- {
113
- filename: openApiFilePath ? parse(openApiFilePath).name : 'filler-filename',
114
- spec: schema as OpenAPI.Document,
115
- originalFileLocation: openApiFilePath,
116
- },
117
- ],
118
- openapiMetaTag
119
- );
120
- navGroup.push(filenameWithoutExtension);
121
- const page: DecoratedNavigationPage = {
122
- openapi: openapiMetaTag,
123
- href: resolve('/', filenameWithoutExtension),
124
- title: titleTag ?? slugToTitle(filenameWithoutExtension),
125
- description,
126
- version,
127
- };
128
- decoratedNavGroup.push(page);
129
- pagesAcc[filenameWithoutExtension] = page;
130
- const targetPath = outDirBasePath
131
- ? join(outDirBasePath, `${filenameWithoutExtension}.mdx`)
132
- : `${filenameWithoutExtension}.mdx`;
133
- if (writeFiles && (!fse.pathExistsSync(targetPath) || opts?.overwrite)) {
134
- writePromises.push(createOpenApiFrontmatter(targetPath, openapiMetaTag, version));
135
- }
136
- }
137
- });
39
+ processOpenApiPath<Navigation, DecoratedNavigation>(
40
+ path,
41
+ pathItemObject,
42
+ schema as OpenAPI.Document,
43
+ nav,
44
+ decoratedNav,
45
+ writePromises,
46
+ pagesAcc,
47
+ opts ?? {},
48
+ findNavGroup
49
+ );
138
50
  });
139
51
 
140
52
  await Promise.all(writePromises);
@@ -146,16 +58,11 @@ export const generateOpenApiPages = async (
146
58
  pagesAcc,
147
59
  isUrl,
148
60
  };
149
- };
150
-
151
- // returns the group with the given group name, or the top-level group if no group name is provided
61
+ }
152
62
  const findNavGroup = <T extends NavigationGroup | DecoratedNavigationGroup>(
153
63
  nav: T['pages'][number][],
154
- groupName?: string
64
+ groupName: string = DEFAULT_API_GROUP_NAME
155
65
  ): T['pages'][number][] => {
156
- if (groupName === undefined) {
157
- groupName = 'API Reference';
158
- }
159
66
  const group = nav.find(
160
67
  (fileOrGroup) =>
161
68
  typeof fileOrGroup === 'object' && 'group' in fileOrGroup && fileOrGroup.group === groupName
@@ -171,41 +78,3 @@ const findNavGroup = <T extends NavigationGroup | DecoratedNavigationGroup>(
171
78
  return group.pages;
172
79
  }
173
80
  };
174
-
175
- // returns a filename that is unique within the given array of pages
176
- const generateUniqueFilenameWithoutExtension = (pages: NavigationEntry[], base: string): string => {
177
- let filename = base;
178
- if (pages.includes(filename)) {
179
- let extension = 1;
180
- filename = `${base}-${extension}`;
181
- while (pages.includes(filename)) {
182
- extension += 1;
183
- filename = `${base}-${extension}`;
184
- }
185
- }
186
- return filename;
187
- };
188
-
189
- const createOpenApiFrontmatter = async (
190
- filename: string,
191
- openApiMetaTag: string,
192
- version?: string
193
- ) => {
194
- const data = `---
195
- openapi: ${openApiMetaTag}${version ? `\nversion: ${version}` : ''}
196
- ---`;
197
-
198
- await outputFile(filename, data);
199
- };
200
-
201
- export const prepareStringToBeValidFilename = (str?: string) =>
202
- str
203
- ? str
204
- .replaceAll(' ', '-')
205
- .replace(/\{.*?\}/g, '-') // remove path parameters
206
- .replace(/^-/, '')
207
- .replace(/-$/, '')
208
- .replace(/[{}(),.'\n\/]/g, '') // remove special characters
209
- .replaceAll(/--/g, '-') // replace double hyphens
210
- .toLowerCase()
211
- : undefined;
@@ -0,0 +1,81 @@
1
+ import { validate } from '@mintlify/common';
2
+ import { DecoratedNavigationPage } from '@mintlify/models';
3
+ import {
4
+ DecoratedGroupsConfig,
5
+ GroupsConfig,
6
+ PagesConfig,
7
+ DecoratedPagesConfig,
8
+ } from '@mintlify/validation';
9
+ import { OpenAPI } from 'openapi-types';
10
+
11
+ import {
12
+ getOpenApiDefinition,
13
+ GenerateOpenApiPagesOptions,
14
+ OpenApiPageGenerationResult,
15
+ processOpenApiPath,
16
+ DEFAULT_API_GROUP_NAME,
17
+ } from './common.js';
18
+
19
+ export async function generateOpenApiPagesForDocsConfig(
20
+ pathOrDocumentOrUrl: string | OpenAPI.Document | URL,
21
+ opts?: GenerateOpenApiPagesOptions
22
+ ): Promise<OpenApiPageGenerationResult<GroupsConfig, DecoratedGroupsConfig>> {
23
+ const { document, isUrl } = await getOpenApiDefinition(pathOrDocumentOrUrl);
24
+ const { schema } = await validate(document);
25
+
26
+ if (schema?.paths === undefined || Object.keys(schema.paths).length === 0) {
27
+ throw new Error('No paths defined.');
28
+ }
29
+
30
+ const nav: GroupsConfig = [];
31
+ const decoratedNav: DecoratedGroupsConfig = [];
32
+ const writePromises: Promise<void>[] = [];
33
+ const pagesAcc: Record<string, DecoratedNavigationPage> = {};
34
+
35
+ Object.entries(schema.paths).forEach(([path, pathItemObject]) => {
36
+ if (!pathItemObject || typeof pathItemObject !== 'object') {
37
+ return;
38
+ }
39
+ processOpenApiPath<GroupsConfig, DecoratedGroupsConfig>(
40
+ path,
41
+ pathItemObject,
42
+ schema as OpenAPI.Document,
43
+ nav,
44
+ decoratedNav,
45
+ writePromises,
46
+ pagesAcc,
47
+ opts ?? {},
48
+ findNavGroup
49
+ );
50
+ });
51
+
52
+ await Promise.all(writePromises);
53
+
54
+ return {
55
+ nav,
56
+ decoratedNav,
57
+ spec: schema as OpenAPI.Document,
58
+ pagesAcc,
59
+ isUrl,
60
+ };
61
+ }
62
+
63
+ const findNavGroup = (
64
+ nav: GroupsConfig | DecoratedGroupsConfig,
65
+ groupName: string = DEFAULT_API_GROUP_NAME
66
+ ): PagesConfig | DecoratedPagesConfig => {
67
+ const group = nav.find(
68
+ (fileOrGroup) =>
69
+ typeof fileOrGroup === 'object' && 'group' in fileOrGroup && fileOrGroup.group === groupName
70
+ );
71
+ if (group === undefined || !('pages' in group)) {
72
+ const newGroup = {
73
+ group: groupName,
74
+ pages: [],
75
+ };
76
+ nav.push(newGroup);
77
+ return newGroup.pages;
78
+ } else {
79
+ return group.pages;
80
+ }
81
+ };