host-mdx 2.2.1 → 2.3.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/README.md +83 -42
- package/cli.js +195 -0
- package/index.js +432 -383
- package/mdx-to-html.js +15 -15
- package/package.json +4 -3
- package/tests/cli.test.js +49 -0
package/README.md
CHANGED
|
@@ -5,50 +5,71 @@ A cli tool to create and serve a static html website from a given mdx directory
|
|
|
5
5
|
|
|
6
6
|
## 🛠️ Usage
|
|
7
7
|
|
|
8
|
+
### With npx:
|
|
9
|
+
```bash
|
|
10
|
+
npx host-mdx --input-path="path/to/input" --output-path="path/to/output"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
List of all available options:
|
|
8
14
|
```
|
|
9
|
-
host-mdx [options]
|
|
15
|
+
Usage: host-mdx [options]
|
|
10
16
|
|
|
11
17
|
Options:
|
|
12
|
-
--
|
|
13
|
-
--
|
|
14
|
-
--
|
|
15
|
-
--
|
|
16
|
-
--
|
|
17
|
-
--
|
|
18
|
-
--
|
|
18
|
+
--concurrency=<num> Limit number of files to concurrently process (Optional, default: 1)
|
|
19
|
+
--create-only, -c Only creates the html website from mdx does not host
|
|
20
|
+
--help, -h Shows all available options
|
|
21
|
+
--input-path=<path> The path at which all mdx files are stored
|
|
22
|
+
--output-path=<path> The path to which all html files will be generated
|
|
23
|
+
--port=<num> Localhost port number on which to host
|
|
24
|
+
--track-changes, -t Tracks any changes & auto reloads, -t=hard for hard reload
|
|
25
|
+
--verbose, -v Shows additional log messages
|
|
19
26
|
```
|
|
20
27
|
|
|
21
28
|
> If `--input-path` is not provided it will default to `./` i.e. current working directory\
|
|
22
29
|
> If `--output-path` is not provided a temp folder will be created automatically & deleted upon exit
|
|
23
30
|
|
|
24
|
-
You can add a file by the name `.hostmdxignore` at the root of your project to filter out which files/folders to skip while generating html
|
|
25
|
-
(similar to [.gitignore](https://git-scm.com/docs/gitignore))
|
|
26
31
|
|
|
27
|
-
|
|
32
|
+
|
|
33
|
+
### With import/require:
|
|
28
34
|
|
|
29
35
|
```js
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
import { HostMdx } from "host-mdx";
|
|
37
|
+
// const { HostMdx } = require("host-mdx");
|
|
38
|
+
|
|
39
|
+
const inputPath = "/home/mrm/Desktop/website-mdx"
|
|
40
|
+
const outputPath = "/home/mrm/Desktop/website-html"
|
|
41
|
+
const configs = {
|
|
42
|
+
toBeVerbose: true,
|
|
43
|
+
port:3000,
|
|
44
|
+
trackChanges:1
|
|
45
|
+
}
|
|
46
|
+
const hostMdx = new HostMdx(inputPath, outputPath, configs);
|
|
47
|
+
hostMdx.start();
|
|
40
48
|
```
|
|
41
49
|
|
|
42
|
-
> **Note:** Any changes made to `host-mdx.js` or any new package added requires complete restart otherwise changes will not reflect due to [this bug](https://github.com/nodejs/node/issues/49442)
|
|
43
50
|
|
|
44
|
-
|
|
51
|
+
### Additional:
|
|
52
|
+
|
|
53
|
+
You can add a file by the name `.hostmdxignore` at the root of your project to filter out which files/folders to skip while generating html
|
|
54
|
+
(similar to [.gitignore](https://git-scm.com/docs/gitignore))
|
|
55
|
+
|
|
56
|
+
You can also add a file by the name `host-mdx.js` at the root of your input folder as a config file (Look at the example below for all available options)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
> **Note:**
|
|
60
|
+
> 1. Any config properties passed from npx or import e.g. `port`, `toBeVerbose`, `trackChanges`, etc will override `host-mdx.js` export values
|
|
61
|
+
> 1. Any changes made to `host-mdx.js` or any new package added requires complete restart otherwise changes will not reflect due to [this bug](https://github.com/nodejs/node/issues/49442)
|
|
62
|
+
|
|
63
|
+
<br/>
|
|
45
64
|
|
|
65
|
+
Default global variables you can use inside any .mdx files:
|
|
46
66
|
```
|
|
47
67
|
hostmdxCwd
|
|
48
68
|
hostmdxInputPath
|
|
49
69
|
hostmdxOutputPath
|
|
50
70
|
```
|
|
51
71
|
|
|
72
|
+
|
|
52
73
|
## 📖 Example
|
|
53
74
|
|
|
54
75
|
Command:
|
|
@@ -61,6 +82,7 @@ Input Directory:
|
|
|
61
82
|
|
|
62
83
|
```
|
|
63
84
|
my-website-template/
|
|
85
|
+
├─ 404.mdx
|
|
64
86
|
├─ index.mdx
|
|
65
87
|
├─ .hostmdxignore
|
|
66
88
|
├─ host-mdx.js
|
|
@@ -93,31 +115,37 @@ static/temp.jpg
|
|
|
93
115
|
`host-mdx.js` file content:
|
|
94
116
|
|
|
95
117
|
```js
|
|
96
|
-
export async function
|
|
97
|
-
console.log("
|
|
118
|
+
export async function onHostStarting(inputPath, outputPath, port) {
|
|
119
|
+
console.log("onHostStarting");
|
|
98
120
|
}
|
|
99
|
-
export async function
|
|
100
|
-
console.log("
|
|
121
|
+
export async function onHostStarted(inputPath, outputPath, port) {
|
|
122
|
+
console.log("onHostStarted");
|
|
101
123
|
}
|
|
102
|
-
export async function
|
|
103
|
-
|
|
104
|
-
if (isGOutputStream) {
|
|
105
|
-
return false;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return true;
|
|
124
|
+
export async function onHostEnded(inputPath, outputPath, port) {
|
|
125
|
+
console.log("onHostEnded");
|
|
109
126
|
}
|
|
110
127
|
export async function onSiteCreateStart(inputPath, outputPath) {
|
|
111
|
-
console.log("onSiteCreateStart"
|
|
128
|
+
console.log("onSiteCreateStart");
|
|
112
129
|
}
|
|
113
|
-
export async function onSiteCreateEnd(inputPath, outputPath,
|
|
114
|
-
console.log("onSiteCreateEnd"
|
|
130
|
+
export async function onSiteCreateEnd(inputPath, outputPath, wasInterrupted) {
|
|
131
|
+
console.log("onSiteCreateEnd");
|
|
115
132
|
}
|
|
116
|
-
export async function onFileCreateStart(inputFilePath, outputFilePath) {
|
|
117
|
-
console.log("onFileCreateStart"
|
|
133
|
+
export async function onFileCreateStart(inputFilePath, outputFilePath, inFilePath, outFilePath) {
|
|
134
|
+
console.log("onFileCreateStart");
|
|
118
135
|
}
|
|
119
|
-
export async function onFileCreateEnd(inputFilePath, outputFilePath) {
|
|
120
|
-
|
|
136
|
+
export async function onFileCreateEnd(inputFilePath, outputFilePath, inFilePath, outFilePath, result) {
|
|
137
|
+
// `result = undefined` if file is not .mdx
|
|
138
|
+
// `result.html` contains stringified HTML
|
|
139
|
+
// `result.exports` contains exports from mdx
|
|
140
|
+
console.log("onFileCreateEnd");
|
|
141
|
+
}
|
|
142
|
+
export async function toIgnore(inputPath, outputPath, path) {
|
|
143
|
+
const isGOutputStream = /\.goutputstream-\w+$/.test(path);
|
|
144
|
+
if (isGOutputStream) {
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return false;
|
|
121
149
|
}
|
|
122
150
|
export async function modMDXCode(inputPath, outputPath, inFilePath, outFilePath, code){
|
|
123
151
|
// Modify code ...
|
|
@@ -129,14 +157,26 @@ export async function modGlobalArgs(inputPath, outputPath, globalArgs){
|
|
|
129
157
|
}
|
|
130
158
|
export async function modBundleMDXSettings(inputPath, outputPath, settings) {
|
|
131
159
|
// Modify settings ...
|
|
132
|
-
return settings
|
|
160
|
+
return settings;
|
|
133
161
|
}
|
|
162
|
+
export async function modRebuildPaths(inputPath, outputPath, rebuildPaths) {
|
|
163
|
+
// Modify rebuildPaths ...
|
|
164
|
+
return rebuildPaths;
|
|
165
|
+
}
|
|
166
|
+
export const chokidarOptions = {
|
|
167
|
+
awaitWriteFinish: true
|
|
168
|
+
}
|
|
169
|
+
export const port = 3000;
|
|
170
|
+
export const trackChanges = 1; // 0=no-tracking, 1=soft-reload, 2=hard-reload
|
|
171
|
+
export const toBeVerbose = true;
|
|
172
|
+
export const concurrency = 10; // Lowest possible value: 1
|
|
134
173
|
```
|
|
135
174
|
|
|
136
175
|
Output Directory:
|
|
137
176
|
|
|
138
177
|
```
|
|
139
178
|
my-website/
|
|
179
|
+
├─ 404.html
|
|
140
180
|
├─ index.html
|
|
141
181
|
├─ about/
|
|
142
182
|
│ └─ index.html
|
|
@@ -152,6 +192,7 @@ my-website/
|
|
|
152
192
|
|
|
153
193
|
The site will now be visible in the browser at `localhost:3113`
|
|
154
194
|
|
|
195
|
+
|
|
155
196
|
## 🔑 License
|
|
156
197
|
|
|
157
198
|
MIT © [Manas Ravindra Makde](https://manasmakde.github.io/)
|
package/cli.js
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import path from "path";
|
|
4
|
+
import * as readline from "readline";
|
|
5
|
+
import { HostMdx, createSite, TrackChanges, log } from "./index.js";
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
// Flags
|
|
9
|
+
const CONCURRENCY_FLAG = "--concurrency"
|
|
10
|
+
const CREATE_FLAG = "--create-only";
|
|
11
|
+
const CREATE_SHORT_FLAG = "-c";
|
|
12
|
+
const HELP_FLAG = "--help";
|
|
13
|
+
const HELP_SHORT_FLAG = "-h";
|
|
14
|
+
const INPUT_PATH_FLAG = "--input-path";
|
|
15
|
+
const OUTPUT_PATH_FLAG = "--output-path";
|
|
16
|
+
const PORT_FLAG = "--port";
|
|
17
|
+
const TRACK_CHANGES_FLAG = "--track-changes";
|
|
18
|
+
const TRACK_CHANGES_SHORT_FLAG = "-t";
|
|
19
|
+
const VERBOSE_FLAG = "--verbose";
|
|
20
|
+
const VERBOSE_SHORT_FLAG = "-v";
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
// Properties
|
|
24
|
+
const SOFT_RELOAD_ARG = "soft"
|
|
25
|
+
const HARD_RELOAD_ARG = "hard"
|
|
26
|
+
const HELP_MESSAGE = `Usage: host-mdx [options]
|
|
27
|
+
|
|
28
|
+
Options:
|
|
29
|
+
${CONCURRENCY_FLAG}=<num> Limit number of files to concurrently process (Optional, default: 1)
|
|
30
|
+
${CREATE_FLAG}, ${CREATE_SHORT_FLAG} Only creates the html website from mdx does not host
|
|
31
|
+
${HELP_FLAG}, ${HELP_SHORT_FLAG} Shows all available options
|
|
32
|
+
${INPUT_PATH_FLAG}=<path> The path at which all mdx files are stored
|
|
33
|
+
${OUTPUT_PATH_FLAG}=<path> The path to which all html files will be generated
|
|
34
|
+
${PORT_FLAG}=<num> Localhost port number on which to host
|
|
35
|
+
${TRACK_CHANGES_FLAG}, ${TRACK_CHANGES_SHORT_FLAG} Tracks any changes & auto reloads, ${TRACK_CHANGES_SHORT_FLAG}=${HARD_RELOAD_ARG} for hard reload
|
|
36
|
+
${VERBOSE_FLAG}, ${VERBOSE_SHORT_FLAG} Shows additional log messages
|
|
37
|
+
`;
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
// Utility Methods
|
|
41
|
+
function getInputPathFromArgs(rawArgs) {
|
|
42
|
+
let inputPath = rawArgs.find(val => val.startsWith(INPUT_PATH_FLAG));
|
|
43
|
+
let inputPathProvided = inputPath !== undefined;
|
|
44
|
+
inputPath = inputPathProvided ? inputPath.split('=')?.[1] : "";
|
|
45
|
+
return inputPath !== "" ? path.resolve(inputPath) : inputPath; // To ensure input path is absolute
|
|
46
|
+
}
|
|
47
|
+
function getOutputPathFromArgs(rawArgs) {
|
|
48
|
+
let outputPath = rawArgs.find(val => val.startsWith(OUTPUT_PATH_FLAG));
|
|
49
|
+
let outputPathProvided = outputPath !== undefined;
|
|
50
|
+
outputPath = outputPathProvided ? outputPath.split('=')?.[1] : "";
|
|
51
|
+
return outputPath !== "" ? path.resolve(outputPath) : outputPath; // To ensure input path is absolute
|
|
52
|
+
}
|
|
53
|
+
function getPortFromArgs(rawArgs) {
|
|
54
|
+
let port = rawArgs.find(val => val.startsWith(PORT_FLAG));
|
|
55
|
+
let portProvided = port !== undefined;
|
|
56
|
+
return portProvided ? Number(port.split('=')[1]) : undefined;
|
|
57
|
+
}
|
|
58
|
+
function getTrackChangesFromArgs(rawArgs) {
|
|
59
|
+
// If flag not passed do not track changes
|
|
60
|
+
let trackChanges = rawArgs.find(val => (val.startsWith(TRACK_CHANGES_FLAG) || val.startsWith(TRACK_CHANGES_SHORT_FLAG)));
|
|
61
|
+
if (trackChanges == undefined) {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
// Check for argument passed (if any)
|
|
67
|
+
let trackChangesSplit = trackChanges?.split('=') ?? [];
|
|
68
|
+
let arg = trackChangesSplit?.[1];
|
|
69
|
+
if (arg === HARD_RELOAD_ARG) {
|
|
70
|
+
return TrackChanges.HARD;
|
|
71
|
+
}
|
|
72
|
+
else if (arg === SOFT_RELOAD_ARG) {
|
|
73
|
+
return TrackChanges.SOFT;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
return TrackChanges.SOFT;
|
|
78
|
+
}
|
|
79
|
+
function getConcurrencyFromArgs(rawArgs) {
|
|
80
|
+
let concurrency = rawArgs.find(val => val.startsWith(CONCURRENCY_FLAG));
|
|
81
|
+
let concurrencyProvided = concurrency !== undefined;
|
|
82
|
+
return concurrencyProvided ? Number(concurrency.split('=')[1]) : undefined;
|
|
83
|
+
}
|
|
84
|
+
function listenForKey(reloadCallback, hardReloadCallback, exitCallback) {
|
|
85
|
+
readline.emitKeypressEvents(process.stdin);
|
|
86
|
+
process.stdin.setRawMode(true);
|
|
87
|
+
process.stdin.on("keypress", (chunk, key) => {
|
|
88
|
+
if (key && key.shift && key.name == 'r') {
|
|
89
|
+
hardReloadCallback();
|
|
90
|
+
}
|
|
91
|
+
else if (key && key.name == 'r') {
|
|
92
|
+
reloadCallback();
|
|
93
|
+
}
|
|
94
|
+
else if (key && key.sequence == '\x03') {
|
|
95
|
+
exitCallback();
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
// Main Methods
|
|
102
|
+
export async function main() {
|
|
103
|
+
|
|
104
|
+
// Get all arguments
|
|
105
|
+
const rawArgs = process.argv.slice(2);
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
// Help flag check, Print out help message & return if passed
|
|
109
|
+
if (rawArgs.includes(HELP_FLAG) || rawArgs.includes(HELP_SHORT_FLAG)) {
|
|
110
|
+
console.log(HELP_MESSAGE)
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
// Assign input path
|
|
116
|
+
let inputPath = getInputPathFromArgs(rawArgs);
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
// Assign output path
|
|
120
|
+
let outputPath = getOutputPathFromArgs(rawArgs);
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
// Assign verbose
|
|
124
|
+
let toBeVerbose = rawArgs.includes(VERBOSE_FLAG) || rawArgs.includes(VERBOSE_SHORT_FLAG);
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
// Assign concurrency
|
|
128
|
+
let concurrency = getConcurrencyFromArgs(rawArgs);
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
// Assign to create only, Return if passed
|
|
132
|
+
let toCreateOnly = rawArgs.includes(CREATE_FLAG) || rawArgs.includes(CREATE_SHORT_FLAG);
|
|
133
|
+
if (toCreateOnly) {
|
|
134
|
+
try {
|
|
135
|
+
await createSite(inputPath, outputPath, null, undefined, { toBeVerbose });
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
process.exitCode = 1; // Exit with error code if not created successfully
|
|
139
|
+
log(`Failed to create site!\n${err?.stack}`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
// Assign port
|
|
147
|
+
let port = getPortFromArgs(rawArgs);
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
// Assign tracking changes
|
|
151
|
+
let trackChanges = getTrackChangesFromArgs(rawArgs);
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
// Start hosting
|
|
155
|
+
let configs = {
|
|
156
|
+
...(port !== undefined && { port }),
|
|
157
|
+
...(concurrency !== undefined && { concurrency }),
|
|
158
|
+
...(trackChanges !== undefined && { trackChanges }),
|
|
159
|
+
...(toBeVerbose && { toBeVerbose }),
|
|
160
|
+
}
|
|
161
|
+
let hostMdx = new HostMdx(inputPath, outputPath, configs);
|
|
162
|
+
let hasHostingStarted = await hostMdx.start();
|
|
163
|
+
if (!hasHostingStarted) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
// Assign cleanup function
|
|
169
|
+
const cleanup = async () => {
|
|
170
|
+
process.stdin.setRawMode(false);
|
|
171
|
+
await hostMdx.stop();
|
|
172
|
+
process.exit(0); // Without this 'Ctrl + c' does not work DO NOT REMOVE
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
// Watch for key press
|
|
177
|
+
listenForKey(
|
|
178
|
+
async () => await hostMdx?.recreateSite(),
|
|
179
|
+
async () => await hostMdx?.recreateSite(true),
|
|
180
|
+
cleanup
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
// Watch for quit
|
|
185
|
+
process.on("exit", cleanup);
|
|
186
|
+
process.on("SIGINT", cleanup);
|
|
187
|
+
process.on("SIGTERM", cleanup);
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
// Log key press instructions
|
|
191
|
+
log(`(Press 'r' to reload, 'Shift + r' to hard reload, 'Ctrl+c' to exit)`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
main()
|