host-mdx 2.4.0 → 2.4.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.
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
@@ -35,15 +35,24 @@ const FILE_404 = "404.html";
35
35
  const NOT_FOUND_404_MESSAGE = "404";
36
36
  const DEFAULT_PORT = 3000;
37
37
  const MAX_PORT = 4000;
38
+ const EXCLUDE_HEADER = "# [EXCLUDE]"; // Case insensitive
38
39
  const TEMP_HTML_DIR = path.join(os.tmpdir(), `${APP_NAME}`);
39
40
  const DEFAULT_IGNORES = `
41
+ ${EXCLUDE_HEADER}
40
42
  ${IGNORE_FILE_NAME}
43
+ ${EXCLUDE_HEADER}
41
44
  ${CONFIG_FILE_NAME}
45
+ ${EXCLUDE_HEADER}
42
46
  node_modules
47
+ ${EXCLUDE_HEADER}
43
48
  package-lock.json
49
+ ${EXCLUDE_HEADER}
44
50
  package.json
51
+ ${EXCLUDE_HEADER}
45
52
  .git
53
+ ${EXCLUDE_HEADER}
46
54
  .github
55
+ ${EXCLUDE_HEADER}
47
56
  .gitignore
48
57
  `;
49
58
 
@@ -64,15 +73,23 @@ const DEFAULT_CHOKIDAR_OPTIONS = {
64
73
  };
