mr-md 1.0.0 → 1.0.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/dist/builder.d.ts CHANGED
@@ -4,7 +4,7 @@ export declare class LessonBuilder {
4
4
  private blocks;
5
5
  private options;
6
6
  private _rawOptions;
7
- constructor(title: string, options?: BuildOptions);
7
+ constructor(title: string, options?: BuildOptions, callerDir?: string);
8
8
  /**
9
9
  * Sets the URL slug for the generated HTML file.
10
10
  * Automatically generated from the title by default.
@@ -26,7 +26,7 @@ export declare class LessonBuilder {
26
26
  /** Curated production defaults for the generated lesson shell. */
27
27
  preset(preset: NonNullable<BuildOptions["preset"]>): this;
28
28
  /** @internal Used by ChapterBuilder to push down shared config */
29
- _inheritOptions(parentOpts: BuildOptions): void;
29
+ _inheritOptions(parentOpts: BuildOptions, parentRawOpts?: BuildOptions): void;
30
30
  /** @internal Used by ChapterBuilder */
31
31
  _setParentSlug(slug: string): void;
32
32
  /** @internal Used by ChapterBuilder */
@@ -146,7 +146,8 @@ export declare class ChapterBuilder {
146
146
  private meta;
147
147
  private lessonBuilders;
148
148
  private options;
149
- constructor(title: string, options?: BuildOptions);
149
+ private _rawOptions;
150
+ constructor(title: string, options?: BuildOptions, callerDir?: string);
150
151
  slug(slug: string): this;
151
152
  description(text: string): this;
152
153
  status(status: "completed" | "active" | "locked"): this;
@@ -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
@@ -1,24 +1,56 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
3
  import { render, renderChapter } from "./renderer/index.js";
4
+ function getCallerDir() {
5
+ const err = new Error();
6
+ const stack = err.stack?.split("\n");
7
+ if (!stack || stack.length < 2)
8
+ return undefined;
9
+ let builderFilePath;
10
+ for (let i = 1; i < stack.length; i++) {
11
+ const line = stack[i];
12
+ const match = line.match(/\((.*?):\d+:\d+\)/) || line.match(/at (.*?):\d+:\d+/);
13
+ if (match) {
14
+ let p = match[1];
15
+ if (p.startsWith("file://")) {
16
+ p = p.replace(/^file:\/\//, "");
17
+ }
18
+ if (p.startsWith("/") && p[2] === ":") {
19
+ p = p.substring(1); // Handle Windows paths like /C:/
20
+ }
21
+ if (!builderFilePath) {
22
+ builderFilePath = p;
23
+ continue;
24
+ }
25
+ if (p === builderFilePath) {
26
+ continue;
27
+ }
28
+ return path.dirname(p);
29
+ }
30
+ }
31
+ return undefined;
32
+ }
4
33
  // ─── LessonBuilder ────────────────────────────────────────────────────────────
5
34
  export class LessonBuilder {
6
35
  meta;
7
36
  blocks = [];
8
37
  options;
9
38
  _rawOptions;
10
- constructor(title, options = {}) {
39
+ constructor(title, options = {}, callerDir) {
11
40
  this._rawOptions = options;
41
+ let slug = title
42
+ .toLowerCase()
43
+ .replace(/[^a-z0-9]+/g, "-")
44
+ .replace(/(^-|-$)/g, "");
45
+ if (!slug)
46
+ slug = "lesson";
12
47
  this.meta = {
13
48
  title,
14
- slug: title
15
- .toLowerCase()
16
- .replace(/[^a-z0-9]+/g, "-")
17
- .replace(/(^-|-$)/g, ""),
49
+ slug,
18
50
  };
19
51
  this.options = {
20
52
  outDir: options.outDir ?? "./out",
21
- contentBase: options.contentBase ?? ".",
53
+ contentBase: options.contentBase ?? callerDir ?? ".",
22
54
  theme: options.theme ?? "auto",
23
55
  palette: options.palette ?? "ink",
24
56
  strict: options.strict ?? true,
@@ -75,11 +107,11 @@ export class LessonBuilder {
75
107
  return this;
76
108
  }
77
109
  /** @internal Used by ChapterBuilder to push down shared config */
78
- _inheritOptions(parentOpts) {
110
+ _inheritOptions(parentOpts, parentRawOpts) {
79
111
  this.options = {
80
- outDir: this._rawOptions.outDir ?? parentOpts.outDir ?? this.options.outDir,
112
+ outDir: this._rawOptions.outDir ?? (parentRawOpts?.outDir || parentOpts.outDir) ?? this.options.outDir,
81
113
  contentBase: this._rawOptions.contentBase ??
82
- parentOpts.contentBase ??
114
+ parentRawOpts?.contentBase ??
83
115
  this.options.contentBase,
84
116
  theme: this._rawOptions.theme ?? parentOpts.theme ?? this.options.theme,
85
117
  palette: this._rawOptions.palette ?? parentOpts.palette ?? this.options.palette,
@@ -359,7 +391,7 @@ export class LessonBuilder {
359
391
  * @example const l = lesson("Introduction to Kinematics").markdown("intro.md");
360
392
  */
361
393
  export function lesson(title, options = {}) {
362
- return new LessonBuilder(title, options);
394
+ return new LessonBuilder(title, options, getCallerDir());
363
395
  }
364
396
  function normalizeSimulationOptions(opts, legacyHeight, fileConfig = null) {
365
397
  let inline;
@@ -448,17 +480,22 @@ export class ChapterBuilder {
448
480
  meta;
449
481
  lessonBuilders = [];
450
482
  options;
451
- constructor(title, options = {}) {
483
+ _rawOptions;
484
+ constructor(title, options = {}, callerDir) {
485
+ this._rawOptions = options;
486
+ let slug = title
487
+ .toLowerCase()
488
+ .replace(/[^a-z0-9]+/g, "-")
489
+ .replace(/(^-|-$)/g, "");
490
+ if (!slug)
491
+ slug = "chapter";
452
492
  this.meta = {
453
493
  title,
454
- slug: title
455
- .toLowerCase()
456
- .replace(/[^a-z0-9]+/g, "-")
457
- .replace(/(^-|-$)/g, ""),
494
+ slug,
458
495
  };
459
496
  this.options = {
460
497
  outDir: options.outDir ?? "./out",
461
- contentBase: options.contentBase ?? ".",
498
+ contentBase: options.contentBase ?? callerDir ?? ".",
462
499
  theme: options.theme ?? "auto",
463
500
  palette: options.palette ?? "ink",
464
501
  strict: options.strict ?? true,
@@ -484,7 +521,7 @@ export class ChapterBuilder {
484
521
  return this;
485
522
  }
486
523
  lesson(lessonBuilder) {
487
- lessonBuilder._inheritOptions(this.options);
524
+ lessonBuilder._inheritOptions(this.options, this._rawOptions);
488
525
  lessonBuilder._setParentSlug(this.meta.slug);
489
526
  this.lessonBuilders.push(lessonBuilder);
490
527
  return this;
@@ -541,5 +578,5 @@ export class ChapterBuilder {
541
578
  * .build();
542
579
  */
543
580
  export function chapter(title, options = {}) {
544
- return new ChapterBuilder(title, options);
581
+ return new ChapterBuilder(title, options, getCallerDir());
545
582
  }
@@ -1 +1 @@
1
- {"version":3,"file":"blocks.d.ts","sourceRoot":"","sources":["../../src/renderer/blocks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAA0B,MAAM,aAAa,CAAC;AAC/E,OAAO,EACN,WAAW,EAKX,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,KAAK,OAAO,EAAmC,MAAM,YAAY,CAAC;AAG3E,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO3C;AACD,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE3C;AAID,iBAAS,WAAW,CACnB,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,YAAY,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAuBrC;AAED,iBAAS,gBAAgB,CACxB,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,YAAY,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAgQrC;AAuID,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,CAAC"}
1
+ {"version":3,"file":"blocks.d.ts","sourceRoot":"","sources":["../../src/renderer/blocks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAA0B,MAAM,aAAa,CAAC;AAC/E,OAAO,EACN,WAAW,EAKX,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,KAAK,OAAO,EAAmC,MAAM,YAAY,CAAC;AAG3E,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO3C;AACD,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE3C;AAID,iBAAS,WAAW,CACnB,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,YAAY,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAuBrC;AAED,iBAAS,gBAAgB,CACxB,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,YAAY,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAqQrC;AAuID,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,CAAC"}
@@ -85,8 +85,9 @@ function renderBlockInner(block, idx, options) {
85
85
  }
86
86
  case "code": {
87
87
  const raw = resolveContent(block.src, options, "text"); // Could be file or inline
88
+ const isInlineCode = typeof block.src === "string" && (block.src.includes("\n") || block.src.includes(" "));
88
89
  const lang = block.lang ??
89
- (typeof block.src === "string" && block.src.includes(".")
90
+ (typeof block.src === "string" && !isInlineCode && block.src.includes(".")
90
91
  ? (block.src.split(".").pop() ?? "")
91
92
  : "");
92
93
  let highlighted = escHtml(raw);
@@ -106,7 +107,7 @@ function renderBlockInner(block, idx, options) {
106
107
  };
107
108
  }
108
109
  case "simulation": {
109
- const propsJson = JSON.stringify(block.props ?? {});
110
+ const propsJson = escapeScriptJson(block.props ?? {});
110
111
  const simSrc = resolveContent(block.src, options, "js");
111
112
  const simConfig = { js: simSrc, loop: false, dependencies: block.dependencies };
112
113
  return {
@@ -200,7 +201,11 @@ function renderBlockInner(block, idx, options) {
200
201
  let quiz = { questions: [] };
201
202
  const rawJson = resolveContent(block.src, options, "json");
202
203
  try {
203
- quiz = JSON.parse(rawJson);
204
+ const trimmed = rawJson.trim();
205
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
206
+ throw new Error("Quiz file not found or invalid JSON format");
207
+ }
208
+ quiz = JSON.parse(trimmed);
204
209
  }
205
210
  catch (e) {
206
211
  const msg = e instanceof Error ? e.message : String(e);
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/renderer/utils.ts"],"names":[],"mappings":"AAEA,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,CAAC;CACrC;AAID,iBAAS,cAAc,CACtB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,YAAY,EACrB,YAAY,GAAE,IAAI,GAAG,IAAI,GAAG,MAAM,GAAG,MAAe,GAClD,MAAM,CA4BR;AAED,iBAAS,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,MAAM,CAmCnE;AAED,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,CAAC"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/renderer/utils.ts"],"names":[],"mappings":"AAEA,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,CAAC;CACrC;AAID,iBAAS,cAAc,CACtB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,YAAY,EACrB,YAAY,GAAE,IAAI,GAAG,IAAI,GAAG,MAAM,GAAG,MAAe,GAClD,MAAM,CAmCR;AAED,iBAAS,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,MAAM,CAwCnE;AAED,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,CAAC"}
@@ -4,6 +4,12 @@ import * as path from "path";
4
4
  function resolveContent(src, options, expectedType = "text") {
5
5
  if (src.includes("\n"))
6
6
  return src;
7
+ if (/^https?:\/\//.test(src)) {
8
+ if (options.strict !== false) {
9
+ throw new Error(`Remote URLs are not yet supported for content files: ${src}`);
10
+ }
11
+ return src;
12
+ }
7
13
  const isLikelyFilePath = (expectedType !== "text" && src.endsWith(`.${expectedType}`)) ||
8
14
  src.startsWith("/") ||
9
15
  src.startsWith("./") ||
@@ -24,9 +30,14 @@ function resolveContent(src, options, expectedType = "text") {
24
30
  return src;
25
31
  }
26
32
  function resolveAssetSrc(src, options) {
27
- if (/^(https?:|data:|\/)/.test(src))
33
+ if (/^(https?:|data:)/.test(src))
34
+ return src;
35
+ const isWebAbsolute = src.startsWith("/") && !fs.existsSync(src);
36
+ if (isWebAbsolute)
28
37
  return src;
29
- const filePath = path.resolve(options.contentBase ?? ".", src);
38
+ const filePath = path.isAbsolute(src)
39
+ ? src
40
+ : path.resolve(options.contentBase ?? ".", src);
30
41
  if (!fs.existsSync(filePath)) {
31
42
  if (options.strict !== false)
32
43
  throw new Error(`Missing media asset: ${filePath}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mr-md",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Mr Markdown is an opinionated TypeScript SDK for building interactive, single-file learning pages.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/builder.ts CHANGED
@@ -31,6 +31,40 @@ import type {
31
31
  YouTubeOptions,
32
32
  } from "./types.js";
33
33
 
34
+ function getCallerDir(): string | undefined {
35
+ const err = new Error();
36
+ const stack = err.stack?.split("\n");
37
+ if (!stack || stack.length < 2) return undefined;
38
+
39
+ let builderFilePath: string | undefined;
40
+
41
+ for (let i = 1; i < stack.length; i++) {
42
+ const line = stack[i];
43
+ const match = line.match(/\((.*?):\d+:\d+\)/) || line.match(/at (.*?):\d+:\d+/);
44
+ if (match) {
45
+ let p = match[1];
46
+ if (p.startsWith("file://")) {
47
+ p = p.replace(/^file:\/\//, "");
48
+ }
49
+ if (p.startsWith("/") && p[2] === ":") {
50
+ p = p.substring(1); // Handle Windows paths like /C:/
51
+ }
52
+
53
+ if (!builderFilePath) {
54
+ builderFilePath = p;
55
+ continue;
56
+ }
57
+
58
+ if (p === builderFilePath) {
59
+ continue;
60
+ }
61
+
62
+ return path.dirname(p);
63
+ }
64
+ }
65
+ return undefined;
66
+ }
67
+
34
68
  // ─── LessonBuilder ────────────────────────────────────────────────────────────
35
69
 
36
70
  export class LessonBuilder {
@@ -39,18 +73,22 @@ export class LessonBuilder {
39
73
  private options: BuildOptions;
40
74
  private _rawOptions: BuildOptions;
41
75
 
42
- constructor(title: string, options: BuildOptions = {}) {
76
+ constructor(title: string, options: BuildOptions = {}, callerDir?: string) {
43
77
  this._rawOptions = options;
78
+
79
+ let slug = title
80
+ .toLowerCase()
81
+ .replace(/[^a-z0-9]+/g, "-")
82
+ .replace(/(^-|-$)/g, "");
83
+ if (!slug) slug = "lesson";
84
+
44
85
  this.meta = {
45
86
  title,
46
- slug: title
47
- .toLowerCase()
48
- .replace(/[^a-z0-9]+/g, "-")
49
- .replace(/(^-|-$)/g, ""),
87
+ slug,
50
88
  };
51
89
  this.options = {
52
90
  outDir: options.outDir ?? "./out",
53
- contentBase: options.contentBase ?? ".",
91
+ contentBase: options.contentBase ?? callerDir ?? ".",
54
92
  theme: options.theme ?? "auto",
55
93
  palette: options.palette ?? "ink",
56
94
  strict: options.strict ?? true,
@@ -115,13 +153,13 @@ export class LessonBuilder {
115
153
  }
116
154
 
117
155
  /** @internal Used by ChapterBuilder to push down shared config */
118
- _inheritOptions(parentOpts: BuildOptions) {
156
+ _inheritOptions(parentOpts: BuildOptions, parentRawOpts?: BuildOptions) {
119
157
  this.options = {
120
158
  outDir:
121
- this._rawOptions.outDir ?? parentOpts.outDir ?? this.options.outDir,
159
+ this._rawOptions.outDir ?? (parentRawOpts?.outDir || parentOpts.outDir) ?? this.options.outDir,
122
160
  contentBase:
123
161
  this._rawOptions.contentBase ??
124
- parentOpts.contentBase ??
162
+ parentRawOpts?.contentBase ??
125
163
  this.options.contentBase,
126
164
  theme: this._rawOptions.theme ?? parentOpts.theme ?? this.options.theme,
127
165
  palette:
@@ -476,7 +514,7 @@ export function lesson(
476
514
  title: string,
477
515
  options: BuildOptions = {},
478
516
  ): LessonBuilder {
479
- return new LessonBuilder(title, options);
517
+ return new LessonBuilder(title, options, getCallerDir());
480
518
  }
481
519
 
482
520
  function normalizeSimulationOptions(
@@ -594,18 +632,23 @@ export class ChapterBuilder {
594
632
  private meta: ChapterMeta;
595
633
  private lessonBuilders: LessonBuilder[] = [];
596
634
  private options: BuildOptions;
635
+ private _rawOptions: BuildOptions;
636
+
637
+ constructor(title: string, options: BuildOptions = {}, callerDir?: string) {
638
+ this._rawOptions = options;
639
+ let slug = title
640
+ .toLowerCase()
641
+ .replace(/[^a-z0-9]+/g, "-")
642
+ .replace(/(^-|-$)/g, "");
643
+ if (!slug) slug = "chapter";
597
644
 
598
- constructor(title: string, options: BuildOptions = {}) {
599
645
  this.meta = {
600
646
  title,
601
- slug: title
602
- .toLowerCase()
603
- .replace(/[^a-z0-9]+/g, "-")
604
- .replace(/(^-|-$)/g, ""),
647
+ slug,
605
648
  };
606
649
  this.options = {
607
650
  outDir: options.outDir ?? "./out",
608
- contentBase: options.contentBase ?? ".",
651
+ contentBase: options.contentBase ?? callerDir ?? ".",
609
652
  theme: options.theme ?? "auto",
610
653
  palette: options.palette ?? "ink",
611
654
  strict: options.strict ?? true,
@@ -635,7 +678,7 @@ export class ChapterBuilder {
635
678
  }
636
679
 
637
680
  lesson(lessonBuilder: LessonBuilder): this {
638
- lessonBuilder._inheritOptions(this.options);
681
+ lessonBuilder._inheritOptions(this.options, this._rawOptions);
639
682
  lessonBuilder._setParentSlug(this.meta.slug);
640
683
  this.lessonBuilders.push(lessonBuilder);
641
684
  return this;
@@ -704,5 +747,5 @@ export function chapter(
704
747
  title: string,
705
748
  options: BuildOptions = {},
706
749
  ): ChapterBuilder {
707
- return new ChapterBuilder(title, options);
750
+ return new ChapterBuilder(title, options, getCallerDir());
708
751
  }
@@ -114,9 +114,10 @@ function renderBlockInner(
114
114
 
115
115
  case "code": {
116
116
  const raw = resolveContent(block.src, options, "text"); // Could be file or inline
117
+ const isInlineCode = typeof block.src === "string" && (block.src.includes("\n") || block.src.includes(" "));
117
118
  const lang =
118
119
  block.lang ??
119
- (typeof block.src === "string" && block.src.includes(".")
120
+ (typeof block.src === "string" && !isInlineCode && block.src.includes(".")
120
121
  ? (block.src.split(".").pop() ?? "")
121
122
  : "");
122
123
  let highlighted = escHtml(raw);
@@ -136,7 +137,7 @@ function renderBlockInner(
136
137
  }
137
138
 
138
139
  case "simulation": {
139
- const propsJson = JSON.stringify(block.props ?? {});
140
+ const propsJson = escapeScriptJson(block.props ?? {});
140
141
  const simSrc = resolveContent(block.src, options, "js");
141
142
  const simConfig = { js: simSrc, loop: false, dependencies: block.dependencies };
142
143
  return {
@@ -278,7 +279,11 @@ function renderBlockInner(
278
279
  let quiz: QuizFile = { questions: [] };
279
280
  const rawJson = resolveContent(block.src, options, "json");
280
281
  try {
281
- quiz = JSON.parse(rawJson);
282
+ const trimmed = rawJson.trim();
283
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
284
+ throw new Error("Quiz file not found or invalid JSON format");
285
+ }
286
+ quiz = JSON.parse(trimmed);
282
287
  } catch (e) {
283
288
  const msg = e instanceof Error ? e.message : String(e);
284
289
  if (options.strict !== false) {
@@ -17,6 +17,13 @@ function resolveContent(
17
17
  ): string {
18
18
  if (src.includes("\n")) return src;
19
19
 
20
+ if (/^https?:\/\//.test(src)) {
21
+ if (options.strict !== false) {
22
+ throw new Error(`Remote URLs are not yet supported for content files: ${src}`);
23
+ }
24
+ return src;
25
+ }
26
+
20
27
  const isLikelyFilePath =
21
28
  (expectedType !== "text" && src.endsWith(`.${expectedType}`)) ||
22
29
  src.startsWith("/") ||
@@ -45,9 +52,14 @@ function resolveContent(
45
52
  }
46
53
 
47
54
  function resolveAssetSrc(src: string, options: BuildOptions): string {
48
- if (/^(https?:|data:|\/)/.test(src)) return src;
55
+ if (/^(https?:|data:)/.test(src)) return src;
56
+
57
+ const isWebAbsolute = src.startsWith("/") && !fs.existsSync(src);
58
+ if (isWebAbsolute) return src;
49
59
 
50
- const filePath = path.resolve(options.contentBase ?? ".", src);
60
+ const filePath = path.isAbsolute(src)
61
+ ? src
62
+ : path.resolve(options.contentBase ?? ".", src);
51
63
  if (!fs.existsSync(filePath)) {
52
64
  if (options.strict !== false)
53
65
  throw new Error(`Missing media asset: ${filePath}`);