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.
Files changed (4) hide show
  1. package/README.md +87 -13
  2. package/index.js +284 -133
  3. package/mdx-to-html.js +42 -0
  4. package/package.json +14 -8
package/README.md CHANGED
@@ -1,49 +1,123 @@
1
1
  # host-mdx
2
2
 
3
- This creates and serves a [github pages](https://docs.github.com/en/pages) style html directory from a corresponding mdx directory
3
+ [![Version](https://img.shields.io/npm/v/host-mdx.svg)](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
- deno npm:host-mdx [<mdx-dir>] [<optional-port-number>] [<optional-html-dir>]
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
- ## Example
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
- deno npm:host-mdx "/home/username/my-website-template/" 3113 "/home/username/Desktop/my-website/"
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
- └─ index.mdx
22
- ├─ projects/
23
- ├─ project1/
55
+ ├─ index.mdx
56
+ │ └─ custom_component.jsx
57
+ ├─ blog/
58
+ │ ├─ page1/
24
59
  │ │ └─ index.mdx
25
- │ └─ project2/
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
- ├─ projects/
39
- ├─ project1/
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
- console.log("version 1.0.13")
4
-
5
- import fs from 'node:fs'
6
- import os from 'node:os'
7
- import path from 'node:path'
8
- import http from 'node:http'
9
- import process from "node:process"
10
- import { pathToFileURL } from "node:url"
11
- import { minify } from 'html-minifier-terser'
12
- import * as runtime from 'react/jsx-runtime'
13
- import { createElement } from 'react'
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
- let TEMP_HTML_DIR = path.join(os.tmpdir(), `${APP_NAME}`)
27
- const HELP_MESSAGE = `deno ${APP_NAME} [mdx-path] [<optional-port-number>] [<optional-html-dir>]`
28
- const STARTED_CREATING_SITE = `==== Started creating site ==== `
29
- const FINISHED_CREATING_SITE = `==== Finished creating site ==== `
30
- const INVALID_MDX_DIR_ERROR = `Invalid mdx directory (1st argument) provided!`
31
- const INVALID_PORT_NUMBER_ERROR = `Invalid port number (2nd argument) provided!`
32
- const INVALID_HTML_DIR_ERROR = `Invalid html directory (3rd argument) could not create/find!`
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
- // Functions
53
- async function mdx_to_html(mdx_code, base_url) { // converts mdx code into html code, `base_url` is the path from where all the mdx import path relatively work from
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
- const jsx = (await evaluate(mdx_code, {
56
- ...runtime,
57
- rehypePlugins: [[rehypeHighlight, { languages: { ...common, gdscript } }]],
58
- baseUrl: base_url
59
- })).default
60
- const html_code = renderToString(createElement(jsx))
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
- return minify(html_code, { minifyCSS: true })
121
+ ig.add(ignoreContent);
63
122
 
123
+ return ig
64
124
  }
65
- async function create_site(create_from_path, create_at_path) {
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 (is_creating_site) {
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
- is_creating_site = true
74
- console.log(STARTED_CREATING_SITE)
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(create_at_path)) {
79
- fs.rmSync(create_at_path, { recursive: true, force: true });
162
+ if (fs.existsSync(outputPath)) {
163
+ fs.rmSync(outputPath, { recursive: true, force: true });
80
164
  }
81
165
 
82
166
 
83
- // copy paste directory
84
- fs.cpSync(create_from_path, create_at_path, { recursive: true });
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
- // Iterate through files
88
- let files = fs.readdirSync(create_from_path, { withFileTypes: true, recursive: true });
183
+ // Skip if to ignore this path
184
+ if (toIgnore) {
185
+ continue
186
+ }
187
+
89
188
 
90
- for (const file of files) {
91
- const file_path = path.join(file.parentPath, file.name)
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
- if (file.isFile() && file_path.endsWith(".mdx")) {
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
- let base_url = pathToFileURL(path.normalize(path.join(create_from_path, path.sep))).href // Converts file into file uri i.e "file:///my/path/"
99
- let mdx_code = fs.readFileSync(file_path, 'utf8');
100
- let html_code = await mdx_to_html(mdx_code, base_url);
101
- let html_file_path = file_path.replace(create_from_path, create_at_path).replace(".mdx", ".html")
102
- let html_dir_path = path.dirname(html_file_path)
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
- // Make directory if it doesn't exist
106
- if (!fs.existsSync(html_dir_path)) {
107
- fs.mkdirSync(html_dir_path, { recursive: true })
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
- // write to file
112
- fs.writeFileSync(html_file_path, html_code);
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
- is_creating_site = false;
118
- console.log(FINISHED_CREATING_SITE)
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
- async function watch_for_reload(mdx_dir, html_dir) { // Watches `mdx_dir` files for any code change
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
- create_site(mdx_dir, html_dir)
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 start_server(html_dir, port) { // Starts server at given port
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 server = http.createServer((req, res) => {
283
+ const newServer = http.createServer((req, res) => {
140
284
 
141
285
  // Parse & Sanitize URL
142
- let parsed_url = new URL("http://" + req.headers.host + req.url)
143
- let sanitized_url = path.normalize(parsed_url.pathname).replace(/^(\.\.[\/\\])+/, '')
144
- let is_directory = !Boolean(path.parse(sanitized_url).ext)
145
- let relative_file_path = path.normalize(sanitized_url + (is_directory ? "/index.html" : ""))
146
- let absolute_file_path = path.join(path.resolve(html_dir), relative_file_path)
147
- let path_exists = fs.existsSync(absolute_file_path)
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 (path_exists)
294
+ if (pathExists)
151
295
  // read file from file system
152
- fs.readFile(absolute_file_path, function (err, data) {
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(absolute_file_path).ext
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
- server.listen(port, () => { console.log(`Server listening at ${port} ... (Press 'r' to reload, Press 'Ctrl+c' to exit)`) })
317
+ return newServer
172
318
  }
173
- async function main() {
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[0] === "--help" || args[0] === "-h") {
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
- // Check if valid mdx folder path
187
- var mdx_path = args[0];
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
- // Check if valid port number
195
- var port = args[1]
196
- var port_number = DEFAULT_PORT
197
- if (port != undefined) {
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
- if (!Number.isInteger(port_number)) {
201
- console.log(INVALID_PORT_NUMBER_ERROR)
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
- // Check if valid html folder path
208
- var html_path = args[2];
209
- if (html_path !== undefined) {
350
+ // Assign verbose
351
+ isVerbose = args.includes(VERBOSE_FLAG) || args.includes(VERBOSE_SHORT_FLAG)
210
352
 
211
- // Create user given html dir if it does not exist
212
- try {
213
- if (!fs.existsSync(html_path)) {
214
- fs.mkdirSync(html_path, { recursive: true })
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
- // Create default temp html dir
225
- if (!fs.existsSync(TEMP_HTML_DIR)) {
226
- fs.mkdirSync(TEMP_HTML_DIR, { recursive: true })
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
- html_path = fs.mkdtempSync(path.join(TEMP_HTML_DIR, `/html-`));
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
- // Create site given html & mdx locations
234
- var abs_mdx_path = path.resolve(mdx_path)
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
- // Start server and watch for changes if flag is passed
240
- watch_for_reload(abs_mdx_path, abs_html_path)
241
- start_server(abs_html_path, port_number)
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(abs_html_path)) {
249
- fs.rmSync(abs_html_path, { recursive: true, force: true })
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
- await main();
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": "1.0.13",
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
- "@exercism/highlightjs-gdscript": "^0.0.1",
14
- "@mdx-js/mdx": "^3.1.0",
15
- "html-minifier-terser": "^7.2.0",
16
- "react": "^19.0.0",
17
- "react-dom": "^19.0.0",
18
- "rehype-highlight": "^7.0.1"
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
  }