@moku-labs/web 1.5.1 → 1.5.3

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/index.cjs CHANGED
@@ -3992,10 +3992,31 @@ function wrap(body) {
3992
3992
  return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>404 — Not Found</title></head><body>${body}</body></html>`;
3993
3993
  }
3994
3994
  /**
3995
+ * Resolve the 404 page HTML from `config.notFound`. Precedence: `path` (a
3996
+ * complete page file, read verbatim) > `body` (a fragment, wrapped in the
3997
+ * minimal shell) > the built-in default.
3998
+ *
3999
+ * @param notFound - The `config.notFound` value (already known to be truthy).
4000
+ * @returns The complete HTML document to write.
4001
+ * @example
4002
+ * ```ts
4003
+ * const html = await resolveHtml({ path: "src/404.html" });
4004
+ * ```
4005
+ */
4006
+ async function resolveHtml(notFound) {
4007
+ if (typeof notFound === "object" && notFound.path) try {
4008
+ return await (0, node_fs_promises.readFile)(notFound.path, "utf8");
4009
+ } catch (error) {
4010
+ throw new Error(`build:not-found — could not read notFound.path "${notFound.path}"`, { cause: error });
4011
+ }
4012
+ return wrap(typeof notFound === "object" && notFound.body ? notFound.body : DEFAULT_BODY);
4013
+ }
4014
+ /**
3995
4015
  * Emits `outDir/404.html`. When `config.notFound` is `true`, writes the built-in
3996
- * default page; when it is `{ body }`, writes the supplied HTML body content
3997
- * verbatim inside the document shell. No-op (returns `null`) when `notFound` is
3998
- * false/unset.
4016
+ * default page; `{ body }` writes the supplied HTML body content inside the
4017
+ * minimal document shell; `{ path }` writes the referenced HTML page file
4018
+ * verbatim (the app owns the whole document). No-op (returns `null`) when
4019
+ * `notFound` is false/unset.
3999
4020
  *
4000
4021
  * @param ctx - Plugin context (provides `config`, `log`).
4001
4022
  * @returns The written file path, or `null` when disabled.
@@ -4010,10 +4031,10 @@ async function generateNotFound(ctx) {
4010
4031
  ctx.log.debug("build:not-found", { skipped: true });
4011
4032
  return null;
4012
4033
  }
4013
- const body = typeof notFound === "object" && notFound.body ? notFound.body : DEFAULT_BODY;
4034
+ const html = await resolveHtml(notFound);
4014
4035
  await (0, node_fs_promises.mkdir)(outDir, { recursive: true });
4015
4036
  const file = node_path$1.default.join(outDir, "404.html");
4016
- await (0, node_fs_promises.writeFile)(file, wrap(body), "utf8");
4037
+ await (0, node_fs_promises.writeFile)(file, html, "utf8");
4017
4038
  ctx.log.debug("build:not-found", { path: file });
4018
4039
  return { path: file };
4019
4040
  }
@@ -4390,9 +4411,10 @@ async function generateOgImages(ctx, options = {}) {
4390
4411
  fonts,
4391
4412
  ...renderHook
4392
4413
  });
4414
+ const siteCardRender = typeof config.defaultCard === "function" ? config.defaultCard : defaultSiteCard;
4393
4415
  const renderSitePng = options.renderPng ?? makeDefaultRenderer({
4394
4416
  fonts,
4395
- render: defaultSiteCard
4417
+ render: siteCardRender
4396
4418
  });
4397
4419
  const siteName = resolveSiteName(ctx);
4398
4420
  const defaultLocale = ctx.require(i18nPlugin).defaultLocale();
@@ -4418,7 +4440,7 @@ async function generateOgImages(ctx, options = {}) {
4418
4440
  outDir
4419
4441
  }, tally);
4420
4442
  })));
4421
- const defaultCard = config.defaultCard === true;
4443
+ const defaultCard = config.defaultCard === true || typeof config.defaultCard === "function";
4422
4444
  if (defaultCard) {
4423
4445
  const png = await renderSitePng({
4424
4446
  title: siteName,
package/dist/index.d.cts CHANGED
@@ -1633,12 +1633,16 @@ interface OgImageConfig {
1633
1633
  /** Explicit named fonts loaded once per build (overrides the first-file scan). */
1634
1634
  fonts?: OgFont[];
1635
1635
  /**
1636
- * When `true`, also render a single SITE-LEVEL default card to `<outDir>/og-default.png`
1637
- * a generic site name + description on a dark background, using the same loaded fonts (the
1638
- * per-article `render` hook is NOT applied). Point `head.defaultOgImage` at `"/og-default.png"`
1639
- * to use it as the og:image fallback for non-article pages. Default `false`.
1636
+ * Also render a single SITE-LEVEL default card to `<outDir>/og-default.png`, used (via
1637
+ * `head.defaultOgImage: "/og-default.png"`) as the og:image fallback for non-article pages.
1638
+ * Rendered ONCE with the same loaded fonts; the per-article `render` hook is NOT applied.
1639
+ *
1640
+ * - `true` → the built-in generic card (site name over its description on a dark background).
1641
+ * - a render function → your OWN card, e.g. `defaultCard: MySiteCard` (a `(input) => VNode`,
1642
+ * the same shape as `render`); `input.siteName`/`input.description` carry the site identity.
1643
+ * - `false`/omitted → no card (default).
1640
1644
  */
1641
- defaultCard?: boolean;
1645
+ defaultCard?: boolean | ((input: RichOgInput) => import("preact").VNode);
1642
1646
  }
1643
1647
  /**
1644
1648
  * Public configuration for the `build` plugin. Flags give opt-in granularity over
@@ -1659,12 +1663,18 @@ type Config$3 = {
1659
1663
  injectAssets?: boolean; /** Directory copied verbatim into `outDir` (skipped silently if absent). Default `"public"`. */
1660
1664
  publicDir?: string;
1661
1665
  /**
1662
- * Emit `outDir/404.html`. `true` for the built-in default page, or
1663
- * `{ body }` to supply the page's literal HTML body content (written into the
1664
- * 404 page verbatim). Default `false`.
1666
+ * Emit `outDir/404.html`. One of:
1667
+ * - `true` the built-in default page.
1668
+ * - `{ body }` — literal HTML body content, wrapped in a minimal document shell.
1669
+ * - `{ path }` — path to a complete HTML page file (resolved from the project
1670
+ * root), written out VERBATIM so the app owns the whole document (its own
1671
+ * `<head>`, asset links, and body).
1672
+ *
1673
+ * `path` takes precedence over `body` when both are set. Default `false`.
1665
1674
  */
1666
1675
  notFound?: boolean | {
1667
1676
  body?: string;
1677
+ path?: string;
1668
1678
  }; /** Emit per-path i18n bare-path redirect HTML pages. Default `false`. */
1669
1679
  localeRedirects?: boolean; /** Authoritative client bundle entry path (overrides the conventional scan). */
1670
1680
  clientEntry?: string;
package/dist/index.d.mts CHANGED
@@ -1633,12 +1633,16 @@ interface OgImageConfig {
1633
1633
  /** Explicit named fonts loaded once per build (overrides the first-file scan). */
1634
1634
  fonts?: OgFont[];
1635
1635
  /**
1636
- * When `true`, also render a single SITE-LEVEL default card to `<outDir>/og-default.png`
1637
- * a generic site name + description on a dark background, using the same loaded fonts (the
1638
- * per-article `render` hook is NOT applied). Point `head.defaultOgImage` at `"/og-default.png"`
1639
- * to use it as the og:image fallback for non-article pages. Default `false`.
1636
+ * Also render a single SITE-LEVEL default card to `<outDir>/og-default.png`, used (via
1637
+ * `head.defaultOgImage: "/og-default.png"`) as the og:image fallback for non-article pages.
1638
+ * Rendered ONCE with the same loaded fonts; the per-article `render` hook is NOT applied.
1639
+ *
1640
+ * - `true` → the built-in generic card (site name over its description on a dark background).
1641
+ * - a render function → your OWN card, e.g. `defaultCard: MySiteCard` (a `(input) => VNode`,
1642
+ * the same shape as `render`); `input.siteName`/`input.description` carry the site identity.
1643
+ * - `false`/omitted → no card (default).
1640
1644
  */
1641
- defaultCard?: boolean;
1645
+ defaultCard?: boolean | ((input: RichOgInput) => import("preact").VNode);
1642
1646
  }
1643
1647
  /**
1644
1648
  * Public configuration for the `build` plugin. Flags give opt-in granularity over
@@ -1659,12 +1663,18 @@ type Config$3 = {
1659
1663
  injectAssets?: boolean; /** Directory copied verbatim into `outDir` (skipped silently if absent). Default `"public"`. */
1660
1664
  publicDir?: string;
1661
1665
  /**
1662
- * Emit `outDir/404.html`. `true` for the built-in default page, or
1663
- * `{ body }` to supply the page's literal HTML body content (written into the
1664
- * 404 page verbatim). Default `false`.
1666
+ * Emit `outDir/404.html`. One of:
1667
+ * - `true` the built-in default page.
1668
+ * - `{ body }` — literal HTML body content, wrapped in a minimal document shell.
1669
+ * - `{ path }` — path to a complete HTML page file (resolved from the project
1670
+ * root), written out VERBATIM so the app owns the whole document (its own
1671
+ * `<head>`, asset links, and body).
1672
+ *
1673
+ * `path` takes precedence over `body` when both are set. Default `false`.
1665
1674
  */
1666
1675
  notFound?: boolean | {
1667
1676
  body?: string;
1677
+ path?: string;
1668
1678
  }; /** Emit per-path i18n bare-path redirect HTML pages. Default `false`. */
1669
1679
  localeRedirects?: boolean; /** Authoritative client bundle entry path (overrides the conventional scan). */
1670
1680
  clientEntry?: string;
package/dist/index.mjs CHANGED
@@ -3979,10 +3979,31 @@ function wrap(body) {
3979
3979
  return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>404 — Not Found</title></head><body>${body}</body></html>`;
3980
3980
  }
