host-mdx 2.0.1 → 2.1.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/LICENSE +21 -0
- package/README.md +13 -6
- package/index.js +166 -151
- package/mdx-to-html.js +35 -9
- package/package.json +7 -5
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) Manas Ravindra Makde <manasmakde@gmail.com> (https://manasmakde.github.io/)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
A cli tool to create and serve a static html website from a given mdx directory
|
|
6
6
|
|
|
7
|
-
## Usage
|
|
7
|
+
## 🛠️ Usage
|
|
8
8
|
|
|
9
9
|
```
|
|
10
10
|
host-mdx [options]
|
|
@@ -18,12 +18,15 @@ Options:
|
|
|
18
18
|
--track-changes, -t Tracks any changes made & auto reloads
|
|
19
19
|
--verobse, -v Shows additional log messages
|
|
20
20
|
```
|
|
21
|
+
|
|
22
|
+
> If `--input-path` is not provided it will default to `./` i.e. current working directory
|
|
23
|
+
> If `--output-path` is not provided a temp folder will be created automatically & deleted upon exit
|
|
21
24
|
|
|
22
25
|
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
26
|
(similar to [.gitignore](https://git-scm.com/docs/gitignore))
|
|
24
27
|
|
|
25
28
|
|
|
26
|
-
Add a file by the name `host-mdx.js` at the root of your
|
|
29
|
+
Add a file by the name `host-mdx.js` at the root of your input folder as a config file with the following:
|
|
27
30
|
|
|
28
31
|
```js
|
|
29
32
|
// Modify
|
|
@@ -32,19 +35,20 @@ modBundleMDXSettings(settings)
|
|
|
32
35
|
|
|
33
36
|
// Hooks
|
|
34
37
|
onSiteCreateStart(inputPath, outputPath)
|
|
35
|
-
onSiteCreateEnd(inputPath, outputPath)
|
|
38
|
+
onSiteCreateEnd(inputPath, outputPath, wasInterrupted)
|
|
36
39
|
onFileCreateStart(inputFilePath, outputFilePath)
|
|
37
40
|
onFileCreateEnd(inputFilePath, outputFilePath)
|
|
38
41
|
```
|
|
39
42
|
> Note: Any changes made to `host-mdx.js` require complete restart otherwise changes will not reflect
|
|
40
43
|
|
|
41
|
-
## Example
|
|
44
|
+
## 📖 Example
|
|
42
45
|
|
|
43
46
|
Command:
|
|
44
47
|
```bash
|
|
45
48
|
npx host-mdx --input-path="path/to/my-website-template" --output-path="path/to/my-website" --port=3113 -t
|
|
46
49
|
```
|
|
47
50
|
|
|
51
|
+
|
|
48
52
|
Input Directory:
|
|
49
53
|
```
|
|
50
54
|
my-website-template/
|
|
@@ -82,7 +86,7 @@ export function onSiteCreateStart(inputPath, outputPath) {
|
|
|
82
86
|
console.log("onSiteCreateStart", inputPath, outputPath)
|
|
83
87
|
}
|
|
84
88
|
export function onSiteCreateEnd(inputPath, outputPath, wasSuccessful){
|
|
85
|
-
console.log("onSiteCreateEnd", inputPath, outputPath)
|
|
89
|
+
console.log("onSiteCreateEnd", inputPath, outputPath, wasSuccessful)
|
|
86
90
|
}
|
|
87
91
|
export function onFileCreateStart(inputFilePath, outputFilePath){
|
|
88
92
|
console.log("onFileCreateStart", inputFilePath, outputFilePath)
|
|
@@ -120,4 +124,7 @@ my-website/
|
|
|
120
124
|
|
|
121
125
|
The site will now be visible in the browser at `localhost:3113`
|
|
122
126
|
|
|
123
|
-
|
|
127
|
+
|
|
128
|
+
## 🔑 License
|
|
129
|
+
|
|
130
|
+
MIT © [Manas Ravindra Makde](https://manasmakde.github.io/)
|
package/index.js
CHANGED
|
@@ -3,18 +3,22 @@
|
|
|
3
3
|
import fs from 'fs'
|
|
4
4
|
import os from 'os'
|
|
5
5
|
import path from 'path'
|
|
6
|
-
import
|
|
6
|
+
import polka from 'polka';
|
|
7
|
+
import sirv from 'sirv';
|
|
7
8
|
import ignore from "ignore";
|
|
8
9
|
import chokidar from 'chokidar';
|
|
9
10
|
import * as readline from 'readline';
|
|
11
|
+
import { pathToFileURL } from 'url';
|
|
10
12
|
import { mdxToHtml } from './mdx-to-html.js'
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
// To-Set Properties
|
|
14
|
-
const APP_NAME = "host-mdx"
|
|
15
|
-
const DEFAULT_PORT = 3000
|
|
16
|
-
const IGNORE_FILE_NAME = ".hostmdxignore"
|
|
17
|
-
const CONFIG_FILE_NAME = "host-mdx.js"
|
|
16
|
+
const APP_NAME = "host-mdx";
|
|
17
|
+
const DEFAULT_PORT = 3000;
|
|
18
|
+
const IGNORE_FILE_NAME = ".hostmdxignore";
|
|
19
|
+
const CONFIG_FILE_NAME = "host-mdx.js";
|
|
20
|
+
const NOT_FOUND_404_FILE = "404.html"
|
|
21
|
+
const NOT_FOUND_404_MESSAGE = "404"
|
|
18
22
|
const DEFAULT_IGNORES = `
|
|
19
23
|
${IGNORE_FILE_NAME}
|
|
20
24
|
${CONFIG_FILE_NAME}
|
|
@@ -22,21 +26,21 @@ node_modules
|
|
|
22
26
|
package-lock.json
|
|
23
27
|
package.json
|
|
24
28
|
.git
|
|
25
|
-
|
|
29
|
+
`;
|
|
26
30
|
|
|
27
31
|
|
|
28
32
|
// 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 = "--
|
|
37
|
-
const VERBOSE_SHORT_FLAG = "-v"
|
|
38
|
-
const TRACK_CHANGES_FLAG = "--track-changes"
|
|
39
|
-
const TRACK_CHANGES_SHORT_FLAG = "-t"
|
|
33
|
+
const CREATE_FLAG = "--create-only";
|
|
34
|
+
const CREATE_SHORT_FLAG = "-c";
|
|
35
|
+
const HELP_FLAG = "--help";
|
|
36
|
+
const HELP_SHORT_FLAG = "-h";
|
|
37
|
+
const INPUT_PATH_FLAG = "--input-path";
|
|
38
|
+
const OUTPUT_PATH_FLAG = "--output-path";
|
|
39
|
+
const PORT_FLAG = "--port";
|
|
40
|
+
const VERBOSE_FLAG = "--verbose";
|
|
41
|
+
const VERBOSE_SHORT_FLAG = "-v";
|
|
42
|
+
const TRACK_CHANGES_FLAG = "--track-changes";
|
|
43
|
+
const TRACK_CHANGES_SHORT_FLAG = "-t";
|
|
40
44
|
|
|
41
45
|
|
|
42
46
|
// Messages & Errors
|
|
@@ -50,15 +54,16 @@ ${OUTPUT_PATH_FLAG}=... The path to which all html files will be generated
|
|
|
50
54
|
${PORT_FLAG}=... Localhost port number on which to host
|
|
51
55
|
${TRACK_CHANGES_FLAG}, ${TRACK_CHANGES_SHORT_FLAG} Tracks any changes made & auto reloads
|
|
52
56
|
${VERBOSE_FLAG}, ${VERBOSE_SHORT_FLAG} Shows additional log messages
|
|
53
|
-
|
|
57
|
+
`;
|
|
54
58
|
|
|
55
59
|
|
|
56
60
|
// Private Properties
|
|
57
|
-
let isCreatingSite = false // Prevents site from being recreated if creation is already ongoing
|
|
58
|
-
let
|
|
59
|
-
let
|
|
60
|
-
let
|
|
61
|
-
|
|
61
|
+
let isCreatingSite = false; // Prevents site from being recreated if creation is already ongoing
|
|
62
|
+
let isCreateSitePending = false // Keeps track if files have been modified and site needs to be recreated
|
|
63
|
+
let isVerbose = false;
|
|
64
|
+
let configs;
|
|
65
|
+
let app;
|
|
66
|
+
const TEMP_HTML_DIR = path.join(os.tmpdir(), `${APP_NAME}`);
|
|
62
67
|
const TIME_OPTIONS = {
|
|
63
68
|
year: 'numeric',
|
|
64
69
|
month: 'long',
|
|
@@ -68,23 +73,7 @@ const TIME_OPTIONS = {
|
|
|
68
73
|
second: 'numeric',
|
|
69
74
|
hour12: false,
|
|
70
75
|
fractionalSecondDigits: 3
|
|
71
|
-
}
|
|
72
|
-
const MIME_TYPE = { // Maps extensions to mime protocol
|
|
73
|
-
'.html': 'text/html',
|
|
74
|
-
'.css': 'text/css',
|
|
75
|
-
'.js': 'text/javascript',
|
|
76
|
-
'.json': 'application/json',
|
|
77
|
-
'.png': 'image/png',
|
|
78
|
-
'.jpg': 'image/jpeg',
|
|
79
|
-
'.ico': 'image/x-icon',
|
|
80
|
-
'.wav': 'audio/wav',
|
|
81
|
-
'.webp': 'image/webp',
|
|
82
|
-
'.mp3': 'audio/mpeg',
|
|
83
|
-
'.mp4': 'video/mp4',
|
|
84
|
-
'.svg': 'image/svg+xml',
|
|
85
|
-
'.pdf': 'application/pdf',
|
|
86
|
-
'.zip': 'application/zip',
|
|
87
|
-
}
|
|
76
|
+
};
|
|
88
77
|
|
|
89
78
|
|
|
90
79
|
// Utility Methods
|
|
@@ -97,18 +86,16 @@ function log(msg, checkVerbose = false) {
|
|
|
97
86
|
console.log(`[${APP_NAME} ${timestamp}] ${msg}`)
|
|
98
87
|
}
|
|
99
88
|
function createTempDir() {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (fs.existsSync(TEMP_HTML_DIR)) {
|
|
103
|
-
fs.rmSync(TEMP_HTML_DIR, { recursive: true, force: true })
|
|
104
|
-
}
|
|
89
|
+
// Create default temp html dir
|
|
90
|
+
fs.mkdirSync(TEMP_HTML_DIR, { recursive: true });
|
|
105
91
|
|
|
106
92
|
|
|
107
|
-
//
|
|
108
|
-
|
|
93
|
+
// Generate time stamp
|
|
94
|
+
const now = new Date()
|
|
95
|
+
const timestamp = `${now.getFullYear()}-${String(now.getMonth() + 1)}-${now.getDate()}T${now.getHours()}_${now.getMinutes()}_${now.getSeconds()}`
|
|
109
96
|
|
|
110
97
|
|
|
111
|
-
return fs.mkdtempSync(path.join(TEMP_HTML_DIR,
|
|
98
|
+
return fs.mkdtempSync(path.join(TEMP_HTML_DIR, `html-${timestamp}-`));
|
|
112
99
|
}
|
|
113
100
|
function getIgnore(ignoreFilePath) {
|
|
114
101
|
const ig = ignore();
|
|
@@ -137,19 +124,22 @@ function createFile(filePath, fileContent = "") {
|
|
|
137
124
|
async function createSite(inputPath, outputPath) {
|
|
138
125
|
// Exit if already creating
|
|
139
126
|
if (isCreatingSite) {
|
|
140
|
-
log("
|
|
127
|
+
log("Site creation already ongoing! Added to pending")
|
|
128
|
+
isCreateSitePending = true
|
|
141
129
|
return
|
|
142
130
|
}
|
|
143
131
|
|
|
144
132
|
|
|
145
133
|
// Set creating status to ongoing
|
|
146
134
|
isCreatingSite = true
|
|
135
|
+
isCreateSitePending = false
|
|
147
136
|
|
|
148
137
|
|
|
149
138
|
// Get config properties
|
|
150
139
|
let configFilePath = path.join(inputPath, `./${CONFIG_FILE_NAME}`)
|
|
151
140
|
if (fs.existsSync(configFilePath)) {
|
|
152
|
-
|
|
141
|
+
|
|
142
|
+
configs = await import(pathToFileURL(configFilePath).href);
|
|
153
143
|
}
|
|
154
144
|
|
|
155
145
|
|
|
@@ -171,20 +161,20 @@ async function createSite(inputPath, outputPath) {
|
|
|
171
161
|
|
|
172
162
|
// Iterate through all folders & files
|
|
173
163
|
const stack = [inputPath];
|
|
174
|
-
while (stack.length > 0) {
|
|
164
|
+
while (stack.length > 0 && !isCreateSitePending) {
|
|
175
165
|
// Continue if path does not exist
|
|
176
166
|
const currentPath = stack.pop()
|
|
177
|
-
if(!fs.existsSync(currentPath)){
|
|
167
|
+
if (!fs.existsSync(currentPath)) {
|
|
178
168
|
continue;
|
|
179
169
|
}
|
|
180
170
|
|
|
181
|
-
|
|
171
|
+
|
|
182
172
|
// Get essentials
|
|
183
173
|
const relToInput = path.relative(inputPath, currentPath)
|
|
184
174
|
const toIgnore = inputPath != currentPath && ig.ignores(relToInput)
|
|
185
175
|
const absToOutput = path.join(outputPath, relToInput)
|
|
186
176
|
const isDir = fs.statSync(currentPath).isDirectory()
|
|
187
|
-
const isMdx = !isDir &&
|
|
177
|
+
const isMdx = !isDir && currentPath.endsWith(".mdx")
|
|
188
178
|
|
|
189
179
|
|
|
190
180
|
// Skip if to ignore this path
|
|
@@ -247,21 +237,87 @@ async function createSite(inputPath, outputPath) {
|
|
|
247
237
|
|
|
248
238
|
|
|
249
239
|
// Broadcast site creation ended
|
|
250
|
-
log(
|
|
251
|
-
configs?.onSiteCreateEnd?.(inputPath, outputPath)
|
|
240
|
+
log(`Created site at ${outputPath}`)
|
|
241
|
+
configs?.onSiteCreateEnd?.(inputPath, outputPath, isCreateSitePending)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
// Reinvoke creation
|
|
245
|
+
if(isCreateSitePending){
|
|
246
|
+
await createSite(inputPath, outputPath);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function filterArgs(rawArgs) {
|
|
250
|
+
// Assign to create
|
|
251
|
+
let toCreateOnly = rawArgs.includes(CREATE_FLAG) || rawArgs.includes(CREATE_SHORT_FLAG)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
// Assign input path
|
|
255
|
+
let inputPath = rawArgs.find(val => val.startsWith(INPUT_PATH_FLAG));
|
|
256
|
+
let inputPathProvided = inputPath !== undefined;
|
|
257
|
+
inputPath = inputPathProvided ? inputPath.split('=')[1] : process.cwd();
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
// Check input path
|
|
261
|
+
if (!fs.existsSync(inputPath) || !fs.lstatSync(inputPath).isDirectory()) {
|
|
262
|
+
log(`Invalid input path "${inputPath}"`)
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
inputPath = inputPath !== "" ? path.resolve(inputPath) : inputPath; // To ensure input path is absolute
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
// Assign output path
|
|
271
|
+
let outputPath = rawArgs.find(val => val.startsWith(OUTPUT_PATH_FLAG));
|
|
272
|
+
let outputPathProvided = outputPath !== undefined;
|
|
273
|
+
outputPath = outputPathProvided ? outputPath.split('=')[1] : createTempDir();
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
// Check output path
|
|
277
|
+
if (!fs.existsSync(outputPath) || !fs.lstatSync(outputPath).isDirectory()) {
|
|
278
|
+
log(`Invalid output path "${outputPath}"`)
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
outputPath = outputPath !== "" ? path.resolve(outputPath) : outputPath; // To ensure output path is absolute
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
// Assign port
|
|
287
|
+
let port = rawArgs.find(val => val.startsWith(PORT_FLAG));
|
|
288
|
+
let portProvided = port !== undefined;
|
|
289
|
+
port = portProvided ? Number(port.split('=')[1]) : DEFAULT_PORT;
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
// Check port
|
|
293
|
+
if (!Number.isInteger(port)) {
|
|
294
|
+
log(`Invalid port`)
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
// Assign tracking changes
|
|
300
|
+
let toTrackChanges = rawArgs.includes(TRACK_CHANGES_FLAG) || rawArgs.includes(TRACK_CHANGES_SHORT_FLAG);
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
return { toCreateOnly, inputPath, inputPathProvided, outputPath, outputPathProvided, toTrackChanges, port, portProvided, };
|
|
252
304
|
}
|
|
253
305
|
|
|
254
306
|
|
|
255
307
|
// Main Methods
|
|
256
308
|
async function createSiteSafe(...args) {
|
|
309
|
+
|
|
310
|
+
let success = true;
|
|
257
311
|
try {
|
|
258
312
|
await createSite(...args);
|
|
259
313
|
}
|
|
260
314
|
catch (err) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
log(
|
|
315
|
+
success = false;
|
|
316
|
+
isCreatingSite = false;
|
|
317
|
+
log(`Failed to create site!\n${err}`);
|
|
264
318
|
}
|
|
319
|
+
|
|
320
|
+
return success;
|
|
265
321
|
}
|
|
266
322
|
async function listenForKey(createSiteCallback) {
|
|
267
323
|
|
|
@@ -276,144 +332,103 @@ async function listenForKey(createSiteCallback) {
|
|
|
276
332
|
createSiteCallback();
|
|
277
333
|
}
|
|
278
334
|
else if (key && key.sequence == '\x03') {
|
|
279
|
-
server.close((e) => { process.exit() })
|
|
335
|
+
app.server.close((e) => { process.exit() })
|
|
280
336
|
}
|
|
281
337
|
});
|
|
282
338
|
}
|
|
283
339
|
function startServer(htmlDir, port) { // Starts server at given port
|
|
284
|
-
|
|
340
|
+
|
|
285
341
|
// Broadcast server starting
|
|
286
342
|
configs?.onHostStart?.(port)
|
|
287
343
|
|
|
288
344
|
|
|
289
345
|
// Start Server
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
res.end(`Error getting the file: ${err}.`)
|
|
307
|
-
}
|
|
308
|
-
else {
|
|
309
|
-
// Based on the URL path, extract the file extention. e.g. .js, .doc, ...
|
|
310
|
-
const ext = path.parse(absoluteFilePath).ext
|
|
311
|
-
res.setHeader('Content-type', MIME_TYPE[ext] || 'text/plain') // if the file is found, set Content-type and send data
|
|
312
|
-
res.end(data)
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
})
|
|
316
|
-
else { // Respondes with 404 if file not found
|
|
317
|
-
res.statusCode = 404
|
|
318
|
-
res.end(`404 Invalid url not found!`)
|
|
346
|
+
const assets = sirv(htmlDir, { dev: true });
|
|
347
|
+
const newApp = polka({
|
|
348
|
+
onNoMatch: (req, res) => {
|
|
349
|
+
|
|
350
|
+
// Set status code to 404
|
|
351
|
+
res.statusCode = 404;
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
// Send 404 file if found otherwise default not found message
|
|
355
|
+
const errorFile = path.join(htmlDir, NOT_FOUND_404_FILE);
|
|
356
|
+
if (fs.existsSync(errorFile)) {
|
|
357
|
+
res.setHeader('Content-Type', 'text/html');
|
|
358
|
+
res.end(fs.readFileSync(errorFile));
|
|
359
|
+
} else {
|
|
360
|
+
res.end(NOT_FOUND_404_MESSAGE);
|
|
361
|
+
}
|
|
319
362
|
}
|
|
320
|
-
})
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
363
|
+
}).use(assets)
|
|
364
|
+
|
|
365
|
+
newApp.listen(port)
|
|
366
|
+
newApp.server.on("close", () => { configs?.onHostEnd?.(port) });
|
|
367
|
+
newApp.server.on("error", (e) => { log(`Failed to start server: ${e.message}`); throw e; });
|
|
368
|
+
log(`Server listening at ${port} ... (Press 'r' to manually reload, Press 'Ctrl+c' to exit)`)
|
|
324
369
|
|
|
325
370
|
|
|
326
|
-
return
|
|
371
|
+
return newApp
|
|
327
372
|
}
|
|
328
373
|
async function Main() {
|
|
329
374
|
// Get all arguments
|
|
330
|
-
const
|
|
375
|
+
const rawArgs = process.argv.slice(2);
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
// Check if verbose
|
|
379
|
+
isVerbose = rawArgs.includes(VERBOSE_FLAG) || rawArgs.includes(VERBOSE_SHORT_FLAG);
|
|
331
380
|
|
|
332
381
|
|
|
333
382
|
// Check if asked for help
|
|
334
|
-
if (
|
|
383
|
+
if (rawArgs.includes(HELP_FLAG) || rawArgs.includes(HELP_SHORT_FLAG)) {
|
|
335
384
|
console.log(HELP_MESSAGE)
|
|
336
385
|
return;
|
|
337
386
|
}
|
|
338
387
|
|
|
339
388
|
|
|
340
|
-
//
|
|
341
|
-
let
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
let inputPath = args.find(val => val.startsWith(INPUT_PATH_FLAG))
|
|
345
|
-
inputPath = inputPath !== undefined ? inputPath.split('=')[1] : process.cwd()
|
|
346
|
-
|
|
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()
|
|
351
|
-
|
|
352
|
-
// Assign tracking changes
|
|
353
|
-
let toTrackChanges = args.includes(TRACK_CHANGES_FLAG) || args.includes(TRACK_CHANGES_SHORT_FLAG)
|
|
354
|
-
|
|
355
|
-
// Assign port
|
|
356
|
-
let port = args.find(val => val.startsWith(PORT_FLAG))
|
|
357
|
-
port = port !== undefined ? Number(port.split('=')[1]) : DEFAULT_PORT
|
|
358
|
-
|
|
359
|
-
// Assign verbose
|
|
360
|
-
isVerbose = args.includes(VERBOSE_FLAG) || args.includes(VERBOSE_SHORT_FLAG)
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
// Check input path
|
|
364
|
-
if (!fs.existsSync(inputPath) || !fs.lstatSync(inputPath).isDirectory()) {
|
|
365
|
-
log(`Invalid input path "${inputPath}"`)
|
|
366
|
-
return
|
|
367
|
-
}
|
|
368
|
-
|
|
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
|
|
389
|
+
// Filter arguments
|
|
390
|
+
let args = filterArgs(rawArgs);
|
|
391
|
+
if (args === null) {
|
|
392
|
+
return;
|
|
379
393
|
}
|
|
380
394
|
|
|
381
395
|
|
|
382
396
|
// Create site from mdx & return if only needed to create site
|
|
383
|
-
await createSiteSafe(inputPath, outputPath)
|
|
384
|
-
if (toCreateOnly) {
|
|
397
|
+
let wasCreated = await createSiteSafe(args.inputPath, args.outputPath);
|
|
398
|
+
if (args.toCreateOnly) {
|
|
399
|
+
process.exitCode = !wasCreated ? 1 : 0; // Exit with error code if not created successfully
|
|
385
400
|
return;
|
|
386
401
|
}
|
|
387
402
|
|
|
388
403
|
|
|
389
404
|
// Watch for key presses
|
|
390
|
-
listenForKey(() => createSiteSafe(inputPath, outputPath))
|
|
405
|
+
listenForKey(() => createSiteSafe(args.inputPath, args.outputPath));
|
|
391
406
|
|
|
392
407
|
|
|
393
408
|
// Watch for changes
|
|
394
|
-
if (toTrackChanges) {
|
|
395
|
-
chokidar.watch(inputPath, {
|
|
396
|
-
ignoreInitial: true
|
|
397
|
-
ignored: (path, stats) => isCreatingSite // Ignore if site creation is ongoing
|
|
409
|
+
if (args.toTrackChanges) {
|
|
410
|
+
chokidar.watch(args.inputPath, {
|
|
411
|
+
ignoreInitial: true
|
|
398
412
|
}).on('all', (event, path) => {
|
|
399
|
-
createSiteSafe(inputPath, outputPath)
|
|
413
|
+
createSiteSafe(args.inputPath, args.outputPath)
|
|
400
414
|
});
|
|
401
415
|
}
|
|
402
416
|
|
|
403
417
|
|
|
404
418
|
// Start server
|
|
405
|
-
|
|
419
|
+
app = startServer(args.outputPath, args.port);
|
|
406
420
|
|
|
407
421
|
|
|
408
422
|
// Handle quit
|
|
409
|
-
|
|
423
|
+
const cleanup = () => {
|
|
410
424
|
// Remove html path
|
|
411
|
-
if (!outputPathProvided && fs.existsSync(outputPath)) {
|
|
412
|
-
fs.rmSync(outputPath, { recursive: true, force: true })
|
|
425
|
+
if (!args.outputPathProvided && fs.existsSync(args.outputPath)) {
|
|
426
|
+
fs.rmSync(args.outputPath, { recursive: true, force: true })
|
|
413
427
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
428
|
+
}
|
|
429
|
+
process.on("exit", cleanup);
|
|
430
|
+
process.on("SIGINT", cleanup);
|
|
431
|
+
process.on("SIGTERM", cleanup);
|
|
417
432
|
}
|
|
418
433
|
|
|
419
434
|
Main()
|
package/mdx-to-html.js
CHANGED
|
@@ -1,19 +1,43 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import * as _jsx_runtime from '
|
|
1
|
+
import * as Preact from "preact";
|
|
2
|
+
import * as PreactDOM from "preact/compat";
|
|
3
|
+
import * as _jsx_runtime from 'preact/jsx-runtime';
|
|
4
|
+
import { renderToString } from 'preact-render-to-string';
|
|
4
5
|
import { common } from 'lowlight';
|
|
5
6
|
import { bundleMDX } from 'mdx-bundler';
|
|
6
|
-
import { getMDXComponent } from 'mdx-bundler/client/index.js'
|
|
7
|
-
import { renderToString } from 'react-dom/server';
|
|
8
7
|
import { createRequire } from 'module';
|
|
8
|
+
import rehypeHighlight from "rehype-highlight";
|
|
9
9
|
|
|
10
|
+
|
|
11
|
+
// Constants
|
|
10
12
|
const nativeRequire = createRequire(import.meta.url);
|
|
13
|
+
const jsxBundlerConfig = {
|
|
14
|
+
jsxLib: {
|
|
15
|
+
varName: 'Preact',
|
|
16
|
+
package: 'preact',
|
|
17
|
+
},
|
|
18
|
+
jsxDom: {
|
|
19
|
+
varName: 'PreactDom',
|
|
20
|
+
package: 'preact/compat',
|
|
21
|
+
},
|
|
22
|
+
jsxRuntime: {
|
|
23
|
+
varName: '_jsx_runtime',
|
|
24
|
+
package: 'preact/jsx-runtime',
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
|
|
11
28
|
|
|
29
|
+
// Methods
|
|
30
|
+
function getMDXComponent(code, globals) {
|
|
31
|
+
const fn = new Function(...Object.keys(globals), code);
|
|
32
|
+
const mdxExport = fn(...Object.values(globals));
|
|
33
|
+
return mdxExport.default;
|
|
34
|
+
}
|
|
12
35
|
export async function mdxToHtml(mdxCode, baseUrl, modSettingsCallback = undefined) {
|
|
13
|
-
|
|
36
|
+
|
|
14
37
|
// Assign default settings
|
|
15
38
|
let settings = {
|
|
16
39
|
source: mdxCode,
|
|
40
|
+
jsxConfig: jsxBundlerConfig,
|
|
17
41
|
cwd: baseUrl,
|
|
18
42
|
esbuildOptions: (options) => {
|
|
19
43
|
options.platform = 'node'
|
|
@@ -30,13 +54,15 @@ export async function mdxToHtml(mdxCode, baseUrl, modSettingsCallback = undefine
|
|
|
30
54
|
|
|
31
55
|
|
|
32
56
|
// Modify settings
|
|
33
|
-
if(modSettingsCallback !== undefined){
|
|
57
|
+
if (modSettingsCallback !== undefined) {
|
|
34
58
|
settings = modSettingsCallback(settings)
|
|
35
59
|
}
|
|
36
60
|
|
|
37
61
|
|
|
38
62
|
// Generate html
|
|
39
63
|
const { code } = await bundleMDX(settings);
|
|
40
|
-
const Component = getMDXComponent(code, { require: nativeRequire, cwd: baseUrl })
|
|
41
|
-
|
|
64
|
+
const Component = getMDXComponent(code, { Preact, PreactDOM, _jsx_runtime, require: nativeRequire, cwd: baseUrl })
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
return renderToString(Preact.h(Component, {}));
|
|
42
68
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "host-mdx",
|
|
3
|
-
"version": "2.0
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "A cli tool to create and serve a static html website from a given mdx directory",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
@@ -15,9 +15,11 @@
|
|
|
15
15
|
"ignore": "^7.0.5",
|
|
16
16
|
"lowlight": "^3.3.0",
|
|
17
17
|
"mdx-bundler": "^10.1.1",
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
18
|
+
"polka": "^0.5.2",
|
|
19
|
+
"preact": "^10.28.2",
|
|
20
|
+
"preact-render-to-string": "^6.6.5",
|
|
21
|
+
"rehype-highlight": "^7.0.2",
|
|
22
|
+
"sirv": "^3.0.2"
|
|
21
23
|
},
|
|
22
24
|
"keywords": [
|
|
23
25
|
"mdx"
|