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 +17 -9
- package/cli.js +16 -6
- package/dependency-graph.js +54 -48
- package/index.js +132 -46
- package/package.json +1 -1
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
|
@@ -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
|
-
|
|
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 =
|
|
75
|
-
|
|
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
|
|
108
|
-
if (fs.existsSync(
|
|
109
|
-
const content =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
314
|
-
if (
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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(
|
|
517
|
+
this.#depGraph.removeEntry(targetPath);
|
|
446
518
|
}
|
|
447
519
|
else {
|
|
448
|
-
this.#depGraph.addEntry(
|
|
520
|
+
this.#depGraph.addEntry(targetPath);
|
|
449
521
|
}
|
|
450
522
|
|
|
451
523
|
|
|
452
524
|
// Add changed path
|
|
453
|
-
let dependencies = this.#depGraph.getDeepDependents(
|
|
454
|
-
this.#alteredPaths = this.#alteredPaths.concat([...dependencies,
|
|
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: ${
|
|
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
|
-
//
|
|
511
|
-
|
|
582
|
+
// Get excludes
|
|
583
|
+
this.#excludes = getExclude(ignoreFilePath);
|
|
512
584
|
|
|
513
585
|
|
|
514
|
-
//
|
|
515
|
-
|
|
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 (
|
|
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}`);
|
|
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
|
-
|
|
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 });
|