65
74
  const DEFAULT_CONFIGS = {
66
75
  // port: 3000, // Intentionally kept commented out, otherwise interferes with auto port assigning DO NOT CHANGE
67
- chokidarOptions: DEFAULT_CHOKIDAR_OPTIONS,
68
76
  trackChanges: 0,
69
77
  toBeVerbose: false,
70
78
  concurrency: 1,
71
79
  chokidarOptions: DEFAULT_CHOKIDAR_OPTIONS,
72
- canTriggerReload: (inputPath, outputpath, p) => {
80
+ toIgnore: (inputPath, outputPath, targetPath) => {
81
+ const isGOutputStream = /\.goutputstream-\w+$/.test(targetPath);
82
+ if (isGOutputStream) {
83
+ return null;
84
+ }
85
+
73
86
  const ignoredDirs = new Set(['node_modules', '.git', '.github']);
74
- const segments = p.split(path.sep);
75
- return !segments.some(segment => ignoredDirs.has(segment));
87
+ const segments = targetPath.split(path.sep);
88
+ if (segments.some(segment => ignoredDirs.has(segment))) {
89
+ return null;
90
+ }
91
+
92
+ return false;
76
93
  }
77
94
  };
78
95
 
@@ -84,9 +101,48 @@ function getIgnore(ignoreFilePath) {
84
101
  if (fs.existsSync(ignoreFilePath)) {
85
102
  ignoreContent += `\n${fs.readFileSync(ignoreFilePath, "utf8")}`;
86
103
  }
87
-
88
104
  ig.add(ignoreContent);
105
+ return ig;
106
+ }
107
+ function getExclude(ignoreFilePath) {
108
+
109
+ // Read .ignore file
110
+ const ig = ignore();
111
+ let rawContent = DEFAULT_IGNORES;
112
+ if (fs.existsSync(ignoreFilePath)) {
113
+ rawContent += "\n" + fs.readFileSync(ignoreFilePath, "utf8");
114
+ }
115
+
116
+
117
+ // Only get lines which have "# [EXCLUDE]" comment on top
118
+ let filteredLines = [];
119
+ let hasExclude = false;
120
+ const lines = rawContent.split(/\r?\n/);
121
+ const excludeComment = EXCLUDE_HEADER.toLowerCase();
122
+ for (const line of lines) {
123
+ const trimmed = line.trim();
124
+
125
+ // Check for the header tag
126
+ if (trimmed.toLowerCase() === excludeComment) {
127
+ hasExclude = true;
128
+ continue;
129
+ }
130
+
131
+ // Reset if empty line found
132
+ if (trimmed === "") {
133
+ hasExclude = false;
134
+ continue;
135
+ }
136
+
137
+ // Add line if has exclude otherwise continue
138
+ if (hasExclude) {
139
+ filteredLines.push(trimmed);
140
+ }
141
+ }
142
+
89
143
 
144
+ // Add to ignore
145
+ ig.add(filteredLines.join("\n"));
90
146
  return ig;
91
147
  }
92
148
  async function createFile(filePath, fileContent = "") {
@@ -103,10 +159,10 @@ async function startServer(hostDir, port, errorCallback) { // Starts server at
103
159
  // Start Server
104
160
  const assets = sirv(hostDir, { dev: true });
105
161
  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);
162
+ onNoMatch: async (req, res) => { // Send 404 file if found else not found message
163
+ const file404 = path.join(hostDir, FILE_404);
164
+ if (fs.existsSync(file404)) {
165
+ const content = await fsp.readFile(file404);
110
166
  res.writeHead(404, {
111
167
  'Content-Type': 'text/html',
112
168
  'Content-Length': content.length
@@ -150,6 +206,18 @@ async function isPortAvailable(port) {
150
206
  server.listen(port);
151
207
  });
152
208
  }
209
+ async function getAvailablePort(startPort = DEFAULT_PORT, maxPort = MAX_PORT) {
210
+ let currentPort = startPort;
211
+ while (currentPort <= maxPort) {
212
+ if (await isPortAvailable(currentPort)) {
213
+ return currentPort;
214
+ }
215
+
216
+ currentPort++;
217
+ }
218
+
219
+ return -1;
220
+ }
153
221
  export function log(msg, toSkip = false) {
154
222
  if (toSkip) { // Useful for verbose check
155
223
  return
@@ -201,25 +269,13 @@ export function createTempDir() {
201
269
 
202
270
  return fs.mkdtempSync(path.join(TEMP_HTML_DIR, `html-${timestamp}-`));
203
271
  }
204
- export function emptyDir(dirPath) {
205
- const files = fs.readdirSync(dirPath);
272
+ export async function emptyDir(dirPath) {
273
+ const files = await fsp.readdir(dirPath);
206
274
  for (const file of files) {
207
275
  const fullPath = path.join(dirPath, file);
208
- fs.rmSync(fullPath, { recursive: true, force: true });
276
+ await fsp.rm(fullPath, { recursive: true, force: true });
209
277
  }
210
278
  }
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
279
  export async function createSite(inputPath = "", outputPath = "", pathsToCreate = [], ignores = undefined, configs = undefined, interruptCondition = undefined) {
224
280
 
225
281
  // Check `inputPath`
@@ -267,7 +323,7 @@ export async function createSite(inputPath = "", outputPath = "", pathsToCreate
267
323
  // Hard reload, clear output path & Get all paths from `inputPath`
268
324
  let isHardReloading = pathsToCreate == null;
269
325
  if (isHardReloading) {
270
- emptyDir(outputPath)
326
+ await emptyDir(outputPath)
271
327
  pathsToCreate = await crawlDir(inputPath);
272
328
  }
273
329
 
@@ -310,8 +366,8 @@ export async function createSite(inputPath = "", outputPath = "", pathsToCreate
310
366
 
311
367
 
312
368
  // Filter based on toIgnore() in configs
313
- const toIgnore = await configs?.toIgnore?.(inputPath, outputPath, currentPath);
314
- if (toIgnore) {
369
+ const toBeIgnored = await configs?.toIgnore?.(inputPath, outputPath, currentPath);
370
+ if (toBeIgnored === true || toBeIgnored === null) {
315
371
  return false;
316
372
  }
317
373
 
@@ -336,7 +392,7 @@ export async function createSite(inputPath = "", outputPath = "", pathsToCreate
336
392
  await configs?.onSiteCreateStart?.(inputPath, outputPath, !isHardReloading);
337
393
 
338
394
 
339
- // Iterate through all folders & files
395
+ // Iterate & build all files
340
396
  let wasInterrupted = false;
341
397
  await Promise.all(pathsToCreate.map((currentPath) => limit(async () => {
342
398
 
@@ -360,9 +416,9 @@ export async function createSite(inputPath = "", outputPath = "", pathsToCreate
360
416
  if (!pathExists) {
361
417
  let pathToDelete = isMdx ? absHtmlPath : absToOutput;
362
418
  log(`Deleting ${pathToDelete}`, !toBeVerbose);
363
- await configs?.onFileChangeStart?.(inputPath, outputPath, pathToDelete, absToOutput, true);
419
+ await configs?.onFileChangeStart?.(inputPath, outputPath, currentPath, pathToDelete, true);
364
420
  await fsp.rm(pathToDelete, { recursive: true, force: true });
365
- await configs?.onFileChangeEnd?.(inputPath, outputPath, pathToDelete, absToOutput, true, undefined);
421
+ await configs?.onFileChangeEnd?.(inputPath, outputPath, currentPath, pathToDelete, true, undefined);
366
422
  }
367
423
  // Make corresponding directory
368
424
  else if (isDir) {
@@ -426,6 +482,7 @@ export class HostMdx {
426
482
  #app = null;
427
483
  #watcher = null;
428
484
  #ignores = null;
485
+ #excludes = null;
429
486
  #depGraph = new DependencyGraph();
430
487
 
431
488
 
@@ -438,26 +495,41 @@ export class HostMdx {
438
495
 
439
496
 
440
497
  // Private Methods
441
- async #watchForChanges(event, somePath) {
498
+ async #watchForChanges(event, targetPath) {
499
+
500
+ // Skip reload if `toIgnore` gives null
501
+ let ignoreStat = await this.configs?.toIgnore?.(this.inputPath, this.outputPath, targetPath);
502
+ if (ignoreStat === null) {
503
+ return;
504
+ }
505
+
506
+
507
+ // Skip reload if has # [EXCLUDE] header in .ignore file
508
+ let relTargetPath = path.relative(this.inputPath, targetPath);
509
+ let excludeStat = this.#excludes?.ignores(relTargetPath);
510
+ if (excludeStat) {
511
+ return;
512
+ }
513
+
442
514
 
443
515
  // Update dependency graph
444
516
  if (event === "unlink") {
445
- this.#depGraph.removeEntry(somePath);
517
+ this.#depGraph.removeEntry(targetPath);
446
518
  }
447
519
  else {
448
- this.#depGraph.addEntry(somePath);
520
+ this.#depGraph.addEntry(targetPath);
449
521
  }
450
522
 
451
523
 
452
524
  // Add changed path
453
- let dependencies = this.#depGraph.getDeepDependents(somePath);
454
- this.#alteredPaths = this.#alteredPaths.concat([...dependencies, somePath]);
525
+ let dependencies = this.#depGraph.getDeepDependents(targetPath);
526
+ this.#alteredPaths = this.#alteredPaths.concat([...dependencies, targetPath]);
455
527
 
456
528
 
457
529
  // Reflect changes immediately
458
530
  if (this.configs?.trackChanges !== undefined && this.configs?.trackChanges != TrackChanges.NONE) {
459
531
  let toHardReload = this.configs?.trackChanges == TrackChanges.HARD;
460
- log(`${toHardReload ? "Hard recreating" : "Recreating"} site, Event: ${event}, Path: ${somePath}`, !this.configs?.toBeVerbose);
532
+ log(`${toHardReload ? "Hard recreating" : "Recreating"} site, Event: ${event}, Path: ${targetPath}`, !this.configs?.toBeVerbose);
461
533
  await this.recreateSite(toHardReload);
462
534
  }
463
535
  }
@@ -507,13 +579,12 @@ export class HostMdx {
507
579
  this.#ignores = getIgnore(ignoreFilePath);
508
580
 
509
581
 
510
- // Broadcast hosting about to start
511
- await this.configs?.onHostStarting?.(this.inputPath, this.outputPath, port);
582
+ // Get excludes
583
+ this.#excludes = getExclude(ignoreFilePath);
512
584
 
513
585
 
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));
586
+ // Broadcast hosting about to start
587
+ await this.configs?.onHostStarting?.(this.inputPath, this.outputPath, port);
517
588
 
518
589
 
519
590
  // Delete old files & Create site
@@ -525,14 +596,19 @@ export class HostMdx {
525
596
  let modMdxSettings = await this.configs?.modBundleMDXSettings?.(this.inputPath, this.outputPath, defaultMdxSettings);
526
597
  let aliases = modMdxSettings?.esbuildOptions?.({})?.alias ?? {};
527
598
  this.#depGraph.setAlias(aliases);
528
- await this.#depGraph.createGraph(this.inputPath, async (p) => !(await this.configs?.canTriggerReload?.(this.inputPath, this.outputPath, p)));
599
+ 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
600
 
530
601
 
531
602
  // Start server to host site
532
- this.#app = await startServer(this.outputPath, port, (e) => { log(`Failed to start server: ${e.message}`); throw e; });
603
+ this.#app = await startServer(this.outputPath, port, (e) => { log(`Failed to start server: ${e.message}`); });
533
604
  this.#app?.server?.on("close", async () => { await this.configs?.onHostEnded?.(this.inputPath, this.outputPath, port); });
534
605
 
535
606
 
607
+ // Watch for changes
608
+ let chokidarOptions = { ...DEFAULT_CHOKIDAR_OPTIONS, ...(this.configs?.chokidarOptions ?? {}) };
609
+ this.#watcher = chokidar.watch(this.inputPath, chokidarOptions).on("all", async (event, targetPath) => { await this.#watchForChanges(event, targetPath) });
610
+
611
+
536
612
  // Broadcast hosting started
537
613
  await this.configs?.onHostStarted?.(this.inputPath, this.outputPath, port);
538
614
 
@@ -566,12 +642,13 @@ export class HostMdx {
566
642
 
567
643
 
568
644
  // Actual site creation
645
+ let pathsToCreate = hardReload ? null : [...new Set(this.#alteredPaths)];
569
646
  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
647
  this.#alteredPaths = [];
648
+ await createSite(this.inputPath, this.outputPath, pathsToCreate, this.#ignores, this.configs, () => this.#siteCreationStatus != SiteCreationStatus.ONGOING);
573
649
  }
574
650
  catch (err) {
651
+ this.#alteredPaths = hardReload ? this.#alteredPaths : [...new Set([...pathsToCreate, ...this.#alteredPaths])]; // Readd incase of failure
575
652
  log(`Failed to create site!\n${err.stack}`);
576
653
  }
577
654
 
@@ -580,13 +657,22 @@ export class HostMdx {
580
657
  const wasPending = this.#siteCreationStatus === SiteCreationStatus.PENDING_RECREATION;
581
658
  this.#siteCreationStatus = SiteCreationStatus.NONE;
582
659
  if (wasPending) {
583
- await this.recreateSite(this.#pendingHardSiteCreation);
660
+ log("Recreating previously pending")
661
+ const wasHard = this.#pendingHardSiteCreation;
662
+ this.#pendingHardSiteCreation = false;
663
+ await this.recreateSite(wasHard);
584
664
  }
585
665
  }
586
666
  async abortSiteCreation() {
587
667
  this.#siteCreationStatus = SiteCreationStatus.NONE;
668
+ this.#pendingHardSiteCreation = false;
588
669
  }
589
670
  async stop() {
671
+
672
+ // Abort site creation if ongoing
673
+ await this.abortSiteCreation()
674
+
675
+
590
676
  // Remove temp dir html path
591
677
  if (!this.#outputPathProvided && fs.existsSync(this.outputPath)) {
592
678
  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.1",
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",