@mintlify/previewing 3.0.0
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/.eslintignore +1 -0
- package/.eslintrc.json +3 -0
- package/CONTRIBUTING.md +17 -0
- package/README.md +78 -0
- package/bin/constants.js +32 -0
- package/bin/constants.js.map +1 -0
- package/bin/downloadImage.js +82 -0
- package/bin/downloadImage.js.map +1 -0
- package/bin/index.js +19 -0
- package/bin/index.js.map +1 -0
- package/bin/local-preview/helper-commands/installDepsCommand.js +12 -0
- package/bin/local-preview/helper-commands/installDepsCommand.js.map +1 -0
- package/bin/local-preview/index.js +154 -0
- package/bin/local-preview/index.js.map +1 -0
- package/bin/local-preview/listener/categorize.js +95 -0
- package/bin/local-preview/listener/categorize.js.map +1 -0
- package/bin/local-preview/listener/generate.js +74 -0
- package/bin/local-preview/listener/generate.js.map +1 -0
- package/bin/local-preview/listener/index.js +200 -0
- package/bin/local-preview/listener/index.js.map +1 -0
- package/bin/local-preview/listener/update.js +24 -0
- package/bin/local-preview/listener/update.js.map +1 -0
- package/bin/local-preview/listener/utils/createPage.js +163 -0
- package/bin/local-preview/listener/utils/createPage.js.map +1 -0
- package/bin/local-preview/listener/utils/getOpenApiContext.js +57 -0
- package/bin/local-preview/listener/utils/getOpenApiContext.js.map +1 -0
- package/bin/local-preview/listener/utils/mintConfigFile.js +22 -0
- package/bin/local-preview/listener/utils/mintConfigFile.js.map +1 -0
- package/bin/local-preview/listener/utils/toTitleCase.js +36 -0
- package/bin/local-preview/listener/utils/toTitleCase.js.map +1 -0
- package/bin/local-preview/listener/utils/types.js +2 -0
- package/bin/local-preview/listener/utils/types.js.map +1 -0
- package/bin/local-preview/listener/utils.js +68 -0
- package/bin/local-preview/listener/utils.js.map +1 -0
- package/bin/util.js +123 -0
- package/bin/util.js.map +1 -0
- package/package.json +77 -0
- package/scraper.md +121 -0
- package/src/constants.ts +40 -0
- package/src/downloadImage.ts +102 -0
- package/src/index.ts +35 -0
- package/src/local-preview/helper-commands/installDepsCommand.ts +13 -0
- package/src/local-preview/index.ts +196 -0
- package/src/local-preview/listener/categorize.ts +107 -0
- package/src/local-preview/listener/generate.ts +121 -0
- package/src/local-preview/listener/index.ts +228 -0
- package/src/local-preview/listener/update.ts +27 -0
- package/src/local-preview/listener/utils/createPage.ts +211 -0
- package/src/local-preview/listener/utils/getOpenApiContext.ts +77 -0
- package/src/local-preview/listener/utils/mintConfigFile.ts +28 -0
- package/src/local-preview/listener/utils/toTitleCase.ts +40 -0
- package/src/local-preview/listener/utils/types.ts +14 -0
- package/src/local-preview/listener/utils.ts +87 -0
- package/src/types.d.ts +35 -0
- package/src/util.ts +154 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import matter from "gray-matter";
|
|
2
|
+
import isAbsoluteUrl from "is-absolute-url";
|
|
3
|
+
import { remark } from "remark";
|
|
4
|
+
import remarkFrontmatter from "remark-frontmatter";
|
|
5
|
+
import remarkGfm from "remark-gfm";
|
|
6
|
+
import remarkMdx from "remark-mdx";
|
|
7
|
+
import { visit } from "unist-util-visit";
|
|
8
|
+
|
|
9
|
+
const createPage = async (
|
|
10
|
+
pagePath: string,
|
|
11
|
+
pageContent: string,
|
|
12
|
+
contentDirectoryPath: string,
|
|
13
|
+
openApiFiles: OpenApiFile[]
|
|
14
|
+
) => {
|
|
15
|
+
const { data: metadata } = matter(pageContent);
|
|
16
|
+
try {
|
|
17
|
+
const parsedContent = await preParseMdx(pageContent, contentDirectoryPath);
|
|
18
|
+
pageContent = parsedContent;
|
|
19
|
+
} catch (error) {
|
|
20
|
+
pageContent = `🚧 A parsing error occured. Please contact the owner of this website.`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Replace .mdx so we can pass file paths into this function
|
|
24
|
+
const slug = pagePath.replace(/\.mdx?$/, "");
|
|
25
|
+
let defaultTitle = slugToTitle(slug);
|
|
26
|
+
// Append data from OpenAPI if it exists
|
|
27
|
+
const { title, description } = getOpenApiTitleAndDescription(
|
|
28
|
+
openApiFiles,
|
|
29
|
+
metadata?.openapi
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
if (title) {
|
|
33
|
+
defaultTitle = title;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const pageMetadata = {
|
|
37
|
+
title: defaultTitle,
|
|
38
|
+
description,
|
|
39
|
+
...metadata,
|
|
40
|
+
href: optionallyAddLeadingSlash(slug),
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
pageMetadata,
|
|
45
|
+
pageContent,
|
|
46
|
+
slug: removeLeadingSlash(slug),
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const preParseMdx = async (
|
|
51
|
+
fileContent: string,
|
|
52
|
+
contentDirectoryPath: string
|
|
53
|
+
) => {
|
|
54
|
+
const removeContentDirectoryPath = (filePath: string) => {
|
|
55
|
+
const pathArr = createPathArr(filePath);
|
|
56
|
+
const contentDirectoryPathArr = createPathArr(contentDirectoryPath);
|
|
57
|
+
contentDirectoryPathArr.reverse().forEach((dir: string, index: number) => {
|
|
58
|
+
if (pathArr[index] === dir) {
|
|
59
|
+
pathArr.pop();
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
return "/" + pathArr.join("/");
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const removeContentDirectoryPaths = () => {
|
|
66
|
+
return (tree) => {
|
|
67
|
+
visit(tree, (node) => {
|
|
68
|
+
if (node == null) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (node.name === "img" || node.name === "source") {
|
|
72
|
+
const srcAttrIndex = node.attributes.findIndex(
|
|
73
|
+
(attr) => attr?.name === "src"
|
|
74
|
+
);
|
|
75
|
+
const nodeUrl = node.attributes[srcAttrIndex].value;
|
|
76
|
+
if (
|
|
77
|
+
// <img/> component
|
|
78
|
+
srcAttrIndex !== -1 &&
|
|
79
|
+
!isAbsoluteUrl(nodeUrl) &&
|
|
80
|
+
!isDataString(nodeUrl)
|
|
81
|
+
) {
|
|
82
|
+
node.attributes[srcAttrIndex].value =
|
|
83
|
+
removeContentDirectoryPath(nodeUrl);
|
|
84
|
+
}
|
|
85
|
+
} else if (
|
|
86
|
+
// ![]() format
|
|
87
|
+
node.type === "image" &&
|
|
88
|
+
node.url &&
|
|
89
|
+
!isAbsoluteUrl(node.url) &&
|
|
90
|
+
!isDataString(node.url)
|
|
91
|
+
) {
|
|
92
|
+
node.url = removeContentDirectoryPath(node.url);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
return tree;
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const file = await remark()
|
|
100
|
+
.use(remarkMdx)
|
|
101
|
+
.use(remarkGfm)
|
|
102
|
+
.use(remarkFrontmatter, ["yaml", "toml"])
|
|
103
|
+
.use(removeContentDirectoryPaths)
|
|
104
|
+
.process(fileContent);
|
|
105
|
+
return String(file);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const removeLeadingSlash = (str: string) => {
|
|
109
|
+
const path = createPathArr(str);
|
|
110
|
+
return path.join("/");
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const createPathArr = (path: string) => {
|
|
114
|
+
return path.split("/").filter((dir) => dir !== "");
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const isDataString = (str: string) => str.startsWith("data:");
|
|
118
|
+
|
|
119
|
+
const getOpenApiTitleAndDescription = (
|
|
120
|
+
openApiFiles: OpenApiFile[],
|
|
121
|
+
openApiMetaField: string
|
|
122
|
+
) => {
|
|
123
|
+
const { operation } = getOpenApiOperationMethodAndEndpoint(
|
|
124
|
+
openApiFiles,
|
|
125
|
+
openApiMetaField
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
if (operation == null) {
|
|
129
|
+
return {};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
title: operation.summary,
|
|
134
|
+
description: operation.description,
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const getOpenApiOperationMethodAndEndpoint = (
|
|
139
|
+
openApiFiles: OpenApiFile[],
|
|
140
|
+
openApiMetaField: string
|
|
141
|
+
) => {
|
|
142
|
+
const { endpoint, method, filename } =
|
|
143
|
+
extractMethodAndEndpoint(openApiMetaField);
|
|
144
|
+
|
|
145
|
+
let path;
|
|
146
|
+
|
|
147
|
+
openApiFiles?.forEach((file) => {
|
|
148
|
+
const openApiFile = file.spec;
|
|
149
|
+
const openApiPath = openApiFile.paths && openApiFile.paths[endpoint];
|
|
150
|
+
const isFilenameOrNone = !filename || filename === file.filename;
|
|
151
|
+
if (openApiPath && isFilenameOrNone) {
|
|
152
|
+
path = openApiPath;
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
if (path == null) {
|
|
157
|
+
return {};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
let operation;
|
|
161
|
+
if (method) {
|
|
162
|
+
operation = path[method.toLowerCase()];
|
|
163
|
+
} else {
|
|
164
|
+
const firstOperationKey = Object.keys(path)[0];
|
|
165
|
+
operation = path[firstOperationKey];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
operation,
|
|
170
|
+
method,
|
|
171
|
+
endpoint,
|
|
172
|
+
};
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const extractMethodAndEndpoint = (openApiMetaField: string) => {
|
|
176
|
+
const methodRegex = /(get|post|put|delete|patch)\s/i;
|
|
177
|
+
const trimmed = openApiMetaField.trim();
|
|
178
|
+
const foundMethod = trimmed.match(methodRegex);
|
|
179
|
+
|
|
180
|
+
const startIndexOfMethod = foundMethod
|
|
181
|
+
? openApiMetaField.indexOf(foundMethod[0])
|
|
182
|
+
: 0;
|
|
183
|
+
const endIndexOfMethod = foundMethod
|
|
184
|
+
? startIndexOfMethod + foundMethod[0].length - 1
|
|
185
|
+
: 0;
|
|
186
|
+
|
|
187
|
+
const filename = openApiMetaField.substring(0, startIndexOfMethod).trim();
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
method: foundMethod ? foundMethod[0].slice(0, -1).toUpperCase() : undefined,
|
|
191
|
+
endpoint: openApiMetaField.substring(endIndexOfMethod).trim(),
|
|
192
|
+
filename: filename ? filename : undefined,
|
|
193
|
+
};
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
function optionallyAddLeadingSlash(path: string) {
|
|
197
|
+
if (path.startsWith("/")) {
|
|
198
|
+
return path;
|
|
199
|
+
}
|
|
200
|
+
return "/" + path;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export const slugToTitle = (slug: string) => {
|
|
204
|
+
const slugArr = slug.split("/");
|
|
205
|
+
let defaultTitle = slugArr[slugArr.length - 1].split("-").join(" "); //replace all dashes
|
|
206
|
+
defaultTitle = defaultTitle.split("_").join(" "); //replace all underscores
|
|
207
|
+
defaultTitle = defaultTitle.charAt(0).toUpperCase() + defaultTitle.slice(1); //capitalize first letter
|
|
208
|
+
return defaultTitle;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
export default createPage;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export const extractMethodAndEndpoint = (
|
|
2
|
+
api: string
|
|
3
|
+
): { method?: string; endpoint: string; filename?: string } => {
|
|
4
|
+
const methodRegex = /(get|post|put|delete|patch)\s/i;
|
|
5
|
+
const trimmed = api.trim();
|
|
6
|
+
const foundMethod = trimmed.match(methodRegex);
|
|
7
|
+
|
|
8
|
+
const startIndexOfMethod = foundMethod ? api.indexOf(foundMethod[0]) : 0;
|
|
9
|
+
const endIndexOfMethod = foundMethod
|
|
10
|
+
? startIndexOfMethod + foundMethod[0].length - 1
|
|
11
|
+
: 0;
|
|
12
|
+
|
|
13
|
+
const filename = api.substring(0, startIndexOfMethod).trim();
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
method: foundMethod ? foundMethod[0].slice(0, -1).toUpperCase() : undefined,
|
|
17
|
+
endpoint: api.substring(endIndexOfMethod).trim(),
|
|
18
|
+
filename: filename ? filename : undefined,
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const getOpenApiOperationMethodAndEndpoint = (
|
|
23
|
+
openApi: any,
|
|
24
|
+
openApiMetaField: string
|
|
25
|
+
) => {
|
|
26
|
+
const { endpoint, method, filename } =
|
|
27
|
+
extractMethodAndEndpoint(openApiMetaField);
|
|
28
|
+
|
|
29
|
+
let path: any;
|
|
30
|
+
|
|
31
|
+
openApi.files?.forEach((file: any) => {
|
|
32
|
+
const openApiFile = file.openapi;
|
|
33
|
+
const openApiPath = openApiFile.paths && openApiFile.paths[endpoint];
|
|
34
|
+
const isFilenameOrNone = !filename || filename === file.name;
|
|
35
|
+
if (openApiPath && isFilenameOrNone) {
|
|
36
|
+
path = openApiPath;
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
if (path == null) {
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let operation;
|
|
45
|
+
if (method) {
|
|
46
|
+
operation = path[method.toLowerCase()];
|
|
47
|
+
} else {
|
|
48
|
+
const firstOperationKey = Object.keys(path)[0];
|
|
49
|
+
operation = path[firstOperationKey];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
operation,
|
|
54
|
+
method,
|
|
55
|
+
endpoint,
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const getOpenApiTitleAndDescription = (openApi, openApiMetaField) => {
|
|
60
|
+
if (openApi == null || !openApiMetaField || openApiMetaField == null) {
|
|
61
|
+
return {};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const { operation } = getOpenApiOperationMethodAndEndpoint(
|
|
65
|
+
openApi,
|
|
66
|
+
openApiMetaField
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
if (operation == null) {
|
|
70
|
+
return {};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
title: operation.summary,
|
|
75
|
+
description: operation.description,
|
|
76
|
+
};
|
|
77
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { promises as _promises } from "fs";
|
|
2
|
+
import { pathExists } from "fs-extra";
|
|
3
|
+
import pathUtil from "path";
|
|
4
|
+
|
|
5
|
+
const { readFile } = _promises;
|
|
6
|
+
|
|
7
|
+
// TODO: Put in prebuild package
|
|
8
|
+
export const getConfigPath = async (
|
|
9
|
+
contentDirectoryPath: string
|
|
10
|
+
): Promise<string | null> => {
|
|
11
|
+
if (await pathExists(pathUtil.join(contentDirectoryPath, "mint.json"))) {
|
|
12
|
+
return pathUtil.join(contentDirectoryPath, "mint.json");
|
|
13
|
+
}
|
|
14
|
+
return null;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// TODO: Put in prebuild package
|
|
18
|
+
export const getConfigObj = async (
|
|
19
|
+
contentDirectoryPath: string
|
|
20
|
+
): Promise<any> => {
|
|
21
|
+
const configPath = await getConfigPath(contentDirectoryPath);
|
|
22
|
+
let configObj = null;
|
|
23
|
+
if (configPath) {
|
|
24
|
+
const configContents = await readFile(configPath);
|
|
25
|
+
configObj = await JSON.parse(configContents.toString());
|
|
26
|
+
}
|
|
27
|
+
return configObj;
|
|
28
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export function toTitleCase(text: string) {
|
|
2
|
+
const smallWords = /^(a|an|and|as|at|but|by|en|for|if|in|nor|of|on|or|per|the|to|v.?|vs.?|via)$/i;
|
|
3
|
+
const alphanumericPattern = /([A-Za-z0-9\u00C0-\u00FF])/;
|
|
4
|
+
const wordSeparators = /([ :–—-])/;
|
|
5
|
+
|
|
6
|
+
return text
|
|
7
|
+
.split(wordSeparators)
|
|
8
|
+
.map(function (current, index, array) {
|
|
9
|
+
if (
|
|
10
|
+
// Check for small words
|
|
11
|
+
current.search(smallWords) > -1 &&
|
|
12
|
+
// Skip first and last word
|
|
13
|
+
index !== 0 &&
|
|
14
|
+
index !== array.length - 1 &&
|
|
15
|
+
// Ignore title end and subtitle start
|
|
16
|
+
array[index - 3] !== ':' &&
|
|
17
|
+
array[index + 1] !== ':' &&
|
|
18
|
+
// Ignore small words that start a hyphenated phrase
|
|
19
|
+
(array[index + 1] !== '-' || (array[index - 1] === '-' && array[index + 1] === '-'))
|
|
20
|
+
) {
|
|
21
|
+
return current.toLowerCase();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Ignore intentional capitalization
|
|
25
|
+
if (current.substring(1).search(/[A-Z]|\../) > -1) {
|
|
26
|
+
return current;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Ignore URLs
|
|
30
|
+
if (array[index + 1] === ':' && array[index + 2] !== '') {
|
|
31
|
+
return current;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Capitalize the first letter
|
|
35
|
+
return current.replace(alphanumericPattern, function (match: string) {
|
|
36
|
+
return match.toUpperCase();
|
|
37
|
+
});
|
|
38
|
+
})
|
|
39
|
+
.join('');
|
|
40
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type PotentialFileCategory =
|
|
2
|
+
| "page"
|
|
3
|
+
| "snippet"
|
|
4
|
+
| "mintConfig"
|
|
5
|
+
| "potentialYamlOpenApiSpec"
|
|
6
|
+
| "potentialJsonOpenApiSpec"
|
|
7
|
+
| "staticFile";
|
|
8
|
+
|
|
9
|
+
export type FileCategory =
|
|
10
|
+
| "page"
|
|
11
|
+
| "snippet"
|
|
12
|
+
| "mintConfig"
|
|
13
|
+
| "openApi"
|
|
14
|
+
| "staticFile";
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import SwaggerParser from "@apidevtools/swagger-parser";
|
|
2
|
+
import { promises as _promises } from "fs";
|
|
3
|
+
|
|
4
|
+
const { readdir, stat } = _promises;
|
|
5
|
+
|
|
6
|
+
export const getFileExtension = (filename: string) => {
|
|
7
|
+
return (
|
|
8
|
+
filename.substring(filename.lastIndexOf(".") + 1, filename.length) ||
|
|
9
|
+
filename
|
|
10
|
+
);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type OpenApiCheckResult = {
|
|
14
|
+
spec: any;
|
|
15
|
+
isOpenApi: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const openApiCheck = async (
|
|
19
|
+
path: string
|
|
20
|
+
): Promise<OpenApiCheckResult> => {
|
|
21
|
+
let spec;
|
|
22
|
+
let isOpenApi = false;
|
|
23
|
+
try {
|
|
24
|
+
spec = await SwaggerParser.validate(path);
|
|
25
|
+
isOpenApi = true;
|
|
26
|
+
} catch {
|
|
27
|
+
// not valid openApi
|
|
28
|
+
}
|
|
29
|
+
return { spec, isOpenApi };
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const filterOutNullInGroup = (group: MintNavigation) => {
|
|
33
|
+
const newPages = filterOutNullInPages(group.pages);
|
|
34
|
+
const newGroup = {
|
|
35
|
+
...group,
|
|
36
|
+
pages: newPages,
|
|
37
|
+
};
|
|
38
|
+
return newGroup;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const filterOutNullInPages = (pages: (MintNavigationEntry | null)[]) => {
|
|
42
|
+
const newPages: MintNavigationEntry[] = [];
|
|
43
|
+
pages.forEach((page) => {
|
|
44
|
+
if (page == null) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (page.hasOwnProperty("pages")) {
|
|
48
|
+
const group = page as MintNavigation;
|
|
49
|
+
const newGroup = filterOutNullInGroup(group);
|
|
50
|
+
newPages.push(newGroup);
|
|
51
|
+
} else {
|
|
52
|
+
newPages.push(page);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return newPages;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const getFileList = async (dirName: string, og = dirName) => {
|
|
60
|
+
let files: string[] = [];
|
|
61
|
+
const items = await readdir(dirName, { withFileTypes: true });
|
|
62
|
+
|
|
63
|
+
for (const item of items) {
|
|
64
|
+
if (item.isDirectory()) {
|
|
65
|
+
files = [...files, ...(await getFileList(`${dirName}/${item.name}`, og))];
|
|
66
|
+
} else {
|
|
67
|
+
const path = `${dirName}/${item.name}`;
|
|
68
|
+
const name = path.replace(og, "");
|
|
69
|
+
files.push(name);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return files;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const isFileSizeValid = async (
|
|
77
|
+
path: string,
|
|
78
|
+
maxFileSizeInMb: number
|
|
79
|
+
): Promise<boolean> => {
|
|
80
|
+
const maxFileSizeBytes = maxFileSizeInMb * 1000000;
|
|
81
|
+
const stats = await stat(path);
|
|
82
|
+
return stats.size <= maxFileSizeBytes;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export function isError(obj: unknown) {
|
|
86
|
+
return Object.prototype.toString.call(obj) === "[object Error]";
|
|
87
|
+
}
|
package/src/types.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
type MintPage = {
|
|
2
|
+
title?: string;
|
|
3
|
+
description?: string;
|
|
4
|
+
markdown?: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
type MintNavigation = {
|
|
8
|
+
group: string;
|
|
9
|
+
version?: string;
|
|
10
|
+
pages: MintNavigationEntry[];
|
|
11
|
+
};
|
|
12
|
+
type MintNavigationEntry = string | MintNavigation;
|
|
13
|
+
|
|
14
|
+
type ScrapePageFn = (
|
|
15
|
+
html: string,
|
|
16
|
+
origin: string,
|
|
17
|
+
cliDir: string,
|
|
18
|
+
imageBaseDir: string,
|
|
19
|
+
overwrite: boolean,
|
|
20
|
+
version: string | undefined
|
|
21
|
+
) => Promise<MintPage>;
|
|
22
|
+
|
|
23
|
+
type ScrapeSectionFn = (
|
|
24
|
+
html: string,
|
|
25
|
+
origin: string,
|
|
26
|
+
cliDir: string,
|
|
27
|
+
imageBaseDir: string,
|
|
28
|
+
overwrite: boolean,
|
|
29
|
+
version: string | undefined
|
|
30
|
+
) => Promise<MintNavigationEntry[]>;
|
|
31
|
+
|
|
32
|
+
type OpenApiFile = {
|
|
33
|
+
filename: string;
|
|
34
|
+
spec: any;
|
|
35
|
+
};
|
package/src/util.ts
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
2
|
+
import Ora, { Ora as OraType } from "ora";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import shell from "shelljs";
|
|
5
|
+
|
|
6
|
+
export const MintConfig = (
|
|
7
|
+
name: string,
|
|
8
|
+
color: string,
|
|
9
|
+
ctaName: string,
|
|
10
|
+
ctaUrl: string,
|
|
11
|
+
filename: string
|
|
12
|
+
) => {
|
|
13
|
+
return {
|
|
14
|
+
name,
|
|
15
|
+
logo: "",
|
|
16
|
+
favicon: "",
|
|
17
|
+
colors: {
|
|
18
|
+
primary: color,
|
|
19
|
+
},
|
|
20
|
+
topbarLinks: [],
|
|
21
|
+
topbarCtaButton: {
|
|
22
|
+
name: ctaName,
|
|
23
|
+
url: ctaUrl,
|
|
24
|
+
},
|
|
25
|
+
anchors: [],
|
|
26
|
+
navigation: [
|
|
27
|
+
{
|
|
28
|
+
group: "Home",
|
|
29
|
+
pages: [filename],
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
// footerSocials: {}, // support object type for footer tyoes
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const Page = (
|
|
37
|
+
title: string,
|
|
38
|
+
description?: string,
|
|
39
|
+
markdown?: string
|
|
40
|
+
) => {
|
|
41
|
+
// If we are an empty String we want to add two quotes,
|
|
42
|
+
// if we added as we went we would detect the first quote
|
|
43
|
+
// as the closing quote.
|
|
44
|
+
const startsWithQuote = title.startsWith('"');
|
|
45
|
+
const endsWithQuote = title.startsWith('"');
|
|
46
|
+
if (!startsWithQuote) {
|
|
47
|
+
title = '"' + title;
|
|
48
|
+
}
|
|
49
|
+
if (!endsWithQuote) {
|
|
50
|
+
title = title + '"';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const optionalDescription = description
|
|
54
|
+
? `\ndescription: "${description}"`
|
|
55
|
+
: "";
|
|
56
|
+
return `---\ntitle: ${title}${optionalDescription}\n---\n\n${markdown}`;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export function getOrigin(url: string) {
|
|
60
|
+
// eg. https://google.com -> https://google.com
|
|
61
|
+
// https://google.com/page -> https://google.com
|
|
62
|
+
return new URL(url).origin;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function objToReadableString(objs: MintNavigationEntry[]) {
|
|
66
|
+
// Two spaces as indentation
|
|
67
|
+
return objs.map((obj) => JSON.stringify(obj, null, 2)).join(",\n");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const toFilename = (title: string) => {
|
|
71
|
+
// Gets rid of special characters at the start and end
|
|
72
|
+
// of the name by converting to spaces then using trim.
|
|
73
|
+
return title
|
|
74
|
+
.replace(/[^a-z0-9]/gi, " ")
|
|
75
|
+
.trim()
|
|
76
|
+
.replace(/ /g, "-")
|
|
77
|
+
.toLowerCase();
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const addMdx = (fileName: string) => {
|
|
81
|
+
if (fileName.endsWith(".mdx")) {
|
|
82
|
+
return fileName;
|
|
83
|
+
}
|
|
84
|
+
return fileName + ".mdx";
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export const createPage = (
|
|
88
|
+
title: string,
|
|
89
|
+
description?: string,
|
|
90
|
+
markdown?: string,
|
|
91
|
+
overwrite = false,
|
|
92
|
+
rootDir = "",
|
|
93
|
+
fileName?: string
|
|
94
|
+
) => {
|
|
95
|
+
const writePath = path.join(rootDir, addMdx(fileName || toFilename(title)));
|
|
96
|
+
|
|
97
|
+
// Create the folders needed if they're missing
|
|
98
|
+
mkdirSync(rootDir, { recursive: true });
|
|
99
|
+
|
|
100
|
+
// Write the page to memory
|
|
101
|
+
if (overwrite) {
|
|
102
|
+
writeFileSync(writePath, Page(title, description, markdown));
|
|
103
|
+
console.log("✏️ - " + writePath);
|
|
104
|
+
} else {
|
|
105
|
+
try {
|
|
106
|
+
writeFileSync(writePath, Page(title, description, markdown), {
|
|
107
|
+
flag: "wx",
|
|
108
|
+
});
|
|
109
|
+
console.log("✏️ - " + writePath);
|
|
110
|
+
} catch (e) {
|
|
111
|
+
// We do a try-catch instead of an if-statement to avoid a race condition
|
|
112
|
+
// of the file being created after we started writing.
|
|
113
|
+
if (e.code === "EEXIST") {
|
|
114
|
+
console.log(`❌ Skipping existing file ${writePath}`);
|
|
115
|
+
} else {
|
|
116
|
+
console.error(e);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export const buildLogger = (startText = ""): OraType => {
|
|
123
|
+
const logger = Ora().start(startText);
|
|
124
|
+
return logger;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export const getFileExtension = (filename: string) => {
|
|
128
|
+
const ext = filename.substring(
|
|
129
|
+
filename.lastIndexOf(".") + 1,
|
|
130
|
+
filename.length
|
|
131
|
+
);
|
|
132
|
+
if (filename === ext) return undefined;
|
|
133
|
+
return ext;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export const fileBelongsInPagesFolder = (filename: string) => {
|
|
137
|
+
const extension = getFileExtension(filename);
|
|
138
|
+
return (
|
|
139
|
+
extension &&
|
|
140
|
+
(extension === "mdx" || extension === "md" || extension === "tsx")
|
|
141
|
+
);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export const ensureYarn = (logger: OraType) => {
|
|
145
|
+
const yarnInstalled = shell.which("yarn");
|
|
146
|
+
if (!yarnInstalled) {
|
|
147
|
+
logger.fail(`yarn must be installed, run
|
|
148
|
+
|
|
149
|
+
npm install --global yarn
|
|
150
|
+
|
|
151
|
+
`);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
};
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
// "extends": "@mintlify/ts-config",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"module": "ES2020",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"target": "es2020",
|
|
7
|
+
"lib": ["es2020", "dom", "ES2021.String"],
|
|
8
|
+
"sourceMap": true,
|
|
9
|
+
"outDir": "bin",
|
|
10
|
+
"baseUrl": ".",
|
|
11
|
+
"paths": {
|
|
12
|
+
"*": ["node_modules/*", "src/types/*"]
|
|
13
|
+
},
|
|
14
|
+
"emitDecoratorMetadata": true,
|
|
15
|
+
"experimentalDecorators": true,
|
|
16
|
+
"esModuleInterop": true
|
|
17
|
+
},
|
|
18
|
+
"include": ["src/**/*"]
|
|
19
|
+
}
|