mr-md 2.1.1-beta → 2.2.0-beta

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.
@@ -1 +1 @@
1
- {"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"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"}
package/dist/builder.js CHANGED
@@ -5,31 +5,29 @@ import { fileURLToPath } from "url";
5
5
  const __filename = fileURLToPath(import.meta.url);
6
6
  const __dirname = path.dirname(__filename);
7
7
  function getCallerDir() {
8
+ const originalPrepareStackTrace = Error.prepareStackTrace;
9
+ Error.prepareStackTrace = (_, stack) => stack;
8
10
  const err = new Error();
9
- const stack = err.stack?.split("\n");
10
- if (!stack || stack.length < 2)
11
+ const stack = err.stack;
12
+ Error.prepareStackTrace = originalPrepareStackTrace;
13
+ if (!stack || !Array.isArray(stack))
11
14
  return undefined;
12
- let builderFilePath;
13
- for (let i = 1; i < stack.length; i++) {
14
- const line = stack[i];
15
- const match = line.match(/\((.*?):\d+:\d+\)/) || line.match(/at (.*?):\d+:\d+/);
16
- if (match) {
17
- let p = match[1];
18
- if (p.startsWith("file://")) {
19
- p = p.replace(/^file:\/\//, "");
20
- }
21
- if (p.startsWith("/") && p[2] === ":") {
22
- p = p.substring(1);
23
- }
24
- if (!builderFilePath) {
25
- builderFilePath = p;
26
- continue;
27
- }
28
- if (p === builderFilePath) {
29
- continue;
30
- }
31
- return path.dirname(p);
15
+ for (let i = 0; i < stack.length; i++) {
16
+ const callSite = stack[i];
17
+ let p = callSite.getFileName();
18
+ if (!p)
19
+ continue;
20
+ if (p.startsWith("file://")) {
21
+ p = fileURLToPath(p);
22
+ }
23
+ else if (p.startsWith("/") && p[2] === ":") {
24
+ p = p.substring(1);
25
+ }
26
+ const basename = path.basename(p);
27
+ if (basename === "builder.ts" || basename === "builder.js" || basename === "index.ts" || basename === "index.js") {
28
+ continue;
32
29
  }
30
+ return path.dirname(p);
33
31
  }
34
32
  return undefined;
35
33
  }
@@ -307,7 +305,7 @@ export class LessonBuilder {
307
305
  const base = this.options.contentBase ?? process.cwd();
308
306
  const resolved = path.resolve(base, src);
309
307
  const ext = path.extname(resolved);
310
- const configPath = `${resolved.slice(0, -ext.length)}.config.json`;
308
+ const configPath = `${ext.length > 0 ? resolved.slice(0, -ext.length) : resolved}.config.json`;
311
309
  if (fs.existsSync(configPath)) {
312
310
  return JSON.parse(fs.readFileSync(configPath, "utf-8"));
313
311
  }
@@ -417,7 +415,7 @@ export class LessonBuilder {
417
415
  const lesson = { meta: this.meta, blocks: this.blocks };
418
416
  const html = render(lesson, this.options);
419
417
  const outDir = path.resolve(this.options.outDir);
420
- const outPath = path.join(outDir, `${this.meta.slug}.html`);
418
+ const outPath = path.join(outDir, "index.html");
421
419
  const outPathDir = path.dirname(outPath);
422
420
  if (!fs.existsSync(outPathDir))
423
421
  fs.mkdirSync(outPathDir, { recursive: true });
@@ -596,7 +594,7 @@ export class ChapterBuilder {
596
594
  const chapterData = { meta: this.meta, lessons };
597
595
  const html = renderChapter(chapterData, this.options);
598
596
  const outDir = path.resolve(this.options.outDir);
599
- const outPath = path.join(outDir, `${this.meta.slug}.html`);
597
+ const outPath = path.join(outDir, "index.html");
600
598
  const outPathDir = path.dirname(outPath);
601
599
  if (!fs.existsSync(outPathDir))
602
600
  fs.mkdirSync(outPathDir, { recursive: true });
package/dist/cli/dev.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare function runDev(args: string[]): Promise<void>;
1
+ export declare function runDev(args: string[]): void;
2
2
  //# sourceMappingURL=dev.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/cli/dev.ts"],"names":[],"mappings":"AAMA,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,iBAwG1C"}
1
+ {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/cli/dev.ts"],"names":[],"mappings":"AAKA,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,QA4HpC"}
package/dist/cli/dev.js CHANGED
@@ -1,23 +1,31 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
- import { exec } from "child_process";
4
- export async function runDev(args) {
3
+ export function runDev(args) {
5
4
  const dir = args[0] || ".";
6
- const outDir = path.resolve(process.cwd(), dir, "out");
5
+ let outDir = path.resolve(process.cwd(), dir, "out");
7
6
  console.log(`Starting dev server for directory: ${dir}`);
8
7
  let server;
8
+ let currentBuild = null;
9
9
  const rebuild = () => {
10
+ if (currentBuild) {
11
+ currentBuild.kill();
12
+ }
10
13
  console.log("Rebuilding...");
11
- const entryPoints = ["chapter.ts", "index.ts", "lesson.ts"];
14
+ const entryPoints = ["chapter.ts", "index.ts", "lesson.ts", "chapters/01-chapter/chapter.ts"];
12
15
  for (const entry of entryPoints) {
13
16
  const entryPath = path.join(dir, entry);
14
17
  if (fs.existsSync(entryPath)) {
15
- exec(`NODE_ENV=development bun "${entryPath}"`, (err, stdout, stderr) => {
16
- if (err)
17
- console.error("Build failed:", stderr);
18
- else {
19
- console.log("Build successful.");
20
- server?.publish("livereload", "reload");
18
+ outDir = path.resolve(process.cwd(), path.dirname(entryPath), "out");
19
+ currentBuild = Bun.spawn([process.execPath, entryPath], {
20
+ env: { ...process.env, NODE_ENV: "development" },
21
+ onExit(proc, exitCode, signalCode, error) {
22
+ if (exitCode === 0) {
23
+ console.log("Build successful.");
24
+ server?.publish("livereload", "reload");
25
+ }
26
+ else if (exitCode !== null) {
27
+ console.error(`Build failed with exit code ${exitCode}`);
28
+ }
21
29
  }
22
30
  });
23
31
  return;
@@ -29,7 +37,7 @@ export async function runDev(args) {
29
37
  if (fs.existsSync(dir)) {
30
38
  let timeout;
31
39
  fs.watch(dir, { recursive: true }, (eventType, filename) => {
32
- if (!filename || filename.includes("out/") || filename.includes(".git/"))
40
+ if (!filename || filename.includes("out/") || filename.includes("out\\") || filename.includes(".git/") || filename.includes(".git\\"))
33
41
  return;
34
42
  clearTimeout(timeout);
35
43
  timeout = setTimeout(() => {
@@ -39,62 +47,69 @@ export async function runDev(args) {
39
47
  });
40
48
  console.log(`Watching ${dir} for changes...`);
41
49
  }
42
- server = Bun.serve({
43
- port: 3000,
44
- async fetch(req, srv) {
45
- if (srv.upgrade(req))
46
- return;
47
- const url = new URL(req.url);
48
- const decodedPath = decodeURIComponent(url.pathname);
49
- let filePath = path.resolve(outDir, "." + decodedPath);
50
- if (filePath.endsWith(path.sep)) {
51
- const files = fs.existsSync(outDir) ? fs.readdirSync(outDir) : [];
52
- const htmlFiles = files.filter(f => f.endsWith(".html"));
53
- if (htmlFiles.includes("index.html")) {
54
- filePath = path.join(outDir, "index.html");
50
+ const basePort = process.env.PORT ? parseInt(process.env.PORT) : 3000;
51
+ let port = basePort;
52
+ const maxPort = basePort + 10;
53
+ const fetchHandler = async (req, srv) => {
54
+ if (srv.upgrade(req))
55
+ return;
56
+ const url = new URL(req.url);
57
+ const decodedPath = decodeURIComponent(url.pathname);
58
+ let filePath = path.resolve(outDir, "." + decodedPath);
59
+ if (decodedPath.endsWith("/")) {
60
+ filePath = path.join(outDir, "index.html");
61
+ }
62
+ if (!filePath.startsWith(outDir + path.sep) && filePath !== outDir) {
63
+ return new Response("Forbidden", { status: 403 });
64
+ }
65
+ if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
66
+ if (filePath.endsWith(".html")) {
67
+ const file = Bun.file(filePath);
68
+ let text = await file.text();
69
+ const script = `<script>
70
+ const ws = new WebSocket(\`ws://\${location.host}/\`);
71
+ ws.onmessage = (e) => { if (e.data === "reload") location.reload(); };
72
+ </script>`;
73
+ const bodyRegex = /<\/body>/i;
74
+ const match = text.match(bodyRegex);
75
+ if (match && match.index !== undefined) {
76
+ text = text.slice(0, match.index) + script + text.slice(match.index);
55
77
  }
56
78
  else {
57
- const chapterFile = htmlFiles.find(f => f.includes("chapter"));
58
- if (chapterFile) {
59
- filePath = path.join(outDir, chapterFile);
60
- }
61
- else if (htmlFiles.length > 0) {
62
- filePath = path.join(outDir, htmlFiles[0]);
63
- }
64
- else {
65
- filePath = path.join(outDir, "index.html");
66
- }
79
+ text += script;
67
80
  }
81
+ return new Response(text, { headers: { "Content-Type": "text/html" } });
68
82
  }
69
- if (!filePath.startsWith(outDir + path.sep) && filePath !== outDir) {
70
- return new Response("Forbidden", { status: 403 });
71
- }
72
- if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
73
- if (filePath.endsWith(".html")) {
74
- const file = Bun.file(filePath);
75
- let text = await file.text();
76
- const lastBodyIndex = text.lastIndexOf("</body>");
77
- if (lastBodyIndex !== -1) {
78
- text = text.slice(0, lastBodyIndex) + `<script>
79
- const ws = new WebSocket("ws://localhost:3000/");
80
- ws.onmessage = (e) => { if (e.data === "reload") location.reload(); };
81
- </script></body>` + text.slice(lastBodyIndex + 7);
82
- }
83
- return new Response(text, { headers: { "Content-Type": "text/html" } });
84
- }
85
- return new Response(Bun.file(filePath));
83
+ return new Response(Bun.file(filePath));
84
+ }
85
+ return new Response("Not found", { status: 404 });
86
+ };
87
+ const wsHandler = {
88
+ message() { },
89
+ open(ws) { ws.subscribe("livereload"); }
90
+ };
91
+ while (port <= maxPort) {
92
+ try {
93
+ server = Bun.serve({
94
+ port,
95
+ fetch: fetchHandler,
96
+ websocket: wsHandler
97
+ });
98
+ console.log(`Dev server listening on http://localhost:${server.port}`);
99
+ break;
100
+ }
101
+ catch (err) {
102
+ if (err.code === 'EADDRINUSE') {
103
+ console.warn(`Port ${port} is in use, trying ${port + 1}...`);
104
+ port++;
86
105
  }
87
- const baseDir = path.resolve(process.cwd(), dir);
88
- const srcPath = path.resolve(baseDir, "." + decodedPath);
89
- if (srcPath.startsWith(baseDir + path.sep) && fs.existsSync(srcPath) && fs.statSync(srcPath).isFile()) {
90
- return new Response(Bun.file(srcPath));
106
+ else {
107
+ throw err;
91
108
  }
92
- return new Response("Not found", { status: 404 });
93
- },
94
- websocket: {
95
- message() { },
96
- open(ws) { ws.subscribe("livereload"); }
97
109
  }
98
- });
99
- console.log(`Dev server listening on http://localhost:3000`);
110
+ }
111
+ if (!server) {
112
+ console.error(`Could not find an open port between ${basePort} and ${maxPort}.`);
113
+ process.exit(1);
114
+ }
100
115
  }
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"AAGA,wBAAsB,OAAO,kBA6F5B"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"AAIA,wBAAsB,OAAO,kBA8F5B"}
package/dist/cli/init.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
+ import { fileURLToPath } from "url";
3
4
  export async function runInit() {
4
5
  console.log("Initializing md project structure...");
5
6
  const dirs = [
@@ -71,7 +72,8 @@ export const firstLesson = lesson("First Lesson", { contentBase: import.meta.dir
71
72
  let mrMdVersion = "latest";
72
73
  try {
73
74
  // Find mr-md's own package.json to get its version
74
- const __dirname = path.dirname(new URL(import.meta.url).pathname);
75
+ const __filename = fileURLToPath(import.meta.url);
76
+ const __dirname = path.dirname(__filename);
75
77
  const ownPkgPath = path.resolve(__dirname, "../../package.json");
76
78
  const ownPkg = JSON.parse(fs.readFileSync(ownPkgPath, "utf-8"));
77
79
  mrMdVersion = ownPkg.version;
package/dist/cli.js CHANGED
File without changes
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/renderer/utils.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,WAAW,OAAO;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,GAAG,YAAY,CAAC;CACpD;AAID,iBAAS,cAAc,CACtB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,YAAY,EACrB,YAAY,GAAE,IAAI,GAAG,IAAI,GAAG,MAAM,GAAG,MAAe,GAClD,MAAM,CA0DR;AAED,iBAAS,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,MAAM,CAuDnE;AAED,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,CAAC"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/renderer/utils.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,WAAW,OAAO;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,GAAG,YAAY,CAAC;CACpD;AAID,iBAAS,cAAc,CACtB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,YAAY,EACrB,YAAY,GAAE,IAAI,GAAG,IAAI,GAAG,MAAM,GAAG,MAAe,GAClD,MAAM,CA0DR;AAED,iBAAS,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,MAAM,CAyDnE;AAED,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,CAAC"}
@@ -28,14 +28,14 @@ function resolveContent(src, options, expectedType = "text") {
28
28
  }
29
29
  }
30
30
  const baseDir = path.resolve(options.contentBase ?? ".");
31
- if (!filePath.startsWith(baseDir) && options.strict !== false) {
31
+ if (!filePath.startsWith(baseDir + path.sep) && filePath !== baseDir && options.strict !== false) {
32
32
  throw new Error(`Security Error: Path traversal attempt outside contentBase: ${filePath}`);
33
33
  }
34
34
  if (fs.existsSync(filePath)) {
35
35
  const stat = fs.statSync(filePath);
36
36
  if (stat.isFile()) {
37
37
  if (expectedType === "js" && (filePath.endsWith(".js") || filePath.endsWith(".ts") || filePath.endsWith(".jsx") || filePath.endsWith(".tsx"))) {
38
- const out = spawnSync("bun", ["build", "--target=browser", filePath]);
38
+ const out = spawnSync(process.execPath, ["build", "--target=browser", filePath]);
39
39
  if (out.status === 0) {
40
40
  return out.stdout.toString("utf-8");
41
41
  }
@@ -92,7 +92,9 @@ function resolveAssetSrc(src, options) {
92
92
  fs.mkdirSync(assetsDir, { recursive: true });
93
93
  }
94
94
  // Create a safe filename with hash to avoid collisions
95
- const hash = crypto.createHash("md5").update(filePath).digest("hex").substring(0, 8);
95
+ // Use relative path for hashing to ensure deterministic builds across different machines
96
+ const relPathForHash = path.relative(options.contentBase ?? process.cwd(), filePath);
97
+ const hash = crypto.createHash("md5").update(relPathForHash).digest("hex").substring(0, 8);
96
98
  const ext = path.extname(filePath);
97
99
  const filename = `${path.basename(filePath, ext)}-${hash}${ext}`;
98
100
  const outPath = path.join(assetsDir, filename);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mr-md",
3
- "version": "2.1.1-beta",
3
+ "version": "2.2.0-beta",
4
4
  "description": "Mr Markdown is an opinionated TypeScript SDK for building interactive learning pages.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -20,7 +20,7 @@
20
20
  "src"
21
21
  ],
22
22
  "scripts": {
23
- "build": "tsc -p tsconfig.build.json && mkdir -p dist/styles dist/client && cp -r src/styles/* dist/styles/ && cp -r src/client/* dist/client/",
23
+ "build": "tsc -p tsconfig.build.json && bun -e \"import {mkdirSync, cpSync} from 'fs'; mkdirSync('dist/styles', {recursive:true}); mkdirSync('dist/client', {recursive:true}); cpSync('src/styles', 'dist/styles', {recursive:true}); cpSync('src/client', 'dist/client', {recursive:true})\"",
24
24
  "prepublishOnly": "bun run build",
25
25
  "build:docs": "bun docs-site/build.ts",
26
26
  "serve:docs": "bunx serve docs-site/out",
package/src/builder.ts CHANGED
@@ -36,32 +36,30 @@ const __filename = fileURLToPath(import.meta.url);
36
36
  const __dirname = path.dirname(__filename);
37
37
 
38
38
  function getCallerDir(): string | undefined {
39
+ const originalPrepareStackTrace = Error.prepareStackTrace;
40
+ Error.prepareStackTrace = (_, stack) => stack;
39
41
  const err = new Error();
40
- const stack = err.stack?.split("\n");
41
- if (!stack || stack.length < 2) return undefined;
42
-
43
- let builderFilePath: string | undefined;
44
-
45
- for (let i = 1; i < stack.length; i++) {
46
- const line = stack[i];
47
- const match = line.match(/\((.*?):\d+:\d+\)/) || line.match(/at (.*?):\d+:\d+/);
48
- if (match) {
49
- let p = match[1];
50
- if (p.startsWith("file://")) {
51
- p = p.replace(/^file:\/\//, "");
52
- }
53
- if (p.startsWith("/") && p[2] === ":") {
54
- p = p.substring(1);
55
- }
56
- if (!builderFilePath) {
57
- builderFilePath = p;
58
- continue;
59
- }
60
- if (p === builderFilePath) {
61
- continue;
62
- }
63
- return path.dirname(p);
42
+ const stack = err.stack as any as NodeJS.CallSite[];
43
+ Error.prepareStackTrace = originalPrepareStackTrace;
44
+
45
+ if (!stack || !Array.isArray(stack)) return undefined;
46
+
47
+ for (let i = 0; i < stack.length; i++) {
48
+ const callSite = stack[i];
49
+ let p = callSite.getFileName();
50
+ if (!p) continue;
51
+
52
+ if (p.startsWith("file://")) {
53
+ p = fileURLToPath(p);
54
+ } else if (p.startsWith("/") && p[2] === ":") {
55
+ p = p.substring(1);
56
+ }
57
+
58
+ const basename = path.basename(p);
59
+ if (basename === "builder.ts" || basename === "builder.js" || basename === "index.ts" || basename === "index.js") {
60
+ continue;
64
61
  }
62
+ return path.dirname(p);
65
63
  }
66
64
  return undefined;
67
65
  }
@@ -405,7 +403,7 @@ export class LessonBuilder {
405
403
  const base = this.options.contentBase ?? process.cwd();
406
404
  const resolved = path.resolve(base, src);
407
405
  const ext = path.extname(resolved);
408
- const configPath = `${resolved.slice(0, -ext.length)}.config.json`;
406
+ const configPath = `${ext.length > 0 ? resolved.slice(0, -ext.length) : resolved}.config.json`;
409
407
  if (fs.existsSync(configPath)) {
410
408
  return JSON.parse(fs.readFileSync(configPath, "utf-8"));
411
409
  }
@@ -532,7 +530,7 @@ export class LessonBuilder {
532
530
  const html = render(lesson, this.options);
533
531
 
534
532
  const outDir = path.resolve(this.options.outDir as string);
535
- const outPath = path.join(outDir, `${this.meta.slug}.html`);
533
+ const outPath = path.join(outDir, "index.html");
536
534
  const outPathDir = path.dirname(outPath);
537
535
 
538
536
  if (!fs.existsSync(outPathDir)) fs.mkdirSync(outPathDir, { recursive: true });
@@ -759,7 +757,7 @@ export class ChapterBuilder {
759
757
  const html = renderChapter(chapterData, this.options);
760
758
 
761
759
  const outDir = path.resolve(this.options.outDir as string);
762
- const outPath = path.join(outDir, `${this.meta.slug}.html`);
760
+ const outPath = path.join(outDir, "index.html");
763
761
  const outPathDir = path.dirname(outPath);
764
762
 
765
763
  if (!fs.existsSync(outPathDir)) fs.mkdirSync(outPathDir, { recursive: true });
package/src/cli/dev.ts CHANGED
@@ -1,28 +1,36 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
- import { exec } from "child_process";
4
3
 
5
4
  declare const Bun: any;
6
5
 
7
- export async function runDev(args: string[]) {
6
+ export function runDev(args: string[]) {
8
7
  const dir = args[0] || ".";
9
- const outDir = path.resolve(process.cwd(), dir, "out");
8
+ let outDir = path.resolve(process.cwd(), dir, "out");
10
9
 
11
10
  console.log(`Starting dev server for directory: ${dir}`);
12
11
 
13
12
  let server: any;
13
+ let currentBuild: any = null;
14
14
 
15
15
  const rebuild = () => {
16
+ if (currentBuild) {
17
+ currentBuild.kill();
18
+ }
16
19
  console.log("Rebuilding...");
17
- const entryPoints = ["chapter.ts", "index.ts", "lesson.ts"];
20
+ const entryPoints = ["chapter.ts", "index.ts", "lesson.ts", "chapters/01-chapter/chapter.ts"];
18
21
  for (const entry of entryPoints) {
19
22
  const entryPath = path.join(dir, entry);
20
23
  if (fs.existsSync(entryPath)) {
21
- exec(`NODE_ENV=development bun "${entryPath}"`, (err, stdout, stderr) => {
22
- if (err) console.error("Build failed:", stderr);
23
- else {
24
- console.log("Build successful.");
25
- server?.publish("livereload", "reload");
24
+ outDir = path.resolve(process.cwd(), path.dirname(entryPath), "out");
25
+ currentBuild = Bun.spawn([process.execPath, entryPath], {
26
+ env: { ...process.env, NODE_ENV: "development" },
27
+ onExit(proc: any, exitCode: number, signalCode: number, error: string) {
28
+ if (exitCode === 0) {
29
+ console.log("Build successful.");
30
+ server?.publish("livereload", "reload");
31
+ } else if (exitCode !== null) {
32
+ console.error(`Build failed with exit code ${exitCode}`);
33
+ }
26
34
  }
27
35
  });
28
36
  return;
@@ -36,7 +44,7 @@ export async function runDev(args: string[]) {
36
44
  if (fs.existsSync(dir)) {
37
45
  let timeout: NodeJS.Timeout;
38
46
  fs.watch(dir, { recursive: true }, (eventType, filename) => {
39
- if (!filename || filename.includes("out/") || filename.includes(".git/")) return;
47
+ if (!filename || filename.includes("out/") || filename.includes("out\\") || filename.includes(".git/") || filename.includes(".git\\")) return;
40
48
 
41
49
  clearTimeout(timeout);
42
50
  timeout = setTimeout(() => {
@@ -47,65 +55,76 @@ export async function runDev(args: string[]) {
47
55
  console.log(`Watching ${dir} for changes...`);
48
56
  }
49
57
 
50
- server = Bun.serve({
51
- port: 3000,
52
- async fetch(req: any, srv: any) {
53
- if (srv.upgrade(req)) return;
58
+ const basePort = process.env.PORT ? parseInt(process.env.PORT) : 3000;
59
+ let port = basePort;
60
+ const maxPort = basePort + 10;
61
+
62
+ const fetchHandler = async (req: any, srv: any) => {
63
+ if (srv.upgrade(req)) return;
54
64
 
55
- const url = new URL(req.url);
56
- const decodedPath = decodeURIComponent(url.pathname);
57
- let filePath = path.resolve(outDir, "." + decodedPath);
58
-
59
- if (filePath.endsWith(path.sep)) {
60
- const files = fs.existsSync(outDir) ? fs.readdirSync(outDir) : [];
61
- const htmlFiles = files.filter(f => f.endsWith(".html"));
62
- if (htmlFiles.includes("index.html")) {
63
- filePath = path.join(outDir, "index.html");
65
+ const url = new URL(req.url);
66
+ const decodedPath = decodeURIComponent(url.pathname);
67
+ let filePath = path.resolve(outDir, "." + decodedPath);
68
+
69
+ if (decodedPath.endsWith("/")) {
70
+ filePath = path.join(outDir, "index.html");
71
+ }
72
+
73
+ if (!filePath.startsWith(outDir + path.sep) && filePath !== outDir) {
74
+ return new Response("Forbidden", { status: 403 });
75
+ }
76
+
77
+ if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
78
+ if (filePath.endsWith(".html")) {
79
+ const file = Bun.file(filePath);
80
+ let text = await file.text();
81
+
82
+ const script = `<script>
83
+ const ws = new WebSocket(\`ws://\${location.host}/\`);
84
+ ws.onmessage = (e) => { if (e.data === "reload") location.reload(); };
85
+ </script>`;
86
+
87
+ const bodyRegex = /<\/body>/i;
88
+ const match = text.match(bodyRegex);
89
+ if (match && match.index !== undefined) {
90
+ text = text.slice(0, match.index) + script + text.slice(match.index);
64
91
  } else {
65
- const chapterFile = htmlFiles.find(f => f.includes("chapter"));
66
- if (chapterFile) {
67
- filePath = path.join(outDir, chapterFile);
68
- } else if (htmlFiles.length > 0) {
69
- filePath = path.join(outDir, htmlFiles[0]);
70
- } else {
71
- filePath = path.join(outDir, "index.html");
72
- }
92
+ text += script;
73
93
  }
94
+ return new Response(text, { headers: { "Content-Type": "text/html" } });
74
95
  }
96
+ return new Response(Bun.file(filePath));
97
+ }
75
98
 
76
- if (!filePath.startsWith(outDir + path.sep) && filePath !== outDir) {
77
- return new Response("Forbidden", { status: 403 });
78
- }
99
+ return new Response("Not found", { status: 404 });
100
+ };
79
101
 
80
- if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
81
- if (filePath.endsWith(".html")) {
82
- const file = Bun.file(filePath);
83
- let text = await file.text();
84
- const lastBodyIndex = text.lastIndexOf("</body>");
85
- if (lastBodyIndex !== -1) {
86
- text = text.slice(0, lastBodyIndex) + `<script>
87
- const ws = new WebSocket("ws://localhost:3000/");
88
- ws.onmessage = (e) => { if (e.data === "reload") location.reload(); };
89
- </script></body>` + text.slice(lastBodyIndex + 7);
90
- }
91
- return new Response(text, { headers: { "Content-Type": "text/html" } });
92
- }
93
- return new Response(Bun.file(filePath));
94
- }
102
+ const wsHandler = {
103
+ message() {},
104
+ open(ws: any) { ws.subscribe("livereload"); }
105
+ };
95
106
 
96
- const baseDir = path.resolve(process.cwd(), dir);
97
- const srcPath = path.resolve(baseDir, "." + decodedPath);
98
- if (srcPath.startsWith(baseDir + path.sep) && fs.existsSync(srcPath) && fs.statSync(srcPath).isFile()) {
99
- return new Response(Bun.file(srcPath));
107
+ while (port <= maxPort) {
108
+ try {
109
+ server = Bun.serve({
110
+ port,
111
+ fetch: fetchHandler,
112
+ websocket: wsHandler
113
+ });
114
+ console.log(`Dev server listening on http://localhost:${server.port}`);
115
+ break;
116
+ } catch (err: any) {
117
+ if (err.code === 'EADDRINUSE') {
118
+ console.warn(`Port ${port} is in use, trying ${port + 1}...`);
119
+ port++;
120
+ } else {
121
+ throw err;
100
122
  }
101
-
102
- return new Response("Not found", { status: 404 });
103
- },
104
- websocket: {
105
- message() {},
106
- open(ws: any) { ws.subscribe("livereload"); }
107
123
  }
108
- });
109
-
110
- console.log(`Dev server listening on http://localhost:3000`);
124
+ }
125
+
126
+ if (!server) {
127
+ console.error(`Could not find an open port between ${basePort} and ${maxPort}.`);
128
+ process.exit(1);
129
+ }
111
130
  }
package/src/cli/init.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
+ import { fileURLToPath } from "url";
3
4
 
4
5
  export async function runInit() {
5
6
  console.log("Initializing md project structure...");
@@ -77,7 +78,8 @@ export const firstLesson = lesson("First Lesson", { contentBase: import.meta.dir
77
78
  let mrMdVersion = "latest";
78
79
  try {
79
80
  // Find mr-md's own package.json to get its version
80
- const __dirname = path.dirname(new URL(import.meta.url).pathname);
81
+ const __filename = fileURLToPath(import.meta.url);
82
+ const __dirname = path.dirname(__filename);
81
83
  const ownPkgPath = path.resolve(__dirname, "../../package.json");
82
84
  const ownPkg = JSON.parse(fs.readFileSync(ownPkgPath, "utf-8"));
83
85
  mrMdVersion = ownPkg.version;
@@ -46,7 +46,7 @@ function resolveContent(
46
46
  }
47
47
 
48
48
  const baseDir = path.resolve(options.contentBase ?? ".");
49
- if (!filePath.startsWith(baseDir) && options.strict !== false) {
49
+ if (!filePath.startsWith(baseDir + path.sep) && filePath !== baseDir && options.strict !== false) {
50
50
  throw new Error(`Security Error: Path traversal attempt outside contentBase: ${filePath}`);
51
51
  }
52
52
 
@@ -54,7 +54,7 @@ function resolveContent(
54
54
  const stat = fs.statSync(filePath);
55
55
  if (stat.isFile()) {
56
56
  if (expectedType === "js" && (filePath.endsWith(".js") || filePath.endsWith(".ts") || filePath.endsWith(".jsx") || filePath.endsWith(".tsx"))) {
57
- const out = spawnSync("bun", ["build", "--target=browser", filePath]);
57
+ const out = spawnSync(process.execPath, ["build", "--target=browser", filePath]);
58
58
  if (out.status === 0) {
59
59
  return out.stdout.toString("utf-8");
60
60
  } else {
@@ -121,7 +121,9 @@ function resolveAssetSrc(src: string, options: BuildOptions): string {
121
121
  }
122
122
 
123
123
  // Create a safe filename with hash to avoid collisions
124
- const hash = crypto.createHash("md5").update(filePath).digest("hex").substring(0, 8);
124
+ // Use relative path for hashing to ensure deterministic builds across different machines
125
+ const relPathForHash = path.relative(options.contentBase ?? process.cwd(), filePath);
126
+ const hash = crypto.createHash("md5").update(relPathForHash).digest("hex").substring(0, 8);
125
127
  const ext = path.extname(filePath);
126
128
  const filename = `${path.basename(filePath, ext)}-${hash}${ext}`;
127
129
  const outPath = path.join(assetsDir, filename);