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.
Files changed (4) hide show
  1. package/README.md +87 -13
  2. package/index.js +296 -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,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
- // 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() {
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
- 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))
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 create_site(create_from_path, create_at_path) {
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
+ // 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
- for (const file of files) {
91
- const file_path = path.join(file.parentPath, file.name)
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
- if (file.isFile() && file_path.endsWith(".mdx")) {
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
- 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)
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
- // Make directory if it doesn't exist
106
- if (!fs.existsSync(html_dir_path)) {
107
- fs.mkdirSync(html_dir_path, { recursive: true })
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
- // write to file
112
- fs.writeFileSync(html_file_path, html_code);
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
- is_creating_site = false;
118
- console.log(FINISHED_CREATING_SITE)
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
- async function watch_for_reload(mdx_dir, html_dir) { // Watches `mdx_dir` files for any code change
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
- create_site(mdx_dir, html_dir)
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 start_server(html_dir, port) { // Starts server at given port
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 server = http.createServer((req, res) => {
290
+ const newServer = http.createServer((req, res) => {
140
291
 
141
292
  // 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)
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 (path_exists)
301
+ if (pathExists)
151
302
  // read file from file system
152
- fs.readFile(absolute_file_path, function (err, data) {
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(absolute_file_path).ext
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[0] === "--help" || args[0] === "-h") {
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
- // 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
- }
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
- // 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)
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
- if (!Number.isInteger(port_number)) {
201
- console.log(INVALID_PORT_NUMBER_ERROR)
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
- // Check if valid html folder path
208
- var html_path = args[2];
209
- if (html_path !== undefined) {
359
+ // Assign verbose
360
+ isVerbose = args.includes(VERBOSE_FLAG) || args.includes(VERBOSE_SHORT_FLAG)
210
361
 
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
- }
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
- // Create default temp html dir
225
- if (!fs.existsSync(TEMP_HTML_DIR)) {
226
- fs.mkdirSync(TEMP_HTML_DIR, { recursive: true })
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
- html_path = fs.mkdtempSync(path.join(TEMP_HTML_DIR, `/html-`));
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
- // 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)
389
+ // Watch for key presses
390
+ listenForKey(() => createSiteSafe(inputPath, outputPath))
237
391
 
238
392
 
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)
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(abs_html_path)) {
249
- fs.rmSync(abs_html_path, { recursive: true, force: true })
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
- await main();
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": "1.0.13",
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
- "@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
  }