host-mdx 2.4.0 → 2.4.2
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 +17 -9
- package/cli.js +16 -6
- package/dependency-graph.js +54 -48
- package/index.js +136 -51
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -53,10 +53,11 @@ hostMdx.start();
|
|
|
53
53
|
You can add `.hostmdxignore` at the root of your project to filter out which files/folders to skip while generating html
|
|
54
54
|
(similar to [.gitignore](https://git-scm.com/docs/gitignore))
|
|
55
55
|
|
|
56
|
+
> You can add `# [EXCLUDE]` comment on top of every path for behaviour similar to returning null in `toIgnore`
|
|
57
|
+
|
|
56
58
|
You can also add `host-mdx.js` at the root of your project as an optional config file for more complex behaviour (Look at the example below for all available options)
|
|
57
59
|
|
|
58
60
|
|
|
59
|
-
> **Note:**
|
|
60
61
|
> 1. Any config properties passed from npx or import e.g. `port`, `toBeVerbose`, `trackChanges`, etc will override `host-mdx.js` export values
|
|
61
62
|
> 1. Any changes made to `host-mdx.js` or any new packages added require complete restart otherwise changes will not reflect due to [this bug](https://github.com/nodejs/node/issues/49442)
|
|
62
63
|
|
|
@@ -107,9 +108,11 @@ my-website-template/
|
|
|
107
108
|
|
|
108
109
|
```sh
|
|
109
110
|
*.jsx
|
|
110
|
-
|
|
111
|
+
# [EXCLUDE]
|
|
111
112
|
static/temp.jpg
|
|
112
113
|
!static/sample.jsx
|
|
114
|
+
# [EXCLUDE]
|
|
115
|
+
blog/page2/
|
|
113
116
|
```
|
|
114
117
|
|
|
115
118
|
`host-mdx.js` file content:
|
|
@@ -140,11 +143,21 @@ export async function onFileChangeEnd(inputPath, outputPath, inFilePath, outFile
|
|
|
140
143
|
console.log("onFileChangeEnd");
|
|
141
144
|
}
|
|
142
145
|
export async function toIgnore(inputPath, outputPath, targetPath) {
|
|
146
|
+
|
|
147
|
+
// Return true = file not generated in output folder,
|
|
148
|
+
// Return null = same as true but also prevents triggering reload
|
|
149
|
+
|
|
143
150
|
const isGOutputStream = /\.goutputstream-\w+$/.test(targetPath);
|
|
144
151
|
if (isGOutputStream) {
|
|
145
|
-
return
|
|
152
|
+
return null;
|
|
146
153
|
}
|
|
147
|
-
|
|
154
|
+
|
|
155
|
+
const ignoredDirs = new Set(['node_modules', '.git', '.github']);
|
|
156
|
+
const segments = targetPath.split(path.sep);
|
|
157
|
+
if (segments.some(segment => ignoredDirs.has(segment))) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
|
|
148
161
|
return false;
|
|
149
162
|
}
|
|
150
163
|
export async function modMDXCode(inputPath, outputPath, inFilePath, outFilePath, code){
|
|
@@ -177,11 +190,6 @@ export async function modRebuildPaths(inputPath, outputPath, rebuildPaths) {
|
|
|
177
190
|
// Modify rebuildPaths ...
|
|
178
191
|
return rebuildPaths;
|
|
179
192
|
}
|
|
180
|
-
export async function canTriggerReload(inputPath, outputPath, targetPath) {
|
|
181
|
-
const ignoredDirs = new Set(['node_modules', '.git', '.github']);
|
|
182
|
-
const segments = targetPath.split(/[\\/]/); // or targetPath.split(path.sep);
|
|
183
|
-
return !segments.some(segment => ignoredDirs.has(segment));
|
|
184
|
-
}
|
|
185
193
|
export const chokidarOptions = {
|
|
186
194
|
awaitWriteFinish: true
|
|
187
195
|
}
|
package/cli.js
CHANGED
|
@@ -56,8 +56,14 @@ function getPortFromArgs(rawArgs) {
|
|
|
56
56
|
return portProvided ? Number(port.split('=')[1]) : undefined;
|
|
57
57
|
}
|
|
58
58
|
function getTrackChangesFromArgs(rawArgs) {
|
|
59
|
+
|
|
59
60
|
// If flag not passed do not track changes
|
|
60
|
-
let trackChanges = rawArgs.find(val =>
|
|
61
|
+
let trackChanges = rawArgs.find(val =>
|
|
62
|
+
val === TRACK_CHANGES_SHORT_FLAG ||
|
|
63
|
+
val.startsWith(`${TRACK_CHANGES_SHORT_FLAG}=`) ||
|
|
64
|
+
val === TRACK_CHANGES_FLAG ||
|
|
65
|
+
val.startsWith(`${TRACK_CHANGES_FLAG}=`)
|
|
66
|
+
);
|
|
61
67
|
if (trackChanges == undefined) {
|
|
62
68
|
return undefined;
|
|
63
69
|
}
|
|
@@ -79,7 +85,8 @@ function getTrackChangesFromArgs(rawArgs) {
|
|
|
79
85
|
function getConcurrencyFromArgs(rawArgs) {
|
|
80
86
|
let concurrency = rawArgs.find(val => val.startsWith(CONCURRENCY_FLAG));
|
|
81
87
|
let concurrencyProvided = concurrency !== undefined;
|
|
82
|
-
|
|
88
|
+
const val = concurrencyProvided ? Number(concurrency.split('=')[1]) : undefined;
|
|
89
|
+
return Number.isInteger(val) && val >= 1 ? val : undefined;
|
|
83
90
|
}
|
|
84
91
|
function listenForKey(reloadCallback, hardReloadCallback, exitCallback) {
|
|
85
92
|
readline.emitKeypressEvents(process.stdin);
|
|
@@ -176,19 +183,22 @@ export async function main() {
|
|
|
176
183
|
|
|
177
184
|
// Watch for key press
|
|
178
185
|
listenForKey(
|
|
179
|
-
async () => await hostMdx?.recreateSite(),
|
|
180
186
|
async () => {
|
|
181
|
-
log("---
|
|
182
|
-
await hostMdx?.recreateSite(
|
|
187
|
+
log("--- RELOAD TRIGGERED ---");
|
|
188
|
+
await hostMdx?.recreateSite();
|
|
189
|
+
},
|
|
190
|
+
async () => {
|
|
191
|
+
log("--- HARD RELOAD TRIGGERED ---");
|
|
192
|
+
await hostMdx?.recreateSite(true);
|
|
183
193
|
},
|
|
184
194
|
cleanup
|
|
185
195
|
);
|
|
186
196
|
|
|
187
197
|
|
|
188
198
|
// Watch for quit
|
|
189
|
-
process.on("exit", cleanup);
|
|
190
199
|
process.on("SIGINT", cleanup);
|
|
191
200
|
process.on("SIGTERM", cleanup);
|
|
201
|
+
process.on("exit", () => { process.stdin.setRawMode(false) });
|
|
192
202
|
|
|
193
203
|
|
|
194
204
|
// Log key press instructions
|
package/dependency-graph.js
CHANGED
|
@@ -20,10 +20,10 @@ export async function crawlDir(dir, ignoreCheck = async (p) => false) {
|
|
|
20
20
|
// Iterate through all files in dir
|
|
21
21
|
let results = [];
|
|
22
22
|
const list = fs.readdirSync(dir);
|
|
23
|
-
for (let
|
|
23
|
+
for (let targetPath of list) {
|
|
24
24
|
|
|
25
25
|
// get absolute path
|
|
26
|
-
const absPath = path.join(dir,
|
|
26
|
+
const absPath = path.join(dir, targetPath);
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
// Skip if to ignore
|
|
@@ -46,21 +46,21 @@ export async function crawlDir(dir, ignoreCheck = async (p) => false) {
|
|
|
46
46
|
|
|
47
47
|
return results;
|
|
48
48
|
}
|
|
49
|
-
export function resolveAlias(
|
|
49
|
+
export function resolveAlias(targetPath, aliases) {
|
|
50
50
|
for (const [alias, aliasPath] of Object.entries(aliases)) {
|
|
51
51
|
|
|
52
52
|
// Check if import is the alias or starts with alias + system separator
|
|
53
|
-
const isExact =
|
|
54
|
-
const isSubPath =
|
|
53
|
+
const isExact = targetPath === alias;
|
|
54
|
+
const isSubPath = targetPath.startsWith(`${alias}${path.sep}`) || targetPath.startsWith(`${alias}/`);
|
|
55
55
|
|
|
56
56
|
if (isExact || isSubPath) {
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
targetPath = targetPath.replace(alias, aliasPath);
|
|
58
|
+
targetPath = path.normalize(targetPath);
|
|
59
59
|
break;
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
return
|
|
63
|
+
return targetPath;
|
|
64
64
|
}
|
|
65
65
|
export function ensureRelativePath(rootPath, filePath) {
|
|
66
66
|
const absoluteTarget = path.resolve(rootPath, filePath);
|
|
@@ -69,9 +69,9 @@ export function ensureRelativePath(rootPath, filePath) {
|
|
|
69
69
|
}
|
|
70
70
|
export async function calcDependencies(filePath, aliases = {}) {
|
|
71
71
|
|
|
72
|
-
// Return if given path
|
|
72
|
+
// Return if given path is in node_modules
|
|
73
73
|
const absolutePath = path.resolve(filePath);
|
|
74
|
-
if (absolutePath.includes('node_modules')) {
|
|
74
|
+
if (absolutePath.split(path.sep).includes('node_modules')) {
|
|
75
75
|
return new Set();
|
|
76
76
|
}
|
|
77
77
|
|
|
@@ -115,7 +115,7 @@ export async function calcDependencies(filePath, aliases = {}) {
|
|
|
115
115
|
|
|
116
116
|
|
|
117
117
|
// Skip if the resolved path is within node_modules
|
|
118
|
-
if (resolvedPath.includes('node_modules')) {
|
|
118
|
+
if (resolvedPath.split(path.sep).includes('node_modules')) {
|
|
119
119
|
continue;
|
|
120
120
|
}
|
|
121
121
|
|
|
@@ -129,7 +129,7 @@ export async function calcDependencies(filePath, aliases = {}) {
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
|
|
132
|
-
//
|
|
132
|
+
// Classes
|
|
133
133
|
export class DependencyGraph {
|
|
134
134
|
|
|
135
135
|
// Private Properties
|
|
@@ -164,36 +164,49 @@ export class DependencyGraph {
|
|
|
164
164
|
let relFilePath = ensureRelativePath(this.#rootFolder, filePath);
|
|
165
165
|
|
|
166
166
|
|
|
167
|
+
// If did not exist previously create fresh
|
|
168
|
+
if (this.#graph?.[relFilePath] === undefined) {
|
|
169
|
+
this.#graph[relFilePath] = {
|
|
170
|
+
[DEPENDENCIES_KEY]: new Set(),
|
|
171
|
+
[DEPENDENTS_KEY]: new Set()
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
// Remove previous dependencies
|
|
177
|
+
const oldDeps = this.#graph[relFilePath][DEPENDENCIES_KEY];
|
|
178
|
+
oldDeps.forEach(dep => {
|
|
179
|
+
this.#graph?.[dep]?.[DEPENDENTS_KEY]?.delete(relFilePath);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
// Intentionally not removing dependents since no way of knowing which files depend on `relFilePath` DO NOT CHANGE
|
|
184
|
+
|
|
185
|
+
|
|
167
186
|
// Get all dependencies
|
|
168
|
-
const absFilePath = path.resolve(this.#rootFolder, relFilePath);
|
|
169
|
-
const dependencies = await calcDependencies(absFilePath, this.#aliases);
|
|
170
187
|
const relDependencies = new Set();
|
|
188
|
+
const absFilePath = path.resolve(this.#rootFolder, relFilePath);
|
|
189
|
+
let dependencies;
|
|
190
|
+
try {
|
|
191
|
+
dependencies = await calcDependencies(absFilePath, this.#aliases);
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
dependencies = new Set();
|
|
195
|
+
}
|
|
171
196
|
dependencies.forEach(p => {
|
|
172
197
|
relDependencies.add(path.relative(this.#rootFolder, p));
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
// Skip if no dependencies
|
|
177
|
-
if (dependencies.size === 0) {
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
198
|
+
});
|
|
180
199
|
|
|
181
200
|
|
|
182
201
|
// Add dependencies
|
|
183
|
-
this.#graph[relFilePath] =
|
|
184
|
-
[DEPENDENCIES_KEY]: relDependencies,
|
|
185
|
-
[DEPENDENTS_KEY]: new Set()
|
|
186
|
-
};
|
|
202
|
+
this.#graph[relFilePath][DEPENDENCIES_KEY] = relDependencies;
|
|
187
203
|
|
|
188
204
|
|
|
189
|
-
// Add dependents
|
|
205
|
+
// Add to dependents
|
|
190
206
|
const depList = this.#graph[relFilePath][DEPENDENCIES_KEY];
|
|
191
207
|
depList.forEach(dep => {
|
|
192
|
-
if (this.#graph[dep] === undefined) {
|
|
193
|
-
this.#graph[dep] = {
|
|
194
|
-
[DEPENDENCIES_KEY]: new Set(),
|
|
195
|
-
[DEPENDENTS_KEY]: new Set()
|
|
196
|
-
}
|
|
208
|
+
if (this.#graph?.[dep] === undefined) {
|
|
209
|
+
this.#graph[dep] = { [DEPENDENCIES_KEY]: new Set(), [DEPENDENTS_KEY]: new Set() };
|
|
197
210
|
}
|
|
198
211
|
|
|
199
212
|
this.#graph[dep][DEPENDENTS_KEY].add(relFilePath);
|
|
@@ -214,11 +227,14 @@ export class DependencyGraph {
|
|
|
214
227
|
// Remove from dependents
|
|
215
228
|
const depList = this.#graph[relFilePath][DEPENDENCIES_KEY];
|
|
216
229
|
depList.forEach(dep => {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
230
|
+
this.#graph?.[dep]?.[DEPENDENTS_KEY]?.delete(relFilePath);
|
|
231
|
+
});
|
|
232
|
+
|
|
220
233
|
|
|
221
|
-
|
|
234
|
+
// Remove from dependencies
|
|
235
|
+
const depOf = this.#graph[relFilePath][DEPENDENTS_KEY];
|
|
236
|
+
depOf.forEach(dependent => {
|
|
237
|
+
this.#graph[dependent]?.[DEPENDENCIES_KEY]?.delete(relFilePath);
|
|
222
238
|
});
|
|
223
239
|
|
|
224
240
|
|
|
@@ -259,13 +275,8 @@ export class DependencyGraph {
|
|
|
259
275
|
const deepDeps = new Set();
|
|
260
276
|
const walk = (currentPath) => {
|
|
261
277
|
|
|
262
|
-
// Skip if not in graph
|
|
263
|
-
const deps = this.getDependencies(currentPath);
|
|
264
|
-
if (!deps) {
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
278
|
// Iterate over all dependencies
|
|
279
|
+
const deps = this.getDependencies(currentPath);
|
|
269
280
|
deps.forEach(dep => {
|
|
270
281
|
if (deepDeps.has(dep)) {
|
|
271
282
|
return;
|
|
@@ -306,13 +317,8 @@ export class DependencyGraph {
|
|
|
306
317
|
const deepDependents = new Set();
|
|
307
318
|
const walk = (currentPath) => {
|
|
308
319
|
|
|
309
|
-
//
|
|
320
|
+
// Iterate over all dependents
|
|
310
321
|
const dependents = this.getDependents(currentPath);
|
|
311
|
-
if (!dependents) {
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// Iterate over all dependencies
|
|
316
322
|
dependents.forEach(dependent => {
|
|
317
323
|
if (deepDependents.has(dependent)) {
|
|
318
324
|
return;
|
|
@@ -324,7 +330,7 @@ export class DependencyGraph {
|
|
|
324
330
|
});
|
|
325
331
|
};
|
|
326
332
|
walk(relFilePath);
|
|
327
|
-
|
|
333
|
+
|
|
328
334
|
|
|
329
335
|
return deepDependents;
|
|
330
336
|
}
|
package/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import os from "os";
|
|
3
3
|
import net from "net";
|
|
4
|
-
import _ from 'lodash';
|
|
5
4
|
import path from "path";
|
|
6
5
|
import sirv from "sirv";
|
|
7
6
|
import polka from "polka";
|
|
@@ -35,15 +34,24 @@ const FILE_404 = "404.html";
|
|
|
35
34
|
const NOT_FOUND_404_MESSAGE = "404";
|
|
36
35
|
const DEFAULT_PORT = 3000;
|
|
37
36
|
const MAX_PORT = 4000;
|
|
37
|
+
const EXCLUDE_HEADER = "# [EXCLUDE]"; // Case insensitive
|
|
38
38
|
const TEMP_HTML_DIR = path.join(os.tmpdir(), `${APP_NAME}`);
|
|
39
39
|
const DEFAULT_IGNORES = `
|
|
40
|
+
${EXCLUDE_HEADER}
|
|
40
41
|
${IGNORE_FILE_NAME}
|
|
42
|
+
${EXCLUDE_HEADER}
|
|
41
43
|
${CONFIG_FILE_NAME}
|
|
44
|
+
${EXCLUDE_HEADER}
|
|
42
45
|
node_modules
|
|
46
|
+
${EXCLUDE_HEADER}
|
|
43
47
|
package-lock.json
|
|
48
|
+
${EXCLUDE_HEADER}
|
|
44
49
|
package.json
|
|
50
|
+
${EXCLUDE_HEADER}
|
|
45
51
|
.git
|
|
52
|
+
${EXCLUDE_HEADER}
|
|
46
53
|
.github
|
|
54
|
+
${EXCLUDE_HEADER}
|
|
47
55
|
.gitignore
|
|
48
56
|
`;
|
|
49
57
|
|
|
@@ -64,15 +72,23 @@ const DEFAULT_CHOKIDAR_OPTIONS = {
|
|
|
64
72
|
};
|
|
65
73
|
const DEFAULT_CONFIGS = {
|
|
66
74
|
// port: 3000, // Intentionally kept commented out, otherwise interferes with auto port assigning DO NOT CHANGE
|
|
67
|
-
chokidarOptions: DEFAULT_CHOKIDAR_OPTIONS,
|
|
68
75
|
trackChanges: 0,
|
|
69
76
|
toBeVerbose: false,
|
|
70
77
|
concurrency: 1,
|
|
71
78
|
chokidarOptions: DEFAULT_CHOKIDAR_OPTIONS,
|
|
72
|
-
|
|
79
|
+
toIgnore: (inputPath, outputPath, targetPath) => {
|
|
80
|
+
const isGOutputStream = /\.goutputstream-\w+$/.test(targetPath);
|
|
81
|
+
if (isGOutputStream) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
73
85
|
const ignoredDirs = new Set(['node_modules', '.git', '.github']);
|
|
74
|
-
const segments =
|
|
75
|
-
|
|
86
|
+
const segments = targetPath.split(path.sep);
|
|
87
|
+
if (segments.some(segment => ignoredDirs.has(segment))) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return false;
|
|
76
92
|
}
|
|
77
93
|
};
|
|
78
94
|
|
|
@@ -84,9 +100,48 @@ function getIgnore(ignoreFilePath) {
|
|
|
84
100
|
if (fs.existsSync(ignoreFilePath)) {
|
|
85
101
|
ignoreContent += `\n${fs.readFileSync(ignoreFilePath, "utf8")}`;
|
|
86
102
|
}
|
|
87
|
-
|
|
88
103
|
ig.add(ignoreContent);
|
|
104
|
+
return ig;
|
|
105
|
+
}
|
|
106
|
+
function getExclude(ignoreFilePath) {
|
|
107
|
+
|
|
108
|
+
// Read .ignore file
|
|
109
|
+
const ig = ignore();
|
|
110
|
+
let rawContent = DEFAULT_IGNORES;
|
|
111
|
+
if (fs.existsSync(ignoreFilePath)) {
|
|
112
|
+
rawContent += "\n" + fs.readFileSync(ignoreFilePath, "utf8");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
// Only get lines which have "# [EXCLUDE]" comment on top
|
|
117
|
+
let filteredLines = [];
|
|
118
|
+
let hasExclude = false;
|
|
119
|
+
const lines = rawContent.split(/\r?\n/);
|
|
120
|
+
const excludeComment = EXCLUDE_HEADER.toLowerCase();
|
|
121
|
+
for (const line of lines) {
|
|
122
|
+
const trimmed = line.trim();
|
|
123
|
+
|
|
124
|
+
// Check for the header tag
|
|
125
|
+
if (trimmed.toLowerCase() === excludeComment) {
|
|
126
|
+
hasExclude = true;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Reset if empty line found
|
|
131
|
+
if (trimmed === "") {
|
|
132
|
+
hasExclude = false;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Add line if has exclude otherwise continue
|
|
137
|
+
if (hasExclude) {
|
|
138
|
+
filteredLines.push(trimmed);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
89
142
|
|
|
143
|
+
// Add to ignore
|
|
144
|
+
ig.add(filteredLines.join("\n"));
|
|
90
145
|
return ig;
|
|
91
146
|
}
|
|
92
147
|
async function createFile(filePath, fileContent = "") {
|
|
@@ -103,10 +158,10 @@ async function startServer(hostDir, port, errorCallback) { // Starts server at
|
|
|
103
158
|
// Start Server
|
|
104
159
|
const assets = sirv(hostDir, { dev: true });
|
|
105
160
|
const newApp = polka({
|
|
106
|
-
onNoMatch: (req, res) => { // Send 404 file if found else not found message
|
|
107
|
-
const
|
|
108
|
-
if (fs.existsSync(
|
|
109
|
-
const content =
|
|
161
|
+
onNoMatch: async (req, res) => { // Send 404 file if found else not found message
|
|
162
|
+
const file404 = path.join(hostDir, FILE_404);
|
|
163
|
+
if (fs.existsSync(file404)) {
|
|
164
|
+
const content = await fsp.readFile(file404);
|
|
110
165
|
res.writeHead(404, {
|
|
111
166
|
'Content-Type': 'text/html',
|
|
112
167
|
'Content-Length': content.length
|
|
@@ -150,6 +205,18 @@ async function isPortAvailable(port) {
|
|
|
150
205
|
server.listen(port);
|
|
151
206
|
});
|
|
152
207
|
}
|
|
208
|
+
async function getAvailablePort(startPort = DEFAULT_PORT, maxPort = MAX_PORT) {
|
|
209
|
+
let currentPort = startPort;
|
|
210
|
+
while (currentPort <= maxPort) {
|
|
211
|
+
if (await isPortAvailable(currentPort)) {
|
|
212
|
+
return currentPort;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
currentPort++;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return -1;
|
|
219
|
+
}
|
|
153
220
|
export function log(msg, toSkip = false) {
|
|
154
221
|
if (toSkip) { // Useful for verbose check
|
|
155
222
|
return
|
|
@@ -180,14 +247,14 @@ export function isPathInside(parentPath, childPath) {
|
|
|
180
247
|
);
|
|
181
248
|
}
|
|
182
249
|
export async function setupConfigs(inputPath) {
|
|
183
|
-
|
|
184
250
|
let configFilePath = path.join(inputPath, CONFIG_FILE_NAME);
|
|
251
|
+
let configs = { ...DEFAULT_CONFIGS };
|
|
185
252
|
if (fs.existsSync(configFilePath)) {
|
|
186
|
-
let cleanConfigFilePath = pathToFileURL(configFilePath).href
|
|
187
|
-
|
|
253
|
+
let cleanConfigFilePath = pathToFileURL(configFilePath).href;
|
|
254
|
+
configs = { ...configs, ...(await import(cleanConfigFilePath)) };
|
|
188
255
|
}
|
|
189
256
|
|
|
190
|
-
return
|
|
257
|
+
return configs;
|
|
191
258
|
}
|
|
192
259
|
export function createTempDir() {
|
|
193
260
|
// Create default temp html dir
|
|
@@ -201,25 +268,13 @@ export function createTempDir() {
|
|
|
201
268
|
|
|
202
269
|
return fs.mkdtempSync(path.join(TEMP_HTML_DIR, `html-${timestamp}-`));
|
|
203
270
|
}
|
|
204
|
-
export function emptyDir(dirPath) {
|
|
205
|
-
const files =
|
|
271
|
+
export async function emptyDir(dirPath) {
|
|
272
|
+
const files = await fsp.readdir(dirPath);
|
|
206
273
|
for (const file of files) {
|
|
207
274
|
const fullPath = path.join(dirPath, file);
|
|
208
|
-
|
|
275
|
+
await fsp.rm(fullPath, { recursive: true, force: true });
|
|
209
276
|
}
|
|
210
277
|
}
|
|
211
|
-
export async function getAvailablePort(startPort = DEFAULT_PORT, maxPort = MAX_PORT) {
|
|
212
|
-
let currentPort = startPort;
|
|
213
|
-
while (currentPort <= maxPort) {
|
|
214
|
-
if (await isPortAvailable(currentPort)) {
|
|
215
|
-
return currentPort;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
currentPort++;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
return -1;
|
|
222
|
-
}
|
|
223
278
|
export async function createSite(inputPath = "", outputPath = "", pathsToCreate = [], ignores = undefined, configs = undefined, interruptCondition = undefined) {
|
|
224
279
|
|
|
225
280
|
// Check `inputPath`
|
|
@@ -267,7 +322,7 @@ export async function createSite(inputPath = "", outputPath = "", pathsToCreate
|
|
|
267
322
|
// Hard reload, clear output path & Get all paths from `inputPath`
|
|
268
323
|
let isHardReloading = pathsToCreate == null;
|
|
269
324
|
if (isHardReloading) {
|
|
270
|
-
emptyDir(outputPath)
|
|
325
|
+
await emptyDir(outputPath)
|
|
271
326
|
pathsToCreate = await crawlDir(inputPath);
|
|
272
327
|
}
|
|
273
328
|
|
|
@@ -310,8 +365,8 @@ export async function createSite(inputPath = "", outputPath = "", pathsToCreate
|
|
|
310
365
|
|
|
311
366
|
|
|
312
367
|
// Filter based on toIgnore() in configs
|
|
313
|
-
const
|
|
314
|
-
if (
|
|
368
|
+
const toBeIgnored = await configs?.toIgnore?.(inputPath, outputPath, currentPath);
|
|
369
|
+
if (toBeIgnored === true || toBeIgnored === null) {
|
|
315
370
|
return false;
|
|
316
371
|
}
|
|
317
372
|
|
|
@@ -336,7 +391,7 @@ export async function createSite(inputPath = "", outputPath = "", pathsToCreate
|
|
|
336
391
|
await configs?.onSiteCreateStart?.(inputPath, outputPath, !isHardReloading);
|
|
337
392
|
|
|
338
393
|
|
|
339
|
-
// Iterate
|
|
394
|
+
// Iterate & build all files
|
|
340
395
|
let wasInterrupted = false;
|
|
341
396
|
await Promise.all(pathsToCreate.map((currentPath) => limit(async () => {
|
|
342
397
|
|
|
@@ -360,9 +415,9 @@ export async function createSite(inputPath = "", outputPath = "", pathsToCreate
|
|
|
360
415
|
if (!pathExists) {
|
|
361
416
|
let pathToDelete = isMdx ? absHtmlPath : absToOutput;
|
|
362
417
|
log(`Deleting ${pathToDelete}`, !toBeVerbose);
|
|
363
|
-
await configs?.onFileChangeStart?.(inputPath, outputPath,
|
|
418
|
+
await configs?.onFileChangeStart?.(inputPath, outputPath, currentPath, pathToDelete, true);
|
|
364
419
|
await fsp.rm(pathToDelete, { recursive: true, force: true });
|
|
365
|
-
await configs?.onFileChangeEnd?.(inputPath, outputPath,
|
|
420
|
+
await configs?.onFileChangeEnd?.(inputPath, outputPath, currentPath, pathToDelete, true, undefined);
|
|
366
421
|
}
|
|
367
422
|
// Make corresponding directory
|
|
368
423
|
else if (isDir) {
|
|
@@ -426,6 +481,7 @@ export class HostMdx {
|
|
|
426
481
|
#app = null;
|
|
427
482
|
#watcher = null;
|
|
428
483
|
#ignores = null;
|
|
484
|
+
#excludes = null;
|
|
429
485
|
#depGraph = new DependencyGraph();
|
|
430
486
|
|
|
431
487
|
|
|
@@ -438,26 +494,41 @@ export class HostMdx {
|
|
|
438
494
|
|
|
439
495
|
|
|
440
496
|
// Private Methods
|
|
441
|
-
async #watchForChanges(event,
|
|
497
|
+
async #watchForChanges(event, targetPath) {
|
|
498
|
+
|
|
499
|
+
// Skip reload if `toIgnore` gives null
|
|
500
|
+
let ignoreStat = await this.configs?.toIgnore?.(this.inputPath, this.outputPath, targetPath);
|
|
501
|
+
if (ignoreStat === null) {
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
// Skip reload if has # [EXCLUDE] header in .ignore file
|
|
507
|
+
let relTargetPath = path.relative(this.inputPath, targetPath);
|
|
508
|
+
let excludeStat = this.#excludes?.ignores(relTargetPath);
|
|
509
|
+
if (excludeStat) {
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
|
|
442
513
|
|
|
443
514
|
// Update dependency graph
|
|
444
515
|
if (event === "unlink") {
|
|
445
|
-
this.#depGraph.removeEntry(
|
|
516
|
+
this.#depGraph.removeEntry(targetPath);
|
|
446
517
|
}
|
|
447
518
|
else {
|
|
448
|
-
this.#depGraph.addEntry(
|
|
519
|
+
this.#depGraph.addEntry(targetPath);
|
|
449
520
|
}
|
|
450
521
|
|
|
451
522
|
|
|
452
523
|
// Add changed path
|
|
453
|
-
let dependencies = this.#depGraph.getDeepDependents(
|
|
454
|
-
this.#alteredPaths = this.#alteredPaths.concat([...dependencies,
|
|
524
|
+
let dependencies = this.#depGraph.getDeepDependents(targetPath);
|
|
525
|
+
this.#alteredPaths = this.#alteredPaths.concat([...dependencies, targetPath]);
|
|
455
526
|
|
|
456
527
|
|
|
457
528
|
// Reflect changes immediately
|
|
458
529
|
if (this.configs?.trackChanges !== undefined && this.configs?.trackChanges != TrackChanges.NONE) {
|
|
459
530
|
let toHardReload = this.configs?.trackChanges == TrackChanges.HARD;
|
|
460
|
-
log(`${toHardReload ? "Hard recreating" : "Recreating"} site, Event: ${event}, Path: ${
|
|
531
|
+
log(`${toHardReload ? "Hard recreating" : "Recreating"} site, Event: ${event}, Path: ${targetPath}`, !this.configs?.toBeVerbose);
|
|
461
532
|
await this.recreateSite(toHardReload);
|
|
462
533
|
}
|
|
463
534
|
}
|
|
@@ -507,13 +578,12 @@ export class HostMdx {
|
|
|
507
578
|
this.#ignores = getIgnore(ignoreFilePath);
|
|
508
579
|
|
|
509
580
|
|
|
510
|
-
//
|
|
511
|
-
|
|
581
|
+
// Get excludes
|
|
582
|
+
this.#excludes = getExclude(ignoreFilePath);
|
|
512
583
|
|
|
513
584
|
|
|
514
|
-
//
|
|
515
|
-
|
|
516
|
-
this.#watcher = chokidar.watch(this.inputPath, chokidarOptions).on("all", (event, targetPath) => this.#watchForChanges(event, targetPath));
|
|
585
|
+
// Broadcast hosting about to start
|
|
586
|
+
await this.configs?.onHostStarting?.(this.inputPath, this.outputPath, port);
|
|
517
587
|
|
|
518
588
|
|
|
519
589
|
// Delete old files & Create site
|
|
@@ -525,14 +595,19 @@ export class HostMdx {
|
|
|
525
595
|
let modMdxSettings = await this.configs?.modBundleMDXSettings?.(this.inputPath, this.outputPath, defaultMdxSettings);
|
|
526
596
|
let aliases = modMdxSettings?.esbuildOptions?.({})?.alias ?? {};
|
|
527
597
|
this.#depGraph.setAlias(aliases);
|
|
528
|
-
await this.#depGraph.createGraph(this.inputPath, async (
|
|
598
|
+
await this.#depGraph.createGraph(this.inputPath, async (targetPath) => (await this.configs?.toIgnore?.(this.inputPath, this.outputPath, targetPath)) === null || this.#excludes?.ignores(path.relative(this.inputPath, targetPath)));
|
|
529
599
|
|
|
530
600
|
|
|
531
601
|
// Start server to host site
|
|
532
|
-
this.#app = await startServer(this.outputPath, port, (e) => { log(`Failed to start server: ${e.message}`);
|
|
602
|
+
this.#app = await startServer(this.outputPath, port, (e) => { log(`Failed to start server: ${e.message}`); });
|
|
533
603
|
this.#app?.server?.on("close", async () => { await this.configs?.onHostEnded?.(this.inputPath, this.outputPath, port); });
|
|
534
604
|
|
|
535
605
|
|
|
606
|
+
// Watch for changes
|
|
607
|
+
let chokidarOptions = { ...DEFAULT_CHOKIDAR_OPTIONS, ...(this.configs?.chokidarOptions ?? {}) };
|
|
608
|
+
this.#watcher = chokidar.watch(this.inputPath, chokidarOptions).on("all", async (event, targetPath) => { await this.#watchForChanges(event, targetPath) });
|
|
609
|
+
|
|
610
|
+
|
|
536
611
|
// Broadcast hosting started
|
|
537
612
|
await this.configs?.onHostStarted?.(this.inputPath, this.outputPath, port);
|
|
538
613
|
|
|
@@ -566,12 +641,13 @@ export class HostMdx {
|
|
|
566
641
|
|
|
567
642
|
|
|
568
643
|
// Actual site creation
|
|
644
|
+
let pathsToCreate = hardReload ? null : [...new Set(this.#alteredPaths)];
|
|
569
645
|
try {
|
|
570
|
-
let pathsToCreate = hardReload ? null : [...this.#alteredPaths];
|
|
571
|
-
await createSite(this.inputPath, this.outputPath, pathsToCreate, this.#ignores, this.configs, () => this.#siteCreationStatus != SiteCreationStatus.ONGOING);
|
|
572
646
|
this.#alteredPaths = [];
|
|
647
|
+
await createSite(this.inputPath, this.outputPath, pathsToCreate, this.#ignores, this.configs, () => this.#siteCreationStatus != SiteCreationStatus.ONGOING);
|
|
573
648
|
}
|
|
574
649
|
catch (err) {
|
|
650
|
+
this.#alteredPaths = hardReload ? this.#alteredPaths : [...new Set([...pathsToCreate, ...this.#alteredPaths])]; // Readd incase of failure
|
|
575
651
|
log(`Failed to create site!\n${err.stack}`);
|
|
576
652
|
}
|
|
577
653
|
|
|
@@ -580,13 +656,22 @@ export class HostMdx {
|
|
|
580
656
|
const wasPending = this.#siteCreationStatus === SiteCreationStatus.PENDING_RECREATION;
|
|
581
657
|
this.#siteCreationStatus = SiteCreationStatus.NONE;
|
|
582
658
|
if (wasPending) {
|
|
583
|
-
|
|
659
|
+
log("Recreating previously pending")
|
|
660
|
+
const wasHard = this.#pendingHardSiteCreation;
|
|
661
|
+
this.#pendingHardSiteCreation = false;
|
|
662
|
+
await this.recreateSite(wasHard);
|
|
584
663
|
}
|
|
585
664
|
}
|
|
586
665
|
async abortSiteCreation() {
|
|
587
666
|
this.#siteCreationStatus = SiteCreationStatus.NONE;
|
|
667
|
+
this.#pendingHardSiteCreation = false;
|
|
588
668
|
}
|
|
589
669
|
async stop() {
|
|
670
|
+
|
|
671
|
+
// Abort site creation if ongoing
|
|
672
|
+
await this.abortSiteCreation()
|
|
673
|
+
|
|
674
|
+
|
|
590
675
|
// Remove temp dir html path
|
|
591
676
|
if (!this.#outputPathProvided && fs.existsSync(this.outputPath)) {
|
|
592
677
|
fs.rmSync(this.outputPath, { recursive: true });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "host-mdx",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.2",
|
|
4
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",
|
|
@@ -20,7 +20,6 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"chokidar": "^5.0.0",
|
|
22
22
|
"ignore": "^7.0.5",
|
|
23
|
-
"lodash": "^4.17.23",
|
|
24
23
|
"lowlight": "^3.3.0",
|
|
25
24
|
"mdx-bundler": "^10.1.1",
|
|
26
25
|
"p-limit": "^7.3.0",
|