3981
3981
  /**
3982
+ * Resolve the 404 page HTML from `config.notFound`. Precedence: `path` (a
3983
+ * complete page file, read verbatim) > `body` (a fragment, wrapped in the
3984
+ * minimal shell) > the built-in default.
3985
+ *
3986
+ * @param notFound - The `config.notFound` value (already known to be truthy).
3987
+ * @returns The complete HTML document to write.
3988
+ * @example
3989
+ * ```ts
3990
+ * const html = await resolveHtml({ path: "src/404.html" });
3991
+ * ```
3992
+ */
3993
+ async function resolveHtml(notFound) {
3994
+ if (typeof notFound === "object" && notFound.path) try {
3995
+ return await readFile(notFound.path, "utf8");
3996
+ } catch (error) {
3997
+ throw new Error(`build:not-found — could not read notFound.path "${notFound.path}"`, { cause: error });
3998
+ }
3999
+ return wrap(typeof notFound === "object" && notFound.body ? notFound.body : DEFAULT_BODY);
4000
+ }
4001
+ /**
3982
4002
  * Emits `outDir/404.html`. When `config.notFound` is `true`, writes the built-in
3983
- * default page; when it is `{ body }`, writes the supplied HTML body content
3984
- * verbatim inside the document shell. No-op (returns `null`) when `notFound` is
3985
- * false/unset.
4003
+ * default page; `{ body }` writes the supplied HTML body content inside the
4004
+ * minimal document shell; `{ path }` writes the referenced HTML page file
4005
+ * verbatim (the app owns the whole document). No-op (returns `null`) when
4006
+ * `notFound` is false/unset.
3986
4007
  *
3987
4008
  * @param ctx - Plugin context (provides `config`, `log`).
3988
4009
  * @returns The written file path, or `null` when disabled.
@@ -3997,10 +4018,10 @@ async function generateNotFound(ctx) {
3997
4018
  ctx.log.debug("build:not-found", { skipped: true });
3998
4019
  return null;
3999
4020
  }
4000
- const body = typeof notFound === "object" && notFound.body ? notFound.body : DEFAULT_BODY;
4021
+ const html = await resolveHtml(notFound);
4001
4022
  await mkdir(outDir, { recursive: true });
4002
4023
  const file = path.join(outDir, "404.html");
4003
- await writeFile(file, wrap(body), "utf8");
4024
+ await writeFile(file, html, "utf8");
4004
4025
  ctx.log.debug("build:not-found", { path: file });
4005
4026
  return { path: file };
4006
4027
  }
@@ -4377,9 +4398,10 @@ async function generateOgImages(ctx, options = {}) {
4377
4398
  fonts,
4378
4399
  ...renderHook
4379
4400
  });
4401
+ const siteCardRender = typeof config.defaultCard === "function" ? config.defaultCard : defaultSiteCard;
4380
4402
  const renderSitePng = options.renderPng ?? makeDefaultRenderer({
4381
4403
  fonts,
4382
- render: defaultSiteCard
4404
+ render: siteCardRender
4383
4405
  });
4384
4406
  const siteName = resolveSiteName(ctx);
4385
4407
  const defaultLocale = ctx.require(i18nPlugin).defaultLocale();
@@ -4405,7 +4427,7 @@ async function generateOgImages(ctx, options = {}) {
4405
4427
  outDir
4406
4428
  }, tally);
4407
4429
  })));
4408
- const defaultCard = config.defaultCard === true;
4430
+ const defaultCard = config.defaultCard === true || typeof config.defaultCard === "function";
4409
4431
  if (defaultCard) {
4410
4432
  const png = await renderSitePng({
4411
4433
  title: siteName,
package/package.json CHANGED
@@ -113,5 +113,5 @@
113
113
  "test:cli-e2e": "bun test src/plugins/cli/__tests__/e2e/",
114
114
  "test:coverage": "vitest run --project unit --project integration --coverage"
115
115
  },
116
- "version": "1.5.1"
116
+ "version": "1.5.3"
117
117
  }