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 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
- blog/page2/
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 true;
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 => (val.startsWith(TRACK_CHANGES_FLAG) || val.startsWith(TRACK_CHANGES_SHORT_FLAG)));
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
- return concurrencyProvided ? Number(concurrency.split('=')[1]) : undefined;
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("--- HARD RELOADING ---")
182
- await hostMdx?.recreateSite(true)
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
@@ -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 somePath of list) {
23
+ for (let targetPath of list) {
24
24
 
25
25
  // get absolute path
26
- const absPath = path.join(dir, somePath);
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(somePath, aliases) {
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 = somePath === alias;
54
- const isSubPath = somePath.startsWith(`${alias}${path.sep}`) || somePath.startsWith(`${alias}/`);
53
+ const isExact = targetPath === alias;
54
+ const isSubPath = targetPath.startsWith(`${alias}${path.sep}`) || targetPath.startsWith(`${alias}/`);
55
55
 
56
56
  if (isExact || isSubPath) {
57
- somePath = somePath.replace(alias, aliasPath);
58
- somePath = path.normalize(somePath);
57
+ targetPath = targetPath.replace(alias, aliasPath);
58
+ targetPath = path.normalize(targetPath);
59
59
  break;
60
60
  }
61
61
  }
62
62
 
63
- return somePath;
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 has already been traversed or is a node_modules
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
- // classes
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
- if (this.#graph[dep] === undefined) {
218
- return;
219
- }
230
+ this.#graph?.[dep]?.[DEPENDENTS_KEY]?.delete(relFilePath);
231
+ });
232
+
220
233
 
221
- this.#graph[dep][DEPENDENTS_KEY].delete(relFilePath);
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
- // Skip if not in graph
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
- canTriggerReload: (inputPath, outputpath, p) => {
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 = p.split(path.sep);
75
- return !segments.some(segment => ignoredDirs.has(segment));
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 errorFile = path.join(hostDir, FILE_404);
108
- if (fs.existsSync(errorFile)) {
109
- const content = fs.readFileSync(errorFile);
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
- return { ...DEFAULT_CONFIGS, ...(await import(cleanConfigFilePath)) };
253
+ let cleanConfigFilePath = pathToFileURL(configFilePath).href;
254
+ configs = { ...configs, ...(await import(cleanConfigFilePath)) };
188
255
  }
189
256
 
190
- return _.cloneDeep(DEFAULT_CONFIGS);
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 = fs.readdirSync(dirPath);
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
- fs.rmSync(fullPath, { recursive: true, force: true });
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 toIgnore = await configs?.toIgnore?.(inputPath, outputPath, currentPath);
314
- if (toIgnore) {
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 through all folders & files
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, pathToDelete, absToOutput, true);
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, pathToDelete, absToOutput, true, undefined);
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, somePath) {
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(somePath);
516
+ this.#depGraph.removeEntry(targetPath);
446
517
  }
447
518
  else {
448
- this.#depGraph.addEntry(somePath);
519
+ this.#depGraph.addEntry(targetPath);
449
520
  }
450
521
 
451
522
 
452
523
  // Add changed path
453
- let dependencies = this.#depGraph.getDeepDependents(somePath);
454
- this.#alteredPaths = this.#alteredPaths.concat([...dependencies, somePath]);
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: ${somePath}`, !this.configs?.toBeVerbose);
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
- // Broadcast hosting about to start
511
- await this.configs?.onHostStarting?.(this.inputPath, this.outputPath, port);
581
+ // Get excludes
582
+ this.#excludes = getExclude(ignoreFilePath);
512
583
 
513
584
 
514
- // Watch for changes
515
- let chokidarOptions = { ...DEFAULT_CHOKIDAR_OPTIONS, ...(this.configs?.chokidarOptions ?? {}) };
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 (p) => !(await this.configs?.canTriggerReload?.(this.inputPath, this.outputPath, p)));
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}`); throw e; });
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
- await this.recreateSite(this.#pendingHardSiteCreation);
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.0",
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",