host-mdx 1.0.13 → 2.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/README.md +87 -13
- package/index.js +284 -133
- package/mdx-to-html.js +42 -0
- package/package.json +14 -8
package/README.md
CHANGED
|
@@ -1,49 +1,123 @@
|
|
|
1
1
|
# host-mdx
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/host-mdx )
|
|
4
|
+
|
|
5
|
+
A cli tool to create and serve a static html website from a given mdx directory
|
|
4
6
|
|
|
5
7
|
## Usage
|
|
8
|
+
|
|
6
9
|
```
|
|
7
|
-
|
|
10
|
+
host-mdx [options]
|
|
11
|
+
|
|
12
|
+
Options:
|
|
13
|
+
--create-only, -c Only create the html website from mdx does not host
|
|
14
|
+
--help, -h Shows all available options
|
|
15
|
+
--input-path=... The path at which all mdx files are stored
|
|
16
|
+
--output-path=... The path to which all html files will be generated
|
|
17
|
+
--port=... Localhost port number on which to host
|
|
18
|
+
--track-changes, -t Tracks any changes made & auto reloads
|
|
19
|
+
--verobse, -v Shows additional log messages
|
|
8
20
|
```
|
|
9
21
|
|
|
10
|
-
|
|
22
|
+
Add a file by the name `.hostmdxignore` at the root of your project to filter out which files/folders to skip while generating html
|
|
23
|
+
(similar to [.gitignore](https://git-scm.com/docs/gitignore))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
Add a file by the name `host-mdx.js` at the root of your project as a config file with the following:
|
|
27
|
+
|
|
28
|
+
```js
|
|
29
|
+
// Modify
|
|
30
|
+
modBundleMDXSettings(settings)
|
|
31
|
+
|
|
11
32
|
|
|
33
|
+
// Hooks
|
|
34
|
+
onSiteCreateStart(inputPath, outputPath)
|
|
35
|
+
onSiteCreateEnd(inputPath, outputPath)
|
|
36
|
+
onFileCreateStart(inputFilePath, outputFilePath)
|
|
37
|
+
onFileCreateEnd(inputFilePath, outputFilePath)
|
|
38
|
+
```
|
|
39
|
+
> Note: Any changes made to `host-mdx.js` require complete restart otherwise changes will not reflect
|
|
40
|
+
|
|
41
|
+
## Example
|
|
12
42
|
|
|
43
|
+
Command:
|
|
13
44
|
```bash
|
|
14
|
-
|
|
45
|
+
npx host-mdx --input-path="path/to/my-website-template" --output-path="path/to/my-website" --port=3113 -t
|
|
15
46
|
```
|
|
16
47
|
|
|
48
|
+
Input Directory:
|
|
17
49
|
```
|
|
18
50
|
my-website-template/
|
|
19
51
|
├─ index.mdx
|
|
52
|
+
├─ .hostmdxignore
|
|
53
|
+
├─ host-mdx.js
|
|
20
54
|
├─ about/
|
|
21
|
-
│
|
|
22
|
-
|
|
23
|
-
|
|
55
|
+
│ ├─ index.mdx
|
|
56
|
+
│ └─ custom_component.jsx
|
|
57
|
+
├─ blog/
|
|
58
|
+
│ ├─ page1/
|
|
24
59
|
│ │ └─ index.mdx
|
|
25
|
-
│ └─
|
|
60
|
+
│ └─ page2/
|
|
61
|
+
│ ├─ extras.png
|
|
26
62
|
│ └─ index.mdx
|
|
27
63
|
└─ static/
|
|
28
64
|
├─ image1.png
|
|
29
65
|
├─ image2.jpg
|
|
66
|
+
├─ temp.jpg
|
|
67
|
+
├─ sample.jsx
|
|
30
68
|
└─ styles.css
|
|
31
69
|
```
|
|
32
70
|
|
|
71
|
+
`.hostmdxignore` file content:
|
|
72
|
+
```sh
|
|
73
|
+
*.jsx
|
|
74
|
+
blog/page2/
|
|
75
|
+
static/temp.jpg
|
|
76
|
+
!static/sample.jsx
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`host-mdx.js` file content:
|
|
80
|
+
```js
|
|
81
|
+
export function onSiteCreateStart(inputPath, outputPath) {
|
|
82
|
+
console.log("onSiteCreateStart", inputPath, outputPath)
|
|
83
|
+
}
|
|
84
|
+
export function onSiteCreateEnd(inputPath, outputPath, wasSuccessful){
|
|
85
|
+
console.log("onSiteCreateEnd", inputPath, outputPath)
|
|
86
|
+
}
|
|
87
|
+
export function onFileCreateStart(inputFilePath, outputFilePath){
|
|
88
|
+
console.log("onFileCreateStart", inputFilePath, outputFilePath)
|
|
89
|
+
}
|
|
90
|
+
export function onFileCreateEnd(inputFilePath, outputFilePath){
|
|
91
|
+
console.log("onFileCreateEnd", inputFilePath, outputFilePath)
|
|
92
|
+
}
|
|
93
|
+
export function onHostStart(port){
|
|
94
|
+
console.log("onHostStart", port)
|
|
95
|
+
}
|
|
96
|
+
export function onHostEnd(port){
|
|
97
|
+
console.log("onHostEnd", port)
|
|
98
|
+
}
|
|
99
|
+
export function modBundleMDXSettings(settings){
|
|
100
|
+
// Modify settings ...
|
|
101
|
+
return settings
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Output Directory:
|
|
33
106
|
```
|
|
34
107
|
my-website/
|
|
35
108
|
├─ index.html
|
|
36
109
|
├─ about/
|
|
37
110
|
│ └─ index.html
|
|
38
|
-
├─
|
|
39
|
-
│
|
|
40
|
-
│ │ └─ index.html
|
|
41
|
-
│ └─ project2/
|
|
111
|
+
├─ blog/
|
|
112
|
+
│ └─ page1/
|
|
42
113
|
│ └─ index.html
|
|
43
114
|
└─ static/
|
|
44
115
|
├─ image1.png
|
|
45
116
|
├─ image2.jpg
|
|
117
|
+
├─ sample.jsx
|
|
46
118
|
└─ styles.css
|
|
47
119
|
```
|
|
48
120
|
|
|
49
|
-
The site will now be visible in the browser at `localhost:3113`
|
|
121
|
+
The site will now be visible in the browser at `localhost:3113`
|
|
122
|
+
|
|
123
|
+
> For a live example take a look at [sourcesnippet.github.io](https://sourcesnippet.github.io/)
|
package/index.js
CHANGED
|
@@ -1,35 +1,74 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
import { renderToString } from 'react-dom/server'
|
|
15
|
-
import { evaluate } from '@mdx-js/mdx'
|
|
16
|
-
import rehypeHighlight from "rehype-highlight";
|
|
17
|
-
import { common } from 'lowlight'
|
|
18
|
-
import gdscript from "@exercism/highlightjs-gdscript"
|
|
19
|
-
import * as readline from 'node:readline';
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
// Constants
|
|
24
|
-
const DEFAULT_PORT = 3000
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
import os from 'os'
|
|
5
|
+
import path from 'path'
|
|
6
|
+
import http from 'http'
|
|
7
|
+
import ignore from "ignore";
|
|
8
|
+
import chokidar from 'chokidar';
|
|
9
|
+
import * as readline from 'readline';
|
|
10
|
+
import { mdxToHtml } from './mdx-to-html.js'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
// To-Set Properties
|
|
25
14
|
const APP_NAME = "host-mdx"
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
15
|
+
const DEFAULT_PORT = 3000
|
|
16
|
+
const IGNORE_FILE_NAME = ".hostmdxignore"
|
|
17
|
+
const CONFIG_FILE_NAME = "host-mdx.js"
|
|
18
|
+
const DEFAULT_IGNORES = `
|
|
19
|
+
${IGNORE_FILE_NAME}
|
|
20
|
+
${CONFIG_FILE_NAME}
|
|
21
|
+
node_modules
|
|
22
|
+
package-lock.json
|
|
23
|
+
package.json
|
|
24
|
+
.git
|
|
25
|
+
`
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
// Flags
|
|
29
|
+
const CREATE_FLAG = "--create-only"
|
|
30
|
+
const CREATE_SHORT_FLAG = "-c"
|
|
31
|
+
const HELP_FLAG = "--help"
|
|
32
|
+
const HELP_SHORT_FLAG = "-h"
|
|
33
|
+
const INPUT_PATH_FLAG = "--input-path"
|
|
34
|
+
const OUTPUT_PATH_FLAG = "--output-path"
|
|
35
|
+
const PORT_FLAG = "--port"
|
|
36
|
+
const VERBOSE_FLAG = "--verobse"
|
|
37
|
+
const VERBOSE_SHORT_FLAG = "-v"
|
|
38
|
+
const TRACK_CHANGES_FLAG = "--track-changes"
|
|
39
|
+
const TRACK_CHANGES_SHORT_FLAG = "-t"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
// Messages & Errors
|
|
43
|
+
const HELP_MESSAGE = `Usage: host-mdx [options]
|
|
44
|
+
|
|
45
|
+
Options:
|
|
46
|
+
${CREATE_FLAG}, ${CREATE_SHORT_FLAG} Only create the html website from mdx does not host
|
|
47
|
+
${HELP_FLAG}, ${HELP_SHORT_FLAG} Shows all available options
|
|
48
|
+
${INPUT_PATH_FLAG}=... The path at which all mdx files are stored
|
|
49
|
+
${OUTPUT_PATH_FLAG}=... The path to which all html files will be generated
|
|
50
|
+
${PORT_FLAG}=... Localhost port number on which to host
|
|
51
|
+
${TRACK_CHANGES_FLAG}, ${TRACK_CHANGES_SHORT_FLAG} Tracks any changes made & auto reloads
|
|
52
|
+
${VERBOSE_FLAG}, ${VERBOSE_SHORT_FLAG} Shows additional log messages
|
|
53
|
+
`
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
// Private Properties
|
|
57
|
+
let isCreatingSite = false // Prevents site from being recreated if creation is already ongoing
|
|
58
|
+
let isVerbose = false
|
|
59
|
+
let configs
|
|
60
|
+
let server
|
|
61
|
+
const TEMP_HTML_DIR = path.join(os.tmpdir(), `${APP_NAME}`)
|
|
62
|
+
const TIME_OPTIONS = {
|
|
63
|
+
year: 'numeric',
|
|
64
|
+
month: 'long',
|
|
65
|
+
day: 'numeric',
|
|
66
|
+
hour: 'numeric',
|
|
67
|
+
minute: 'numeric',
|
|
68
|
+
second: 'numeric',
|
|
69
|
+
hour12: false,
|
|
70
|
+
fractionalSecondDigits: 3
|
|
71
|
+
}
|
|
33
72
|
const MIME_TYPE = { // Maps extensions to mime protocol
|
|
34
73
|
'.html': 'text/html',
|
|
35
74
|
'.css': 'text/css',
|
|
@@ -46,117 +85,222 @@ const MIME_TYPE = { // Maps extensions to mime protocol
|
|
|
46
85
|
'.pdf': 'application/pdf',
|
|
47
86
|
'.zip': 'application/zip',
|
|
48
87
|
}
|
|
49
|
-
let is_creating_site = false // Prevents site from being recreated if creation is ongoing
|
|
50
88
|
|
|
51
89
|
|
|
52
|
-
//
|
|
53
|
-
|
|
90
|
+
// Utility Methods
|
|
91
|
+
function log(msg, checkVerbose = false) {
|
|
92
|
+
if (checkVerbose && !isVerbose) {
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let timestamp = new Date().toLocaleString(undefined, TIME_OPTIONS)
|
|
97
|
+
console.log(`[${APP_NAME} ${timestamp}] ${msg}`)
|
|
98
|
+
}
|
|
99
|
+
function createTempDir() {
|
|
54
100
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
101
|
+
// Delete existing temp dir
|
|
102
|
+
if (fs.existsSync(TEMP_HTML_DIR)) {
|
|
103
|
+
fs.rmSync(TEMP_HTML_DIR, { recursive: true, force: true })
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
// Create default temp html dir
|
|
108
|
+
fs.mkdirSync(TEMP_HTML_DIR, { recursive: true })
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
return fs.mkdtempSync(path.join(TEMP_HTML_DIR, `/html-`));
|
|
112
|
+
}
|
|
113
|
+
function getIgnore(ignoreFilePath) {
|
|
114
|
+
const ig = ignore();
|
|
115
|
+
let ignoreContent = DEFAULT_IGNORES
|
|
116
|
+
|
|
117
|
+
if (fs.existsSync(ignoreFilePath)) {
|
|
118
|
+
ignoreContent += `\n${fs.readFileSync(ignoreFilePath, "utf8")}`
|
|
119
|
+
}
|
|
61
120
|
|
|
62
|
-
|
|
121
|
+
ig.add(ignoreContent);
|
|
63
122
|
|
|
123
|
+
return ig
|
|
64
124
|
}
|
|
65
|
-
|
|
125
|
+
function createFile(filePath, fileContent = "") {
|
|
126
|
+
|
|
127
|
+
// Check if path for file exists
|
|
128
|
+
let fileLocation = path.dirname(filePath)
|
|
129
|
+
if (!fs.existsSync(fileLocation)) {
|
|
130
|
+
fs.mkdirSync(fileLocation, { recursive: true });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
// Create file
|
|
135
|
+
fs.writeFileSync(filePath, fileContent);
|
|
136
|
+
}
|
|
137
|
+
async function createSite(inputPath, outputPath) {
|
|
66
138
|
// Exit if already creating
|
|
67
|
-
if (
|
|
139
|
+
if (isCreatingSite) {
|
|
140
|
+
log("site creation already ongoing!")
|
|
68
141
|
return
|
|
69
142
|
}
|
|
70
143
|
|
|
71
144
|
|
|
72
145
|
// Set creating status to ongoing
|
|
73
|
-
|
|
74
|
-
|
|
146
|
+
isCreatingSite = true
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
// Get config properties
|
|
150
|
+
let configFilePath = path.join(inputPath, `./${CONFIG_FILE_NAME}`)
|
|
151
|
+
if (fs.existsSync(configFilePath)) {
|
|
152
|
+
configs = await import(configFilePath);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
// Broadcast site creation started
|
|
157
|
+
log("Creating site...")
|
|
158
|
+
configs?.onSiteCreateStart?.(inputPath, outputPath)
|
|
75
159
|
|
|
76
160
|
|
|
77
161
|
// Remove html folder if it already exists
|
|
78
|
-
if (fs.existsSync(
|
|
79
|
-
fs.rmSync(
|
|
162
|
+
if (fs.existsSync(outputPath)) {
|
|
163
|
+
fs.rmSync(outputPath, { recursive: true, force: true });
|
|
80
164
|
}
|
|
81
165
|
|
|
82
166
|
|
|
83
|
-
//
|
|
84
|
-
|
|
167
|
+
// Setup ignore
|
|
168
|
+
let ignoreFilePath = path.join(inputPath, IGNORE_FILE_NAME)
|
|
169
|
+
let ig = getIgnore(ignoreFilePath)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
// Iterate through all folders & files
|
|
173
|
+
const stack = [inputPath];
|
|
174
|
+
while (stack.length > 0) {
|
|
175
|
+
const currentPath = stack.pop()
|
|
176
|
+
const relToInput = path.relative(inputPath, currentPath)
|
|
177
|
+
const toIgnore = inputPath != currentPath && ig.ignores(relToInput)
|
|
178
|
+
const absToOutput = path.join(outputPath, relToInput)
|
|
179
|
+
const isDir = fs.statSync(currentPath).isDirectory()
|
|
180
|
+
const isMdx = !isDir && absToOutput.endsWith(".mdx")
|
|
85
181
|
|
|
86
182
|
|
|
87
|
-
|
|
88
|
-
|
|
183
|
+
// Skip if to ignore this path
|
|
184
|
+
if (toIgnore) {
|
|
185
|
+
continue
|
|
186
|
+
}
|
|
187
|
+
|
|
89
188
|
|
|
90
|
-
|
|
91
|
-
|
|
189
|
+
// Make dir
|
|
190
|
+
if (isDir) {
|
|
191
|
+
log(`${currentPath} ---> ${absToOutput}`, true)
|
|
192
|
+
configs?.onFileCreateStart?.(currentPath, absToOutput)
|
|
193
|
+
fs.mkdirSync(absToOutput, { recursive: true });
|
|
194
|
+
configs?.onFileCreateEnd?.(currentPath, absToOutput)
|
|
195
|
+
}
|
|
196
|
+
// Make html file from mdx
|
|
197
|
+
else if (!isDir && isMdx) {
|
|
92
198
|
|
|
93
|
-
|
|
199
|
+
// Broadcast file creation started
|
|
200
|
+
let absHtmlPath = path.format({ ...path.parse(absToOutput), base: '', ext: '.html' })
|
|
201
|
+
log(`${currentPath} ---> ${absHtmlPath}`, true)
|
|
202
|
+
configs?.onFileCreateStart?.(currentPath, absHtmlPath)
|
|
94
203
|
|
|
95
|
-
// To ensure file paths work
|
|
96
|
-
process.chdir(file.parentPath);
|
|
97
204
|
|
|
98
|
-
|
|
99
|
-
let
|
|
100
|
-
let
|
|
101
|
-
let
|
|
102
|
-
|
|
205
|
+
// convert mdx code into html & paste into file
|
|
206
|
+
let mdxCode = fs.readFileSync(currentPath, 'utf8');
|
|
207
|
+
let parentDir = path.dirname(currentPath)
|
|
208
|
+
let htmlCode = await mdxToHtml(mdxCode, parentDir, configs?.modBundleMDXSettings);
|
|
209
|
+
createFile(absHtmlPath, `<!DOCTYPE html>\n${htmlCode}`);
|
|
103
210
|
|
|
104
211
|
|
|
105
|
-
//
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
212
|
+
// Broadcast file creation ended
|
|
213
|
+
configs?.onFileCreateEnd?.(currentPath, absHtmlPath)
|
|
214
|
+
}
|
|
215
|
+
// Copy paste file
|
|
216
|
+
else if (!isDir) {
|
|
217
|
+
log(`${currentPath} ---> ${absToOutput}`, true)
|
|
218
|
+
configs?.onFileCreateStart?.(currentPath, absToOutput)
|
|
219
|
+
fs.copyFileSync(currentPath, absToOutput)
|
|
220
|
+
configs?.onFileCreateEnd?.(currentPath, absToOutput)
|
|
221
|
+
}
|
|
109
222
|
|
|
110
223
|
|
|
111
|
-
|
|
112
|
-
|
|
224
|
+
// Skip if current path is a file or a directory to ignore
|
|
225
|
+
if (!isDir) {
|
|
226
|
+
continue
|
|
113
227
|
}
|
|
114
228
|
|
|
229
|
+
|
|
230
|
+
// Add to stack if current path is dir
|
|
231
|
+
const files = fs.readdirSync(currentPath);
|
|
232
|
+
for (const file of files) {
|
|
233
|
+
stack.push(path.join(currentPath, file));
|
|
234
|
+
}
|
|
115
235
|
}
|
|
116
236
|
|
|
117
|
-
|
|
118
|
-
|
|
237
|
+
|
|
238
|
+
// Unset creating status & Notify
|
|
239
|
+
isCreatingSite = false;
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
// Broadcast site creation ended
|
|
243
|
+
log("Created site")
|
|
244
|
+
configs?.onSiteCreateEnd?.(inputPath, outputPath)
|
|
119
245
|
}
|
|
120
|
-
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
// Main Methods
|
|
249
|
+
async function createSiteSafe(...args) {
|
|
250
|
+
try {
|
|
251
|
+
await createSite(...args);
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
isCreatingSite = false
|
|
255
|
+
console.log(err);
|
|
256
|
+
log("Failed to create site!");
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
async function listenForKey(createSiteCallback) {
|
|
121
260
|
|
|
122
261
|
readline.emitKeypressEvents(process.stdin);
|
|
123
262
|
|
|
124
|
-
if (process.stdin.isTTY)
|
|
263
|
+
if (process.stdin.isTTY) {
|
|
125
264
|
process.stdin.setRawMode(true);
|
|
265
|
+
}
|
|
126
266
|
|
|
127
267
|
process.stdin.on('keypress', (chunk, key) => {
|
|
128
268
|
if (key && key.name == 'r') {
|
|
129
|
-
|
|
269
|
+
createSiteCallback();
|
|
130
270
|
}
|
|
131
271
|
else if (key && key.sequence == '\x03') {
|
|
132
|
-
process.exit()
|
|
272
|
+
server.close((e) => { process.exit() })
|
|
133
273
|
}
|
|
134
274
|
});
|
|
135
275
|
}
|
|
136
|
-
function
|
|
276
|
+
function startServer(htmlDir, port) { // Starts server at given port
|
|
277
|
+
|
|
278
|
+
// Broadcast server starting
|
|
279
|
+
configs?.onHostStart?.(port)
|
|
280
|
+
|
|
137
281
|
|
|
138
282
|
// Start Server
|
|
139
|
-
const
|
|
283
|
+
const newServer = http.createServer((req, res) => {
|
|
140
284
|
|
|
141
285
|
// Parse & Sanitize URL
|
|
142
|
-
let
|
|
143
|
-
let
|
|
144
|
-
let
|
|
145
|
-
let
|
|
146
|
-
let
|
|
147
|
-
let
|
|
286
|
+
let parsedUrl = new URL("http://" + req.headers.host + req.url)
|
|
287
|
+
let sanitizedUrl = path.normalize(parsedUrl.pathname).replace(/^(\.\.[\/\\])+/, '')
|
|
288
|
+
let isDirectory = !Boolean(path.parse(sanitizedUrl).ext)
|
|
289
|
+
let relativeFilePath = path.normalize(sanitizedUrl + (isDirectory ? "/index.html" : ""))
|
|
290
|
+
let absoluteFilePath = path.join(path.resolve(htmlDir), relativeFilePath)
|
|
291
|
+
let pathExists = fs.existsSync(absoluteFilePath)
|
|
148
292
|
|
|
149
293
|
// Respondes with content of file
|
|
150
|
-
if (
|
|
294
|
+
if (pathExists)
|
|
151
295
|
// read file from file system
|
|
152
|
-
fs.readFile(
|
|
296
|
+
fs.readFile(absoluteFilePath, function (err, data) {
|
|
153
297
|
if (err) {
|
|
154
298
|
res.statusCode = 500
|
|
155
299
|
res.end(`Error getting the file: ${err}.`)
|
|
156
300
|
}
|
|
157
301
|
else {
|
|
158
302
|
// Based on the URL path, extract the file extention. e.g. .js, .doc, ...
|
|
159
|
-
const ext = path.parse(
|
|
303
|
+
const ext = path.parse(absoluteFilePath).ext
|
|
160
304
|
res.setHeader('Content-type', MIME_TYPE[ext] || 'text/plain') // if the file is found, set Content-type and send data
|
|
161
305
|
res.end(data)
|
|
162
306
|
}
|
|
@@ -167,90 +311,97 @@ function start_server(html_dir, port) { // Starts server at given port
|
|
|
167
311
|
res.end(`404 Invalid url not found!`)
|
|
168
312
|
}
|
|
169
313
|
})
|
|
314
|
+
newServer.listen(port, () => { log(`Server listening at ${port} ... (Press 'r' to manually reload, Press 'Ctrl+c' to exit)`) })
|
|
315
|
+
newServer.on("close", () => { configs?.onHostEnd?.(port) });
|
|
170
316
|
|
|
171
|
-
|
|
317
|
+
return newServer
|
|
172
318
|
}
|
|
173
|
-
async function
|
|
174
|
-
|
|
319
|
+
async function Main() {
|
|
175
320
|
// Get all arguments
|
|
176
321
|
const args = process.argv.slice(2)
|
|
177
322
|
|
|
178
323
|
|
|
179
324
|
// Check if asked for help
|
|
180
|
-
if (args
|
|
325
|
+
if (args.includes(HELP_FLAG) || args.includes(HELP_SHORT_FLAG)) {
|
|
181
326
|
console.log(HELP_MESSAGE)
|
|
182
327
|
return;
|
|
183
328
|
}
|
|
184
329
|
|
|
185
330
|
|
|
186
|
-
//
|
|
187
|
-
|
|
188
|
-
if (!fs.existsSync(mdx_path)) {
|
|
189
|
-
console.log(INVALID_MDX_DIR_ERROR)
|
|
190
|
-
return
|
|
191
|
-
}
|
|
331
|
+
// Assign to create
|
|
332
|
+
let toCreateOnly = args.includes(CREATE_FLAG) || args.includes(CREATE_SHORT_FLAG)
|
|
192
333
|
|
|
334
|
+
// Assign input path
|
|
335
|
+
let inputPath = args.find(val => val.startsWith(INPUT_PATH_FLAG))
|
|
336
|
+
inputPath = inputPath !== undefined ? inputPath.split('=')[1] : process.cwd()
|
|
193
337
|
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
port_number = Number(port)
|
|
338
|
+
// Assign output path
|
|
339
|
+
let outputPath = args.find(val => val.startsWith(OUTPUT_PATH_FLAG))
|
|
340
|
+
let outputPathProvided = outputPath !== undefined
|
|
341
|
+
outputPath = outputPathProvided ? outputPath.split('=')[1] : createTempDir()
|
|
199
342
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
return
|
|
203
|
-
}
|
|
204
|
-
}
|
|
343
|
+
// Assign tracking changes
|
|
344
|
+
let toTrackChanges = args.includes(TRACK_CHANGES_FLAG) || args.includes(TRACK_CHANGES_SHORT_FLAG)
|
|
205
345
|
|
|
346
|
+
// Assign port
|
|
347
|
+
let port = args.find(val => val.startsWith(PORT_FLAG))
|
|
348
|
+
port = port !== undefined ? Number(port.split('=')[1]) : DEFAULT_PORT
|
|
206
349
|
|
|
207
|
-
//
|
|
208
|
-
|
|
209
|
-
if (html_path !== undefined) {
|
|
350
|
+
// Assign verbose
|
|
351
|
+
isVerbose = args.includes(VERBOSE_FLAG) || args.includes(VERBOSE_SHORT_FLAG)
|
|
210
352
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
catch {
|
|
218
|
-
console.log(INVALID_HTML_DIR_ERROR)
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
353
|
+
|
|
354
|
+
// Check input path
|
|
355
|
+
if (!fs.existsSync(inputPath) || !fs.lstatSync(inputPath).isDirectory()) {
|
|
356
|
+
log(`Invalid input path "${inputPath}"`)
|
|
357
|
+
return
|
|
221
358
|
}
|
|
222
|
-
else {
|
|
223
359
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
360
|
+
// Check output path
|
|
361
|
+
if (!fs.existsSync(outputPath) || !fs.lstatSync(outputPath).isDirectory()) {
|
|
362
|
+
log(`Invalid output path "${outputPath}"`)
|
|
363
|
+
return
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Check port
|
|
367
|
+
if (!Number.isInteger(port)) {
|
|
368
|
+
log(`Invalid port`)
|
|
369
|
+
return
|
|
370
|
+
}
|
|
228
371
|
|
|
229
|
-
|
|
372
|
+
|
|
373
|
+
// Create site from mdx & return if only needed to create site
|
|
374
|
+
await createSiteSafe(inputPath, outputPath)
|
|
375
|
+
if (toCreateOnly) {
|
|
376
|
+
return;
|
|
230
377
|
}
|
|
231
378
|
|
|
232
379
|
|
|
233
|
-
//
|
|
234
|
-
|
|
235
|
-
var abs_html_path = path.resolve(html_path)
|
|
236
|
-
create_site(abs_mdx_path, abs_html_path)
|
|
380
|
+
// Watch for key presses
|
|
381
|
+
listenForKey(() => createSiteSafe(inputPath, outputPath))
|
|
237
382
|
|
|
238
383
|
|
|
239
|
-
//
|
|
240
|
-
|
|
241
|
-
|
|
384
|
+
// Watch for changes
|
|
385
|
+
if (toTrackChanges) {
|
|
386
|
+
chokidar.watch(inputPath, { ignoreInitial: true }).on('all', (event, path) => {
|
|
387
|
+
createSiteSafe(inputPath, outputPath)
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
|
|
242
391
|
|
|
392
|
+
// Start server
|
|
393
|
+
server = startServer(outputPath, port)
|
|
243
394
|
|
|
244
|
-
// Remove temp html directory
|
|
245
|
-
process.on("SIGINT", () => {
|
|
246
395
|
|
|
396
|
+
// Handle quit
|
|
397
|
+
process.on("exit", () => {
|
|
247
398
|
// Remove html path
|
|
248
|
-
if (fs.existsSync(
|
|
249
|
-
fs.rmSync(
|
|
399
|
+
if (!outputPathProvided && fs.existsSync(outputPath)) {
|
|
400
|
+
fs.rmSync(outputPath, { recursive: true, force: true })
|
|
250
401
|
}
|
|
251
402
|
|
|
252
403
|
process.exit(0);
|
|
253
404
|
});
|
|
254
405
|
}
|
|
255
406
|
|
|
256
|
-
|
|
407
|
+
Main()
|
package/mdx-to-html.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import rehypeHighlight from "rehype-highlight";
|
|
3
|
+
import * as _jsx_runtime from 'react/jsx-runtime';
|
|
4
|
+
import { common } from 'lowlight';
|
|
5
|
+
import { bundleMDX } from 'mdx-bundler';
|
|
6
|
+
import { getMDXComponent } from 'mdx-bundler/client/index.js'
|
|
7
|
+
import { renderToString } from 'react-dom/server';
|
|
8
|
+
import { createRequire } from 'module';
|
|
9
|
+
|
|
10
|
+
const nativeRequire = createRequire(import.meta.url);
|
|
11
|
+
|
|
12
|
+
export async function mdxToHtml(mdxCode, baseUrl, modSettingsCallback = undefined) {
|
|
13
|
+
|
|
14
|
+
// Assign default settings
|
|
15
|
+
let settings = {
|
|
16
|
+
source: mdxCode,
|
|
17
|
+
cwd: baseUrl,
|
|
18
|
+
esbuildOptions: (options) => {
|
|
19
|
+
options.platform = 'node'
|
|
20
|
+
return options;
|
|
21
|
+
},
|
|
22
|
+
mdxOptions(options) {
|
|
23
|
+
options.rehypePlugins = [
|
|
24
|
+
...(options.rehypePlugins ?? []),
|
|
25
|
+
[rehypeHighlight, { languages: { ...common } }]
|
|
26
|
+
];
|
|
27
|
+
return options;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
// Modify settings
|
|
33
|
+
if(modSettingsCallback !== undefined){
|
|
34
|
+
settings = modSettingsCallback(settings)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
// Generate html
|
|
39
|
+
const { code } = await bundleMDX(settings);
|
|
40
|
+
const Component = getMDXComponent(code, { require: nativeRequire, cwd: baseUrl })
|
|
41
|
+
return renderToString(React.createElement(Component));
|
|
42
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "host-mdx",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Creates and serves a github pages style html directory from a corresponding mdx directory",
|
|
5
5
|
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
6
7
|
"scripts": {
|
|
7
8
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
9
|
},
|
|
@@ -10,16 +11,21 @@
|
|
|
10
11
|
"host-mdx": "index.js"
|
|
11
12
|
},
|
|
12
13
|
"dependencies": {
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"react
|
|
18
|
-
"
|
|
14
|
+
"chokidar": "^5.0.0",
|
|
15
|
+
"ignore": "^7.0.5",
|
|
16
|
+
"lowlight": "^3.3.0",
|
|
17
|
+
"mdx-bundler": "^10.1.1",
|
|
18
|
+
"react": "^19.2.3",
|
|
19
|
+
"react-dom": "^19.2.3",
|
|
20
|
+
"rehype-highlight": "^7.0.2"
|
|
19
21
|
},
|
|
20
22
|
"keywords": [
|
|
21
23
|
"mdx"
|
|
22
24
|
],
|
|
23
25
|
"author": "Manas Makde",
|
|
24
|
-
"license": "ISC"
|
|
26
|
+
"license": "ISC",
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@babel/preset-react": "^7.28.5",
|
|
29
|
+
"@babel/register": "^7.28.3"
|
|
30
|
+
}
|
|
25
31
|
}
|