host-mdx 2.2.0 → 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 +90 -38
- package/cli.js +195 -0
- package/index.js +432 -376
- package/mdx-to-html.js +15 -15
- package/package.json +4 -3
- package/tests/cli.test.js +49 -0
package/README.md
CHANGED
|
@@ -5,47 +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
|
-
|
|
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();
|
|
37
48
|
```
|
|
38
49
|
|
|
39
|
-
> **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)
|
|
40
50
|
|
|
41
|
-
|
|
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
|
+
|
|
42
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/>
|
|
64
|
+
|
|
65
|
+
Default global variables you can use inside any .mdx files:
|
|
43
66
|
```
|
|
44
67
|
hostmdxCwd
|
|
45
68
|
hostmdxInputPath
|
|
46
69
|
hostmdxOutputPath
|
|
47
70
|
```
|
|
48
71
|
|
|
72
|
+
|
|
49
73
|
## 📖 Example
|
|
50
74
|
|
|
51
75
|
Command:
|
|
@@ -58,6 +82,7 @@ Input Directory:
|
|
|
58
82
|
|
|
59
83
|
```
|
|
60
84
|
my-website-template/
|
|
85
|
+
├─ 404.mdx
|
|
61
86
|
├─ index.mdx
|
|
62
87
|
├─ .hostmdxignore
|
|
63
88
|
├─ host-mdx.js
|
|
@@ -90,42 +115,68 @@ static/temp.jpg
|
|
|
90
115
|
`host-mdx.js` file content:
|
|
91
116
|
|
|
92
117
|
```js
|
|
93
|
-
export function
|
|
94
|
-
console.log("
|
|
118
|
+
export async function onHostStarting(inputPath, outputPath, port) {
|
|
119
|
+
console.log("onHostStarting");
|
|
95
120
|
}
|
|
96
|
-
export function
|
|
97
|
-
console.log("
|
|
121
|
+
export async function onHostStarted(inputPath, outputPath, port) {
|
|
122
|
+
console.log("onHostStarted");
|
|
98
123
|
}
|
|
99
|
-
export function
|
|
100
|
-
console.log("
|
|
124
|
+
export async function onHostEnded(inputPath, outputPath, port) {
|
|
125
|
+
console.log("onHostEnded");
|
|
101
126
|
}
|
|
102
|
-
export function
|
|
103
|
-
console.log("
|
|
127
|
+
export async function onSiteCreateStart(inputPath, outputPath) {
|
|
128
|
+
console.log("onSiteCreateStart");
|
|
104
129
|
}
|
|
105
|
-
export function
|
|
106
|
-
console.log("
|
|
130
|
+
export async function onSiteCreateEnd(inputPath, outputPath, wasInterrupted) {
|
|
131
|
+
console.log("onSiteCreateEnd");
|
|
107
132
|
}
|
|
108
|
-
export function
|
|
109
|
-
console.log("
|
|
133
|
+
export async function onFileCreateStart(inputFilePath, outputFilePath, inFilePath, outFilePath) {
|
|
134
|
+
console.log("onFileCreateStart");
|
|
110
135
|
}
|
|
111
|
-
export function
|
|
112
|
-
//
|
|
113
|
-
|
|
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");
|
|
114
141
|
}
|
|
115
|
-
export function
|
|
142
|
+
export async function toIgnore(inputPath, outputPath, path) {
|
|
116
143
|
const isGOutputStream = /\.goutputstream-\w+$/.test(path);
|
|
117
144
|
if (isGOutputStream) {
|
|
118
|
-
return
|
|
145
|
+
return true;
|
|
119
146
|
}
|
|
120
147
|
|
|
121
|
-
return
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
export async function modMDXCode(inputPath, outputPath, inFilePath, outFilePath, code){
|
|
151
|
+
// Modify code ...
|
|
152
|
+
return code;
|
|
153
|
+
}
|
|
154
|
+
export async function modGlobalArgs(inputPath, outputPath, globalArgs){
|
|
155
|
+
// Modify globalArgs ...
|
|
156
|
+
return globalArgs;
|
|
122
157
|
}
|
|
158
|
+
export async function modBundleMDXSettings(inputPath, outputPath, settings) {
|
|
159
|
+
// Modify settings ...
|
|
160
|
+
return settings;
|
|
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
|
|
123
173
|
```
|
|
124
174
|
|
|
125
175
|
Output Directory:
|
|
126
176
|
|
|
127
177
|
```
|
|
128
178
|
my-website/
|
|
179
|
+
├─ 404.html
|
|
129
180
|
├─ index.html
|
|
130
181
|
├─ about/
|
|
131
182
|
│ └─ index.html
|
|
@@ -141,6 +192,7 @@ my-website/
|
|
|
141
192
|
|
|
142
193
|
The site will now be visible in the browser at `localhost:3113`
|
|
143
194
|
|
|
195
|
+
|
|
144
196
|
## 🔑 License
|
|
145
197
|
|
|
146
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()
|