@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,196 @@
|
|
|
1
|
+
import Chalk from "chalk";
|
|
2
|
+
import child_process from "child_process";
|
|
3
|
+
import open from "open";
|
|
4
|
+
import fse, { pathExists } from "fs-extra";
|
|
5
|
+
import inquirer from "inquirer";
|
|
6
|
+
import { isInternetAvailable } from "is-internet-available";
|
|
7
|
+
import path from "path";
|
|
8
|
+
import shell from "shelljs";
|
|
9
|
+
import { Octokit } from "@octokit/rest";
|
|
10
|
+
import {
|
|
11
|
+
CLIENT_PATH,
|
|
12
|
+
HOME_DIR,
|
|
13
|
+
DOT_MINTLIFY,
|
|
14
|
+
CMD_EXEC_PATH,
|
|
15
|
+
TARGET_MINT_VERSION,
|
|
16
|
+
VERSION_PATH,
|
|
17
|
+
MINT_PATH,
|
|
18
|
+
} from "../constants.js";
|
|
19
|
+
import { buildLogger, ensureYarn } from "../util.js";
|
|
20
|
+
import listener from "./listener/index.js";
|
|
21
|
+
import { ArgumentsCamelCase } from "yargs";
|
|
22
|
+
import { getConfigPath } from "./listener/utils/mintConfigFile.js";
|
|
23
|
+
import type { Ora as OraType } from "ora";
|
|
24
|
+
|
|
25
|
+
const nodeModulesExists = async () => {
|
|
26
|
+
return pathExists(path.join(DOT_MINTLIFY, "mint", "client", "node_modules"));
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const promptForYarn = async () => {
|
|
30
|
+
const yarnInstalled = shell.which("yarn");
|
|
31
|
+
if (!yarnInstalled) {
|
|
32
|
+
await inquirer
|
|
33
|
+
.prompt([
|
|
34
|
+
{
|
|
35
|
+
type: "confirm",
|
|
36
|
+
name: "confirm",
|
|
37
|
+
message: "yarn must be globally installed. Install yarn?",
|
|
38
|
+
default: true,
|
|
39
|
+
},
|
|
40
|
+
])
|
|
41
|
+
.then(({ confirm }) => {
|
|
42
|
+
if (confirm) {
|
|
43
|
+
shell.exec("npm install --global yarn");
|
|
44
|
+
} else {
|
|
45
|
+
console.log("Installation cancelled.");
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const downloadTargetMint = async (logger: OraType) => {
|
|
52
|
+
fse.emptyDirSync(MINT_PATH);
|
|
53
|
+
|
|
54
|
+
logger.text = "Downloading Mintlify framework...";
|
|
55
|
+
|
|
56
|
+
const octokit = new Octokit();
|
|
57
|
+
const downloadRes = await octokit.repos.downloadTarballArchive({
|
|
58
|
+
owner: "mintlify",
|
|
59
|
+
repo: "mint",
|
|
60
|
+
ref: TARGET_MINT_VERSION,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
logger.text = "Extracting Mintlify framework...";
|
|
64
|
+
const TAR_PATH = path.join(MINT_PATH, "mint.tar.gz");
|
|
65
|
+
fse.writeFileSync(TAR_PATH, Buffer.from(downloadRes.data as any));
|
|
66
|
+
|
|
67
|
+
// strip-components 1 removes the top level directory from the unzipped content
|
|
68
|
+
// which is a folder with the release sha
|
|
69
|
+
fse.mkdirSync(path.join(MINT_PATH, "mint-tmp"));
|
|
70
|
+
shell.exec("tar -xzf mint.tar.gz -C mint-tmp --strip-components 1", {
|
|
71
|
+
silent: true,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
fse.removeSync(TAR_PATH);
|
|
75
|
+
|
|
76
|
+
fse.moveSync(
|
|
77
|
+
path.join(MINT_PATH, "mint-tmp", "client"),
|
|
78
|
+
path.join(CLIENT_PATH)
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
fse.writeFileSync(VERSION_PATH, TARGET_MINT_VERSION);
|
|
82
|
+
|
|
83
|
+
// Delete unnecessary content downloaded from GitHub
|
|
84
|
+
fse.removeSync(path.join(MINT_PATH, "mint-tmp"));
|
|
85
|
+
|
|
86
|
+
logger.text = "Installing dependencies...";
|
|
87
|
+
|
|
88
|
+
ensureYarn(logger);
|
|
89
|
+
shell.cd(CLIENT_PATH);
|
|
90
|
+
shell.exec("yarn", { silent: true });
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const checkForMintJson = async (logger: OraType) => {
|
|
94
|
+
const configPath = await getConfigPath(CMD_EXEC_PATH);
|
|
95
|
+
if (configPath == null) {
|
|
96
|
+
logger.fail("Must be ran in a directory where a mint.json file exists.");
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
return;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const dev = async (argv: ArgumentsCamelCase) => {
|
|
103
|
+
shell.cd(HOME_DIR);
|
|
104
|
+
await promptForYarn();
|
|
105
|
+
const logger = buildLogger("Preparing local Mintlify instance...");
|
|
106
|
+
await fse.ensureDir(MINT_PATH);
|
|
107
|
+
shell.cd(MINT_PATH);
|
|
108
|
+
|
|
109
|
+
const internet = await isInternetAvailable();
|
|
110
|
+
if (!internet && !(await pathExists(CLIENT_PATH))) {
|
|
111
|
+
logger.fail(
|
|
112
|
+
"Running mintlify dev for the first time requires an internet connection."
|
|
113
|
+
);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (internet) {
|
|
118
|
+
const mintVersionExists = await pathExists(VERSION_PATH);
|
|
119
|
+
|
|
120
|
+
let needToDownloadTargetMint = !mintVersionExists;
|
|
121
|
+
|
|
122
|
+
if (mintVersionExists) {
|
|
123
|
+
const currVersion = fse.readFileSync(VERSION_PATH, "utf8");
|
|
124
|
+
if (currVersion !== TARGET_MINT_VERSION) {
|
|
125
|
+
needToDownloadTargetMint = true;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (needToDownloadTargetMint) {
|
|
130
|
+
await downloadTargetMint(logger);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!(await nodeModulesExists())) {
|
|
135
|
+
if (!internet) {
|
|
136
|
+
logger.fail(`Dependencies are missing and you are offline. Connect to the internet and run
|
|
137
|
+
|
|
138
|
+
mintlify install
|
|
139
|
+
|
|
140
|
+
`);
|
|
141
|
+
} else {
|
|
142
|
+
logger.fail(`Dependencies were not installed correctly, run
|
|
143
|
+
|
|
144
|
+
mintlify install
|
|
145
|
+
|
|
146
|
+
`);
|
|
147
|
+
}
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
await checkForMintJson(logger);
|
|
151
|
+
shell.cd(CLIENT_PATH);
|
|
152
|
+
const relativePath = path.relative(CLIENT_PATH, CMD_EXEC_PATH);
|
|
153
|
+
child_process.spawnSync("yarn preconfigure", [relativePath], { shell: true });
|
|
154
|
+
logger.succeed("Local Mintlify instance is ready. Launching your site...");
|
|
155
|
+
run((argv.port as string) || "3000");
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const run = (port: string) => {
|
|
159
|
+
shell.cd(CLIENT_PATH);
|
|
160
|
+
|
|
161
|
+
// next-remote-watch can only receive ports as env variables
|
|
162
|
+
// https://github.com/hashicorp/next-remote-watch/issues/23
|
|
163
|
+
const mintlifyDevProcess = child_process.spawn("npm run dev-watch", {
|
|
164
|
+
env: {
|
|
165
|
+
...process.env,
|
|
166
|
+
PORT: port,
|
|
167
|
+
},
|
|
168
|
+
cwd: CLIENT_PATH,
|
|
169
|
+
stdio: "pipe",
|
|
170
|
+
shell: true,
|
|
171
|
+
});
|
|
172
|
+
mintlifyDevProcess.stdout.on("data", (data) => {
|
|
173
|
+
const output = data.toString();
|
|
174
|
+
console.log(output);
|
|
175
|
+
if (output.startsWith("> Ready on http://localhost:")) {
|
|
176
|
+
console.log(
|
|
177
|
+
`🌿 ${Chalk.green(
|
|
178
|
+
`Your local preview is available at http://localhost:${port}`
|
|
179
|
+
)}`
|
|
180
|
+
);
|
|
181
|
+
console.log(
|
|
182
|
+
`🌿 ${Chalk.green("Press Ctrl+C any time to stop the local preview.")}`
|
|
183
|
+
);
|
|
184
|
+
open(`http://localhost:${port}`);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
const onExit = () => {
|
|
188
|
+
mintlifyDevProcess.kill("SIGINT");
|
|
189
|
+
process.exit(0);
|
|
190
|
+
};
|
|
191
|
+
process.on("SIGINT", onExit);
|
|
192
|
+
process.on("SIGTERM", onExit);
|
|
193
|
+
listener();
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
export default dev;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// TODO - put in prebuild package
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
import { getFileExtension, openApiCheck, getFileList } from "./utils.js";
|
|
5
|
+
import { PotentialFileCategory } from "./utils/types.js";
|
|
6
|
+
|
|
7
|
+
export const categorizeFiles = async (contentDirectoryPath: string) => {
|
|
8
|
+
const allFilesInCmdExecutionPath: string[] = await getFileList(
|
|
9
|
+
contentDirectoryPath
|
|
10
|
+
);
|
|
11
|
+
const contentFilenames: string[] = [];
|
|
12
|
+
const staticFilenames: string[] = [];
|
|
13
|
+
const promises: Promise<void>[] = [];
|
|
14
|
+
const openApiFiles: OpenApiFile[] = [];
|
|
15
|
+
const snippets: string[] = [];
|
|
16
|
+
allFilesInCmdExecutionPath.forEach((filename: string) => {
|
|
17
|
+
promises.push(
|
|
18
|
+
(async () => {
|
|
19
|
+
const extension = getFileExtension(filename);
|
|
20
|
+
let isOpenApi = false;
|
|
21
|
+
if (extension && (extension === "mdx" || extension === "md")) {
|
|
22
|
+
if (filename.startsWith("/_snippets")) {
|
|
23
|
+
snippets.push(filename);
|
|
24
|
+
} else {
|
|
25
|
+
contentFilenames.push(filename);
|
|
26
|
+
}
|
|
27
|
+
} else if (
|
|
28
|
+
extension &&
|
|
29
|
+
(extension === "json" || extension === "yaml" || extension === "yml")
|
|
30
|
+
) {
|
|
31
|
+
const openApiInfo = await openApiCheck(
|
|
32
|
+
path.join(contentDirectoryPath, filename)
|
|
33
|
+
);
|
|
34
|
+
isOpenApi = openApiInfo.isOpenApi;
|
|
35
|
+
if (isOpenApi) {
|
|
36
|
+
const fileName = path.parse(filename).base;
|
|
37
|
+
openApiFiles.push({
|
|
38
|
+
filename: fileName.substring(0, fileName.lastIndexOf(".")),
|
|
39
|
+
spec: openApiInfo.spec,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
} else if (!filename.endsWith("mint.json") && !isOpenApi) {
|
|
43
|
+
// all other files
|
|
44
|
+
staticFilenames.push(filename);
|
|
45
|
+
}
|
|
46
|
+
})()
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
await Promise.all(promises);
|
|
50
|
+
|
|
51
|
+
return { contentFilenames, staticFilenames, openApiFiles, snippets };
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const excludedMdFiles = ["readme", "license", "contributing", "contribute"];
|
|
55
|
+
|
|
56
|
+
const supportedStaticFileExtensions = [
|
|
57
|
+
".jpeg",
|
|
58
|
+
".jpg",
|
|
59
|
+
".jfif",
|
|
60
|
+
".pjpeg",
|
|
61
|
+
".pjp",
|
|
62
|
+
".png",
|
|
63
|
+
".svg",
|
|
64
|
+
".svgz",
|
|
65
|
+
".ico",
|
|
66
|
+
".webp",
|
|
67
|
+
".gif",
|
|
68
|
+
".apng",
|
|
69
|
+
".avif",
|
|
70
|
+
".bmp",
|
|
71
|
+
".mp4",
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
export const getCategory = (filePath: string): PotentialFileCategory => {
|
|
75
|
+
filePath = filePath.toLowerCase();
|
|
76
|
+
|
|
77
|
+
const parsed = path.parse(filePath);
|
|
78
|
+
|
|
79
|
+
if (parsed.base === "mint.json") {
|
|
80
|
+
return "mintConfig";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const fileName = parsed.name;
|
|
84
|
+
const extension = parsed.ext;
|
|
85
|
+
if (
|
|
86
|
+
filePath.startsWith("_snippets") &&
|
|
87
|
+
(extension === ".mdx" || extension === ".md")
|
|
88
|
+
) {
|
|
89
|
+
return "snippet";
|
|
90
|
+
} else if (extension === ".mdx") {
|
|
91
|
+
return "page";
|
|
92
|
+
} else if (extension === ".md") {
|
|
93
|
+
// Exclude common markdown files people don't want on their docs website
|
|
94
|
+
if (excludedMdFiles.includes(fileName)) {
|
|
95
|
+
throw new Error("Excluded Md File");
|
|
96
|
+
}
|
|
97
|
+
return "page";
|
|
98
|
+
} else if (extension === ".yaml" || extension === ".yml") {
|
|
99
|
+
return "potentialYamlOpenApiSpec";
|
|
100
|
+
} else if (extension === ".json") {
|
|
101
|
+
return "potentialJsonOpenApiSpec";
|
|
102
|
+
} else if (supportedStaticFileExtensions.includes(extension)) {
|
|
103
|
+
return "staticFile";
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
throw new Error("Unsupported File Type, No change enacted");
|
|
107
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// TODO - add types
|
|
2
|
+
import { promises as _promises } from "fs";
|
|
3
|
+
import { outputFile } from "fs-extra";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import createPage from "./utils/createPage.js";
|
|
6
|
+
import { categorizeFiles } from "./categorize.js";
|
|
7
|
+
import { CMD_EXEC_PATH } from "../../constants.js";
|
|
8
|
+
import { getConfigObj } from "./utils/mintConfigFile.js";
|
|
9
|
+
|
|
10
|
+
const { readFile } = _promises;
|
|
11
|
+
|
|
12
|
+
type DecoratedMintNavigation = DecoratedMintNavigationEntry[];
|
|
13
|
+
|
|
14
|
+
type DecoratedMintNavigationEntry = {
|
|
15
|
+
group: string;
|
|
16
|
+
version?: string;
|
|
17
|
+
pages: DecoratedMintNavigationEntryChild[];
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type DecoratedMintNavigationEntryChild =
|
|
21
|
+
| DecoratedMintNavigationEntry
|
|
22
|
+
| PageMetadata;
|
|
23
|
+
|
|
24
|
+
type PageMetadata = Record<PageMetadataKeys, string>;
|
|
25
|
+
|
|
26
|
+
const pageMetadataKeys = [
|
|
27
|
+
"title",
|
|
28
|
+
"description",
|
|
29
|
+
"sidebarTitle",
|
|
30
|
+
"href",
|
|
31
|
+
"api",
|
|
32
|
+
"openapi",
|
|
33
|
+
"contentType",
|
|
34
|
+
"auth",
|
|
35
|
+
"version",
|
|
36
|
+
"mode",
|
|
37
|
+
"hideFooterPagination",
|
|
38
|
+
"authors",
|
|
39
|
+
"lastUpdatedDate",
|
|
40
|
+
"createdDate",
|
|
41
|
+
"size",
|
|
42
|
+
] as const;
|
|
43
|
+
|
|
44
|
+
type PageMetadataKeys = typeof pageMetadataKeys[number];
|
|
45
|
+
|
|
46
|
+
const generateDecoratedMintNavigationFromPages = (
|
|
47
|
+
filenamePageMetadataMap: Record<string, PageMetadata>,
|
|
48
|
+
mintConfigNav: MintNavigation[]
|
|
49
|
+
): DecoratedMintNavigation => {
|
|
50
|
+
const filenames = Object.keys(filenamePageMetadataMap);
|
|
51
|
+
const createNav = (nav: MintNavigation): DecoratedMintNavigationEntry => {
|
|
52
|
+
return {
|
|
53
|
+
group: nav.group,
|
|
54
|
+
version: nav?.version,
|
|
55
|
+
pages: nav.pages.map((page: MintNavigationEntry) => {
|
|
56
|
+
if (typeof page === "string" && filenames.includes(page)) {
|
|
57
|
+
return filenamePageMetadataMap[page];
|
|
58
|
+
}
|
|
59
|
+
return createNav(page as MintNavigation);
|
|
60
|
+
}),
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return mintConfigNav.map((nav) => createNav(nav));
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const createFilenamePageMetadataMap = async (
|
|
68
|
+
contentDirectoryPath: string,
|
|
69
|
+
contentFilenames: string[],
|
|
70
|
+
openApiFiles: OpenApiFile[],
|
|
71
|
+
clientPath?: string,
|
|
72
|
+
writeFiles = false
|
|
73
|
+
) => {
|
|
74
|
+
let pagesAcc = {};
|
|
75
|
+
const contentPromises: Promise<void>[] = [];
|
|
76
|
+
contentFilenames.forEach((filename) => {
|
|
77
|
+
contentPromises.push(
|
|
78
|
+
(async () => {
|
|
79
|
+
const sourcePath = path.join(contentDirectoryPath, filename);
|
|
80
|
+
|
|
81
|
+
const contentStr = (await readFile(sourcePath)).toString();
|
|
82
|
+
const { slug, pageMetadata, pageContent } = await createPage(
|
|
83
|
+
filename,
|
|
84
|
+
contentStr,
|
|
85
|
+
contentDirectoryPath,
|
|
86
|
+
openApiFiles
|
|
87
|
+
);
|
|
88
|
+
if (clientPath && writeFiles) {
|
|
89
|
+
const targetPath = path.join(clientPath, "src", "_props", filename);
|
|
90
|
+
await outputFile(targetPath, pageContent, {
|
|
91
|
+
flag: "w",
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
pagesAcc = {
|
|
95
|
+
...pagesAcc,
|
|
96
|
+
[slug]: pageMetadata,
|
|
97
|
+
};
|
|
98
|
+
})()
|
|
99
|
+
);
|
|
100
|
+
});
|
|
101
|
+
await Promise.all(contentPromises);
|
|
102
|
+
return pagesAcc;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export const generateNav = async () => {
|
|
106
|
+
const { contentFilenames, openApiFiles } = await categorizeFiles(
|
|
107
|
+
CMD_EXEC_PATH
|
|
108
|
+
);
|
|
109
|
+
const [filenamePageMetadataMap, configObj] = await Promise.all([
|
|
110
|
+
createFilenamePageMetadataMap(
|
|
111
|
+
CMD_EXEC_PATH,
|
|
112
|
+
contentFilenames,
|
|
113
|
+
openApiFiles
|
|
114
|
+
),
|
|
115
|
+
getConfigObj(CMD_EXEC_PATH),
|
|
116
|
+
]);
|
|
117
|
+
return generateDecoratedMintNavigationFromPages(
|
|
118
|
+
filenamePageMetadataMap,
|
|
119
|
+
configObj?.navigation
|
|
120
|
+
);
|
|
121
|
+
};
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import chokidar from "chokidar";
|
|
2
|
+
import fse from "fs-extra";
|
|
3
|
+
import pathUtil from "path";
|
|
4
|
+
import Chalk from "chalk";
|
|
5
|
+
import mintValidation from "@mintlify/validation";
|
|
6
|
+
import { isFileSizeValid, openApiCheck } from "./utils.js";
|
|
7
|
+
import { updateGeneratedNav, updateOpenApiFiles } from "./update.js";
|
|
8
|
+
import { CLIENT_PATH, CMD_EXEC_PATH } from "../../constants.js";
|
|
9
|
+
import { promises as _promises } from "fs";
|
|
10
|
+
import createPage from "./utils/createPage.js";
|
|
11
|
+
import { getCategory } from "./categorize.js";
|
|
12
|
+
import { PotentialFileCategory, FileCategory } from "./utils/types.js";
|
|
13
|
+
|
|
14
|
+
const { readFile } = _promises;
|
|
15
|
+
|
|
16
|
+
const listener = () => {
|
|
17
|
+
chokidar
|
|
18
|
+
.watch(CMD_EXEC_PATH, {
|
|
19
|
+
ignoreInitial: true,
|
|
20
|
+
ignored: ["node_modules", ".git", ".idea"],
|
|
21
|
+
cwd: CMD_EXEC_PATH,
|
|
22
|
+
})
|
|
23
|
+
.on("add", async (filename: string) => {
|
|
24
|
+
try {
|
|
25
|
+
const category = await onUpdateEvent(filename);
|
|
26
|
+
switch (category) {
|
|
27
|
+
case "page":
|
|
28
|
+
console.log("New page detected: ", filename);
|
|
29
|
+
break;
|
|
30
|
+
case "snippet":
|
|
31
|
+
console.log("New snippet detected: ", filename);
|
|
32
|
+
break;
|
|
33
|
+
case "mintConfig":
|
|
34
|
+
console.log("Config added");
|
|
35
|
+
break;
|
|
36
|
+
case "openApi":
|
|
37
|
+
console.log("OpenApi spec added: ", filename);
|
|
38
|
+
break;
|
|
39
|
+
case "staticFile":
|
|
40
|
+
console.log("Static file added: ", filename);
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error(error.message);
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
.on("change", async (filename: string) => {
|
|
48
|
+
try {
|
|
49
|
+
const category = await onUpdateEvent(filename);
|
|
50
|
+
switch (category) {
|
|
51
|
+
case "page":
|
|
52
|
+
console.log("Page edited: ", filename);
|
|
53
|
+
break;
|
|
54
|
+
case "snippet":
|
|
55
|
+
console.log("Snippet edited: ", filename);
|
|
56
|
+
break;
|
|
57
|
+
case "mintConfig":
|
|
58
|
+
console.log("Config edited");
|
|
59
|
+
break;
|
|
60
|
+
case "openApi":
|
|
61
|
+
console.log("OpenApi spec edited: ", filename);
|
|
62
|
+
break;
|
|
63
|
+
case "staticFile":
|
|
64
|
+
console.log("Static file edited: ", filename);
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error(error.message);
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
.on("unlink", async (filename: string) => {
|
|
72
|
+
try {
|
|
73
|
+
const potentialCategory = getCategory(filename);
|
|
74
|
+
const targetPath = getTargetPath(potentialCategory, filename);
|
|
75
|
+
if (
|
|
76
|
+
potentialCategory === "page" ||
|
|
77
|
+
potentialCategory === "snippet" ||
|
|
78
|
+
potentialCategory === "mintConfig" ||
|
|
79
|
+
potentialCategory === "staticFile"
|
|
80
|
+
) {
|
|
81
|
+
await fse.remove(targetPath);
|
|
82
|
+
}
|
|
83
|
+
switch (potentialCategory) {
|
|
84
|
+
case "page":
|
|
85
|
+
console.log(`Page deleted: ${filename}`);
|
|
86
|
+
break;
|
|
87
|
+
case "snippet":
|
|
88
|
+
console.log(`Snippet deleted: ${filename}`);
|
|
89
|
+
break;
|
|
90
|
+
case "mintConfig":
|
|
91
|
+
console.log(
|
|
92
|
+
"⚠️ mint.json deleted. Please create a new mint.json file as it is mandatory."
|
|
93
|
+
);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
case "potentialJsonOpenApiSpec":
|
|
96
|
+
case "potentialYamlOpenApiSpec":
|
|
97
|
+
await updateOpenApiFiles();
|
|
98
|
+
await updateGeneratedNav();
|
|
99
|
+
break;
|
|
100
|
+
case "staticFile":
|
|
101
|
+
console.log("Static file deleted: ", filename);
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error(error.message);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const getTargetPath = (
|
|
111
|
+
potentialCategory: PotentialFileCategory,
|
|
112
|
+
filePath: string
|
|
113
|
+
): string => {
|
|
114
|
+
switch (potentialCategory) {
|
|
115
|
+
case "page":
|
|
116
|
+
case "snippet":
|
|
117
|
+
return pathUtil.join(CLIENT_PATH, "src", "_props", filePath);
|
|
118
|
+
case "mintConfig":
|
|
119
|
+
return pathUtil.join(CLIENT_PATH, "src", "_props", "mint.json");
|
|
120
|
+
case "potentialYamlOpenApiSpec":
|
|
121
|
+
case "potentialJsonOpenApiSpec":
|
|
122
|
+
return pathUtil.join(CLIENT_PATH, "src", "_props", "openApiFiles.json");
|
|
123
|
+
case "staticFile":
|
|
124
|
+
return pathUtil.join(CLIENT_PATH, "public", filePath);
|
|
125
|
+
default:
|
|
126
|
+
throw new Error("Invalid category");
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* This function is called when a file is added or changed
|
|
132
|
+
* @param filename
|
|
133
|
+
* @returns FileCategory
|
|
134
|
+
*/
|
|
135
|
+
const onUpdateEvent = async (filename: string): Promise<FileCategory> => {
|
|
136
|
+
const filePath = pathUtil.join(CMD_EXEC_PATH, filename);
|
|
137
|
+
const potentialCategory = getCategory(filename);
|
|
138
|
+
const targetPath = getTargetPath(potentialCategory, filename);
|
|
139
|
+
let regenerateNav = false;
|
|
140
|
+
let category: FileCategory =
|
|
141
|
+
potentialCategory === "potentialYamlOpenApiSpec" ||
|
|
142
|
+
potentialCategory === "potentialJsonOpenApiSpec"
|
|
143
|
+
? "staticFile"
|
|
144
|
+
: potentialCategory;
|
|
145
|
+
|
|
146
|
+
switch (potentialCategory) {
|
|
147
|
+
case "page":
|
|
148
|
+
regenerateNav = true;
|
|
149
|
+
|
|
150
|
+
const contentStr = (await readFile(filePath)).toString();
|
|
151
|
+
const { pageContent } = await createPage(
|
|
152
|
+
filename,
|
|
153
|
+
contentStr,
|
|
154
|
+
CMD_EXEC_PATH,
|
|
155
|
+
[]
|
|
156
|
+
);
|
|
157
|
+
await fse.outputFile(targetPath, pageContent, {
|
|
158
|
+
flag: "w",
|
|
159
|
+
});
|
|
160
|
+
break;
|
|
161
|
+
case "snippet":
|
|
162
|
+
await fse.copy(filePath, targetPath);
|
|
163
|
+
break;
|
|
164
|
+
case "mintConfig":
|
|
165
|
+
regenerateNav = true;
|
|
166
|
+
|
|
167
|
+
const mintJsonFileContent = (await readFile(filePath)).toString();
|
|
168
|
+
try {
|
|
169
|
+
const mintConfig = JSON.parse(mintJsonFileContent);
|
|
170
|
+
const { status, errors, warnings } =
|
|
171
|
+
mintValidation.validateMintConfig(mintConfig);
|
|
172
|
+
|
|
173
|
+
errors.forEach((error) => {
|
|
174
|
+
console.error(`🚨 ${Chalk.red(error)}`);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
warnings.forEach((warning) => {
|
|
178
|
+
console.warn(`⚠️ ${Chalk.yellow(warning)}`);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
if (status === "success") {
|
|
182
|
+
await fse.copy(filePath, targetPath);
|
|
183
|
+
}
|
|
184
|
+
} catch (error) {
|
|
185
|
+
if (error.name === "SyntaxError") {
|
|
186
|
+
console.error(
|
|
187
|
+
`🚨 ${Chalk.red(
|
|
188
|
+
"mint.json has invalid JSON. You are likely missing a comma or a bracket. You can paste your mint.json file into https://jsonlint.com/ to get a more specific error message."
|
|
189
|
+
)}`
|
|
190
|
+
);
|
|
191
|
+
} else {
|
|
192
|
+
console.error(`🚨 ${Chalk.red(error.message)}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
break;
|
|
197
|
+
case "potentialYamlOpenApiSpec":
|
|
198
|
+
case "potentialJsonOpenApiSpec":
|
|
199
|
+
let isOpenApi = false;
|
|
200
|
+
const openApiInfo = await openApiCheck(filePath);
|
|
201
|
+
isOpenApi = openApiInfo.isOpenApi;
|
|
202
|
+
if (isOpenApi) {
|
|
203
|
+
// TODO: Instead of re-generating all openApi files, optimize by just updating the specific file that changed.
|
|
204
|
+
await updateOpenApiFiles();
|
|
205
|
+
regenerateNav = true;
|
|
206
|
+
category = "openApi";
|
|
207
|
+
}
|
|
208
|
+
break;
|
|
209
|
+
case "staticFile":
|
|
210
|
+
if (await isFileSizeValid(filePath, 5)) {
|
|
211
|
+
await fse.copy(filePath, targetPath);
|
|
212
|
+
} else {
|
|
213
|
+
console.error(
|
|
214
|
+
Chalk.red(
|
|
215
|
+
`🚨 The file at ${filename} is too big. The maximum file size is 5 mb.`
|
|
216
|
+
)
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
if (regenerateNav) {
|
|
222
|
+
// TODO: Instead of re-generating the entire nav, optimize by just updating the specific page that changed.
|
|
223
|
+
await updateGeneratedNav();
|
|
224
|
+
}
|
|
225
|
+
return category;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
export default listener;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// TODO: Add Types
|
|
2
|
+
import fse from "fs-extra";
|
|
3
|
+
import { promises as _promises } from "fs";
|
|
4
|
+
import { CLIENT_PATH, CMD_EXEC_PATH } from "../../constants.js";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
import { generateNav } from "./generate.js";
|
|
7
|
+
import { categorizeFiles } from "./categorize.js";
|
|
8
|
+
|
|
9
|
+
const { readFile } = _promises;
|
|
10
|
+
|
|
11
|
+
// TODO: Put in prebuild package
|
|
12
|
+
|
|
13
|
+
export const updateGeneratedNav = async () => {
|
|
14
|
+
const generatedNav = await generateNav();
|
|
15
|
+
const targetPath = join(CLIENT_PATH, "src", "_props", "generatedNav.json");
|
|
16
|
+
await fse.outputFile(targetPath, JSON.stringify(generatedNav, null, 2), {
|
|
17
|
+
flag: "w",
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const updateOpenApiFiles = async () => {
|
|
22
|
+
const { openApiFiles } = await categorizeFiles(CMD_EXEC_PATH);
|
|
23
|
+
const targetPath = join(CLIENT_PATH, "src", "_props", "openApiFiles.json");
|
|
24
|
+
await fse.outputFile(targetPath, JSON.stringify(openApiFiles, null, 2), {
|
|
25
|
+
flag: "w",
|
|
26
|
+
});
|
|
27
|
+
};
|