host-mdx 1.0.13 → 2.0.1
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 +296 -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,229 @@ 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() {
|
|
100
|
+
|
|
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
|
+
}
|
|
120
|
+
|
|
121
|
+
ig.add(ignoreContent);
|
|
54
122
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
123
|
+
return ig
|
|
124
|
+
}
|
|
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
|
+
}
|
|
61
132
|
|
|
62
|
-
return minify(html_code, { minifyCSS: true })
|
|
63
133
|
|
|
134
|
+
// Create file
|
|
135
|
+
fs.writeFileSync(filePath, fileContent);
|
|
64
136
|
}
|
|
65
|
-
async function
|
|
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
|
+
// Continue if path does not exist
|
|
176
|
+
const currentPath = stack.pop()
|
|
177
|
+
if(!fs.existsSync(currentPath)){
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
// Get essentials
|
|
183
|
+
const relToInput = path.relative(inputPath, currentPath)
|
|
184
|
+
const toIgnore = inputPath != currentPath && ig.ignores(relToInput)
|
|
185
|
+
const absToOutput = path.join(outputPath, relToInput)
|
|
186
|
+
const isDir = fs.statSync(currentPath).isDirectory()
|
|
187
|
+
const isMdx = !isDir && absToOutput.endsWith(".mdx")
|
|
188
|
+
|
|
85
189
|
|
|
190
|
+
// Skip if to ignore this path
|
|
191
|
+
if (toIgnore) {
|
|
192
|
+
continue
|
|
193
|
+
}
|
|
86
194
|
|
|
87
|
-
// Iterate through files
|
|
88
|
-
let files = fs.readdirSync(create_from_path, { withFileTypes: true, recursive: true });
|
|
89
195
|
|
|
90
|
-
|
|
91
|
-
|
|
196
|
+
// Make dir
|
|
197
|
+
if (isDir) {
|
|
198
|
+
log(`${currentPath} ---> ${absToOutput}`, true)
|
|
199
|
+
configs?.onFileCreateStart?.(currentPath, absToOutput)
|
|
200
|
+
fs.mkdirSync(absToOutput, { recursive: true });
|
|
201
|
+
configs?.onFileCreateEnd?.(currentPath, absToOutput)
|
|
202
|
+
}
|
|
203
|
+
// Make html file from mdx
|
|
204
|
+
else if (!isDir && isMdx) {
|
|
92
205
|
|
|
93
|
-
|
|
206
|
+
// Broadcast file creation started
|
|
207
|
+
let absHtmlPath = path.format({ ...path.parse(absToOutput), base: '', ext: '.html' })
|
|
208
|
+
log(`${currentPath} ---> ${absHtmlPath}`, true)
|
|
209
|
+
configs?.onFileCreateStart?.(currentPath, absHtmlPath)
|
|
94
210
|
|
|
95
|
-
// To ensure file paths work
|
|
96
|
-
process.chdir(file.parentPath);
|
|
97
211
|
|
|
98
|
-
|
|
99
|
-
let
|
|
100
|
-
let
|
|
101
|
-
let
|
|
102
|
-
|
|
212
|
+
// convert mdx code into html & paste into file
|
|
213
|
+
let mdxCode = fs.readFileSync(currentPath, 'utf8');
|
|
214
|
+
let parentDir = path.dirname(currentPath)
|
|
215
|
+
let htmlCode = await mdxToHtml(mdxCode, parentDir, configs?.modBundleMDXSettings);
|
|
216
|
+
createFile(absHtmlPath, `<!DOCTYPE html>\n${htmlCode}`);
|
|
103
217
|
|
|
104
218
|
|
|
105
|
-
//
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
219
|
+
// Broadcast file creation ended
|
|
220
|
+
configs?.onFileCreateEnd?.(currentPath, absHtmlPath)
|
|
221
|
+
}
|
|
222
|
+
// Copy paste file
|
|
223
|
+
else if (!isDir) {
|
|
224
|
+
log(`${currentPath} ---> ${absToOutput}`, true)
|
|
225
|
+
configs?.onFileCreateStart?.(currentPath, absToOutput)
|
|
226
|
+
fs.copyFileSync(currentPath, absToOutput)
|
|
227
|
+
configs?.onFileCreateEnd?.(currentPath, absToOutput)
|
|
228
|
+
}
|
|
109
229
|
|
|
110
230
|
|
|
111
|
-
|
|
112
|
-
|
|
231
|
+
// Skip if current path is a file or a directory to ignore
|
|
232
|
+
if (!isDir) {
|
|
233
|
+
continue
|
|
113
234
|
}
|
|
114
235
|
|
|
236
|
+
|
|
237
|
+
// Add to stack if current path is dir
|
|
238
|
+
const files = fs.readdirSync(currentPath);
|
|
239
|
+
for (const file of files) {
|
|
240
|
+
stack.push(path.join(currentPath, file));
|
|
241
|
+
}
|
|
115
242
|
}
|
|
116
243
|
|
|
117
|
-
|
|
118
|
-
|
|
244
|
+
|
|
245
|
+
// Unset creating status & Notify
|
|
246
|
+
isCreatingSite = false;
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
// Broadcast site creation ended
|
|
250
|
+
log("Created site")
|
|
251
|
+
configs?.onSiteCreateEnd?.(inputPath, outputPath)
|
|
119
252
|
}
|
|
120
|
-
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
// Main Methods
|
|
256
|
+
async function createSiteSafe(...args) {
|
|
257
|
+
try {
|
|
258
|
+
await createSite(...args);
|
|
259
|
+
}
|
|
260
|
+
catch (err) {
|
|
261
|
+
isCreatingSite = false
|
|
262
|
+
console.log(err);
|
|
263
|
+
log("Failed to create site!");
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
async function listenForKey(createSiteCallback) {
|
|
121
267
|
|
|
122
268
|
readline.emitKeypressEvents(process.stdin);
|
|
123
269
|
|
|
124
|
-
if (process.stdin.isTTY)
|
|
270
|
+
if (process.stdin.isTTY) {
|
|
125
271
|
process.stdin.setRawMode(true);
|
|
272
|
+
}
|
|
126
273
|
|
|
127
274
|
process.stdin.on('keypress', (chunk, key) => {
|
|
128
275
|
if (key && key.name == 'r') {
|
|
129
|
-
|
|
276
|
+
createSiteCallback();
|
|
130
277
|
}
|
|
131
278
|
else if (key && key.sequence == '\x03') {
|
|
132
|
-
process.exit()
|
|
279
|
+
server.close((e) => { process.exit() })
|
|
133
280
|
}
|
|
134
281
|
});
|
|
135
282
|
}
|
|
136
|
-
function
|
|
283
|
+
function startServer(htmlDir, port) { // Starts server at given port
|
|
284
|
+
|
|
285
|
+
// Broadcast server starting
|
|
286
|
+
configs?.onHostStart?.(port)
|
|
287
|
+
|
|
137
288
|
|
|
138
289
|
// Start Server
|
|
139
|
-
const
|
|
290
|
+
const newServer = http.createServer((req, res) => {
|
|
140
291
|
|
|
141
292
|
// Parse & Sanitize URL
|
|
142
|
-
let
|
|
143
|
-
let
|
|
144
|
-
let
|
|
145
|
-
let
|
|
146
|
-
let
|
|
147
|
-
let
|
|
293
|
+
let parsedUrl = new URL("http://" + req.headers.host + req.url)
|
|
294
|
+
let sanitizedUrl = path.normalize(parsedUrl.pathname).replace(/^(\.\.[\/\\])+/, '')
|
|
295
|
+
let isDirectory = !Boolean(path.parse(sanitizedUrl).ext)
|
|
296
|
+
let relativeFilePath = path.normalize(sanitizedUrl + (isDirectory ? "/index.html" : ""))
|
|
297
|
+
let absoluteFilePath = path.join(path.resolve(htmlDir), relativeFilePath)
|
|
298
|
+
let pathExists = fs.existsSync(absoluteFilePath)
|
|
148
299
|
|
|
149
300
|
// Respondes with content of file
|
|
150
|
-
if (
|
|
301
|
+
if (pathExists)
|
|
151
302
|
// read file from file system
|
|
152
|
-
fs.readFile(
|
|
303
|
+
fs.readFile(absoluteFilePath, function (err, data) {
|
|
153
304
|
if (err) {
|
|
154
305
|
res.statusCode = 500
|
|
155
306
|
res.end(`Error getting the file: ${err}.`)
|
|
156
307
|
}
|
|
157
308
|
else {
|
|
158
309
|
// Based on the URL path, extract the file extention. e.g. .js, .doc, ...
|
|
159
|
-
const ext = path.parse(
|
|
310
|
+
const ext = path.parse(absoluteFilePath).ext
|
|
160
311
|
res.setHeader('Content-type', MIME_TYPE[ext] || 'text/plain') // if the file is found, set Content-type and send data
|
|
161
312
|
res.end(data)
|
|
162
313
|
}
|
|
@@ -167,90 +318,102 @@ function start_server(html_dir, port) { // Starts server at given port
|
|
|
167
318
|
res.end(`404 Invalid url not found!`)
|
|
168
319
|
}
|
|
169
320
|
})
|
|
321
|
+
newServer.listen(port, () => { log(`Server listening at ${port} ... (Press 'r' to manually reload, Press 'Ctrl+c' to exit)`) })
|
|
322
|
+
newServer.on("close", () => { configs?.onHostEnd?.(port) });
|
|
323
|
+
newServer.on("error", (e) => { log(`Error Starting server ${e.message}`); throw e; });
|
|
170
324
|
|
|
171
|
-
server.listen(port, () => { console.log(`Server listening at ${port} ... (Press 'r' to reload, Press 'Ctrl+c' to exit)`) })
|
|
172
|
-
}
|
|
173
|
-
async function main() {
|
|
174
325
|
|
|
326
|
+
return newServer
|
|
327
|
+
}
|
|
328
|
+
async function Main() {
|
|
175
329
|
// Get all arguments
|
|
176
330
|
const args = process.argv.slice(2)
|
|
177
331
|
|
|
178
332
|
|
|
179
333
|
// Check if asked for help
|
|
180
|
-
if (args
|
|
334
|
+
if (args.includes(HELP_FLAG) || args.includes(HELP_SHORT_FLAG)) {
|
|
181
335
|
console.log(HELP_MESSAGE)
|
|
182
336
|
return;
|
|
183
337
|
}
|
|
184
338
|
|
|
185
339
|
|
|
186
|
-
//
|
|
187
|
-
|
|
188
|
-
if (!fs.existsSync(mdx_path)) {
|
|
189
|
-
console.log(INVALID_MDX_DIR_ERROR)
|
|
190
|
-
return
|
|
191
|
-
}
|
|
340
|
+
// Assign to create
|
|
341
|
+
let toCreateOnly = args.includes(CREATE_FLAG) || args.includes(CREATE_SHORT_FLAG)
|
|
192
342
|
|
|
343
|
+
// Assign input path
|
|
344
|
+
let inputPath = args.find(val => val.startsWith(INPUT_PATH_FLAG))
|
|
345
|
+
inputPath = inputPath !== undefined ? inputPath.split('=')[1] : process.cwd()
|
|
193
346
|
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
port_number = Number(port)
|
|
347
|
+
// Assign output path
|
|
348
|
+
let outputPath = args.find(val => val.startsWith(OUTPUT_PATH_FLAG))
|
|
349
|
+
let outputPathProvided = outputPath !== undefined
|
|
350
|
+
outputPath = outputPathProvided ? outputPath.split('=')[1] : createTempDir()
|
|
199
351
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
return
|
|
203
|
-
}
|
|
204
|
-
}
|
|
352
|
+
// Assign tracking changes
|
|
353
|
+
let toTrackChanges = args.includes(TRACK_CHANGES_FLAG) || args.includes(TRACK_CHANGES_SHORT_FLAG)
|
|
205
354
|
|
|
355
|
+
// Assign port
|
|
356
|
+
let port = args.find(val => val.startsWith(PORT_FLAG))
|
|
357
|
+
port = port !== undefined ? Number(port.split('=')[1]) : DEFAULT_PORT
|
|
206
358
|
|
|
207
|
-
//
|
|
208
|
-
|
|
209
|
-
if (html_path !== undefined) {
|
|
359
|
+
// Assign verbose
|
|
360
|
+
isVerbose = args.includes(VERBOSE_FLAG) || args.includes(VERBOSE_SHORT_FLAG)
|
|
210
361
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
catch {
|
|
218
|
-
console.log(INVALID_HTML_DIR_ERROR)
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
362
|
+
|
|
363
|
+
// Check input path
|
|
364
|
+
if (!fs.existsSync(inputPath) || !fs.lstatSync(inputPath).isDirectory()) {
|
|
365
|
+
log(`Invalid input path "${inputPath}"`)
|
|
366
|
+
return
|
|
221
367
|
}
|
|
222
|
-
else {
|
|
223
368
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
369
|
+
// Check output path
|
|
370
|
+
if (!fs.existsSync(outputPath) || !fs.lstatSync(outputPath).isDirectory()) {
|
|
371
|
+
log(`Invalid output path "${outputPath}"`)
|
|
372
|
+
return
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Check port
|
|
376
|
+
if (!Number.isInteger(port)) {
|
|
377
|
+
log(`Invalid port`)
|
|
378
|
+
return
|
|
379
|
+
}
|
|
228
380
|
|
|
229
|
-
|
|
381
|
+
|
|
382
|
+
// Create site from mdx & return if only needed to create site
|
|
383
|
+
await createSiteSafe(inputPath, outputPath)
|
|
384
|
+
if (toCreateOnly) {
|
|
385
|
+
return;
|
|
230
386
|
}
|
|
231
387
|
|
|
232
388
|
|
|
233
|
-
//
|
|
234
|
-
|
|
235
|
-
var abs_html_path = path.resolve(html_path)
|
|
236
|
-
create_site(abs_mdx_path, abs_html_path)
|
|
389
|
+
// Watch for key presses
|
|
390
|
+
listenForKey(() => createSiteSafe(inputPath, outputPath))
|
|
237
391
|
|
|
238
392
|
|
|
239
|
-
//
|
|
240
|
-
|
|
241
|
-
|
|
393
|
+
// Watch for changes
|
|
394
|
+
if (toTrackChanges) {
|
|
395
|
+
chokidar.watch(inputPath, {
|
|
396
|
+
ignoreInitial: true,
|
|
397
|
+
ignored: (path, stats) => isCreatingSite // Ignore if site creation is ongoing
|
|
398
|
+
}).on('all', (event, path) => {
|
|
399
|
+
createSiteSafe(inputPath, outputPath)
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
|
|
242
403
|
|
|
404
|
+
// Start server
|
|
405
|
+
server = startServer(outputPath, port)
|
|
243
406
|
|
|
244
|
-
// Remove temp html directory
|
|
245
|
-
process.on("SIGINT", () => {
|
|
246
407
|
|
|
408
|
+
// Handle quit
|
|
409
|
+
process.on("exit", () => {
|
|
247
410
|
// Remove html path
|
|
248
|
-
if (fs.existsSync(
|
|
249
|
-
fs.rmSync(
|
|
411
|
+
if (!outputPathProvided && fs.existsSync(outputPath)) {
|
|
412
|
+
fs.rmSync(outputPath, { recursive: true, force: true })
|
|
250
413
|
}
|
|
251
414
|
|
|
252
415
|
process.exit(0);
|
|
253
416
|
});
|
|
254
417
|
}
|
|
255
418
|
|
|
256
|
-
|
|
419
|
+
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.1",
|
|
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
|
}
|