@moku-labs/web 1.5.0 → 1.5.2

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
@@ -4174,6 +4174,45 @@ function defaultCard(input) {
4174
4174
  } }, input.title);
4175
4175
  }
4176
4176
  /**
4177
+ * The built-in SITE-LEVEL default card — the site name over its description on a dark
4178
+ * background, centered. Rendered once (when `ogImage.defaultCard` is true) to `og-default.png`
4179
+ * and used as the `head.defaultOgImage` fallback. Reads `siteName` + `description` from the
4180
+ * {@link RichOgInput}; the per-article `render` hook is deliberately NOT applied here.
4181
+ *
4182
+ * @param input - The rich OG input (only `siteName` + `description` are used).
4183
+ * @returns The Preact `VNode` for the site default card.
4184
+ * @example
4185
+ * ```ts
4186
+ * defaultSiteCard({ ...input, siteName: "My Blog", description: "A dev blog" });
4187
+ * ```
4188
+ */
4189
+ function defaultSiteCard(input) {
4190
+ const children = [(0, preact.h)("div", { style: {
4191
+ display: "flex",
4192
+ fontSize: 72,
4193
+ fontWeight: 700,
4194
+ color: "#ffffff"
4195
+ } }, input.siteName)];
4196
+ if (input.description) children.push((0, preact.h)("div", { style: {
4197
+ display: "flex",
4198
+ marginTop: 28,
4199
+ maxWidth: 900,
4200
+ fontSize: 32,
4201
+ color: "#a1a1aa",
4202
+ textAlign: "center"
4203
+ } }, input.description));
4204
+ return (0, preact.h)("div", { style: {
4205
+ display: "flex",
4206
+ flexDirection: "column",
4207
+ width: "100%",
4208
+ height: "100%",
4209
+ alignItems: "center",
4210
+ justifyContent: "center",
4211
+ padding: 80,
4212
+ background: "#0b0b0c"
4213
+ } }, ...children);
4214
+ }
4215
+ /**
4177
4216
  * The default PNG renderer: a Preact `VNode` (custom `render` hook or the built-in
4178
4217
  * card) is rendered to SVG by Satori, then rasterized to PNG by resvg. Both native
4179
4218
  * deps are imported LAZILY (browser-safe goal); the VNode→Satori-input cast happens
@@ -4263,6 +4302,24 @@ function resolveSiteName(ctx) {
4263
4302
  }
4264
4303
  }
4265
4304
  /**
4305
+ * Resolve the site description via `ctx.require(sitePlugin)`, falling back to `""` when the
4306
+ * site API is unavailable (e.g. unit mocks that omit it). Used by the site default card.
4307
+ *
4308
+ * @param ctx - Plugin context (provides `require`).
4309
+ * @returns The site description, or `""` when the site plugin is not wired.
4310
+ * @example
4311
+ * ```ts
4312
+ * resolveSiteDescription(ctx);
4313
+ * ```
4314
+ */
4315
+ function resolveSiteDescription(ctx) {
4316
+ try {
4317
+ return ctx.require(sitePlugin).description();
4318
+ } catch {
4319
+ return "";
4320
+ }
4321
+ }
4322
+ /**
4266
4323
  * Render (or cache-skip) one article's OG image, mutating {@link RenderTally} in
4267
4324
  * place. A matching cached hash bumps `skipped` and returns early; otherwise the
4268
4325
  * PNG is rasterized to `<outDir>/og/<slug>.png`, the cache entry is updated, and
@@ -4333,6 +4390,11 @@ async function generateOgImages(ctx, options = {}) {
4333
4390
  fonts,
4334
4391
  ...renderHook
4335
4392
  });
4393
+ const siteCardRender = typeof config.defaultCard === "function" ? config.defaultCard : defaultSiteCard;
4394
+ const renderSitePng = options.renderPng ?? makeDefaultRenderer({
4395
+ fonts,
4396
+ render: siteCardRender
4397
+ });
4336
4398
  const siteName = resolveSiteName(ctx);
4337
4399
  const defaultLocale = ctx.require(i18nPlugin).defaultLocale();
4338
4400
  const articles = selectArticles(readCachedContent(ctx), defaultLocale);
@@ -4357,15 +4419,31 @@ async function generateOgImages(ctx, options = {}) {
4357
4419
  outDir
4358
4420
  }, tally);
4359
4421
  })));
4422
+ const defaultCard = config.defaultCard === true || typeof config.defaultCard === "function";
4423
+ if (defaultCard) {
4424
+ const png = await renderSitePng({
4425
+ title: siteName,
4426
+ description: resolveSiteDescription(ctx),
4427
+ date: "",
4428
+ tags: [],
4429
+ locale: defaultLocale,
4430
+ siteName,
4431
+ size
4432
+ });
4433
+ await (0, node_fs_promises.mkdir)(ctx.config.outDir, { recursive: true });
4434
+ await (0, node_fs_promises.writeFile)(node_path.default.join(ctx.config.outDir, "og-default.png"), png);
4435
+ }
4360
4436
  await persistDiskCache(ctx.config.outDir, cache);
4361
4437
  ctx.log.debug("build:og-images", {
4362
4438
  rendered: tally.rendered,
4363
- skipped: tally.skipped
4439
+ skipped: tally.skipped,
4440
+ defaultCard
4364
4441
  });
4365
4442
  return {
4366
4443
  rendered: tally.rendered,
4367
4444
  skipped: tally.skipped,
4368
- peakConcurrency: tally.peakConcurrency
4445
+ peakConcurrency: tally.peakConcurrency,
4446
+ defaultCard
4369
4447
  };
4370
4448
  }
4371
4449
  /**
package/dist/index.d.cts CHANGED
@@ -1632,6 +1632,17 @@ interface OgImageConfig {
1632
1632
  render?(input: RichOgInput): import("preact").VNode;
1633
1633
  /** Explicit named fonts loaded once per build (overrides the first-file scan). */
1634
1634
  fonts?: OgFont[];
1635
+ /**
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).
1644
+ */
1645
+ defaultCard?: boolean | ((input: RichOgInput) => import("preact").VNode);
1635
1646
  }
1636
1647
  /**
1637
1648
  * Public configuration for the `build` plugin. Flags give opt-in granularity over
package/dist/index.d.mts CHANGED
@@ -1632,6 +1632,17 @@ interface OgImageConfig {
1632
1632
  render?(input: RichOgInput): import("preact").VNode;
1633
1633
  /** Explicit named fonts loaded once per build (overrides the first-file scan). */
1634
1634
  fonts?: OgFont[];
1635
+ /**
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).
1644
+ */
1645
+ defaultCard?: boolean | ((input: RichOgInput) => import("preact").VNode);
1635
1646
  }
1636
1647
  /**
1637
1648
  * Public configuration for the `build` plugin. Flags give opt-in granularity over
package/dist/index.mjs CHANGED
@@ -4161,6 +4161,45 @@ function defaultCard(input) {
4161
4161
  } }, input.title);
4162
4162
  }
4163
4163
  /**
4164
+ * The built-in SITE-LEVEL default card — the site name over its description on a dark
4165
+ * background, centered. Rendered once (when `ogImage.defaultCard` is true) to `og-default.png`
4166
+ * and used as the `head.defaultOgImage` fallback. Reads `siteName` + `description` from the
4167
+ * {@link RichOgInput}; the per-article `render` hook is deliberately NOT applied here.
4168
+ *
4169
+ * @param input - The rich OG input (only `siteName` + `description` are used).
4170
+ * @returns The Preact `VNode` for the site default card.
4171
+ * @example
4172
+ * ```ts
4173
+ * defaultSiteCard({ ...input, siteName: "My Blog", description: "A dev blog" });
4174
+ * ```
4175
+ */
4176
+ function defaultSiteCard(input) {
4177
+ const children = [h("div", { style: {
4178
+ display: "flex",
4179
+ fontSize: 72,
4180
+ fontWeight: 700,
4181
+ color: "#ffffff"
4182
+ } }, input.siteName)];
4183
+ if (input.description) children.push(h("div", { style: {
4184
+ display: "flex",
4185
+ marginTop: 28,
4186
+ maxWidth: 900,
4187
+ fontSize: 32,
4188
+ color: "#a1a1aa",
4189
+ textAlign: "center"
4190
+ } }, input.description));
4191
+ return h("div", { style: {
4192
+ display: "flex",
4193
+ flexDirection: "column",
4194
+ width: "100%",
4195
+ height: "100%",
4196
+ alignItems: "center",
4197
+ justifyContent: "center",
4198
+ padding: 80,
4199
+ background: "#0b0b0c"
4200
+ } }, ...children);
4201
+ }
4202
+ /**
4164
4203
  * The default PNG renderer: a Preact `VNode` (custom `render` hook or the built-in
4165
4204
  * card) is rendered to SVG by Satori, then rasterized to PNG by resvg. Both native
4166
4205
  * deps are imported LAZILY (browser-safe goal); the VNode→Satori-input cast happens
@@ -4250,6 +4289,24 @@ function resolveSiteName(ctx) {
4250
4289
  }
4251
4290
  }
4252
4291
  /**
4292
+ * Resolve the site description via `ctx.require(sitePlugin)`, falling back to `""` when the
4293
+ * site API is unavailable (e.g. unit mocks that omit it). Used by the site default card.
4294
+ *
4295
+ * @param ctx - Plugin context (provides `require`).
4296
+ * @returns The site description, or `""` when the site plugin is not wired.
4297
+ * @example
4298
+ * ```ts
4299
+ * resolveSiteDescription(ctx);
4300
+ * ```
4301
+ */
4302
+ function resolveSiteDescription(ctx) {
4303
+ try {
4304
+ return ctx.require(sitePlugin).description();
4305
+ } catch {
4306
+ return "";
4307
+ }
4308
+ }
4309
+ /**
4253
4310
  * Render (or cache-skip) one article's OG image, mutating {@link RenderTally} in
4254
4311
  * place. A matching cached hash bumps `skipped` and returns early; otherwise the
4255
4312
  * PNG is rasterized to `<outDir>/og/<slug>.png`, the cache entry is updated, and
@@ -4320,6 +4377,11 @@ async function generateOgImages(ctx, options = {}) {
4320
4377
  fonts,
4321
4378
  ...renderHook
4322
4379
  });
4380
+ const siteCardRender = typeof config.defaultCard === "function" ? config.defaultCard : defaultSiteCard;
4381
+ const renderSitePng = options.renderPng ?? makeDefaultRenderer({
4382
+ fonts,
4383
+ render: siteCardRender
4384
+ });
4323
4385
  const siteName = resolveSiteName(ctx);
4324
4386
  const defaultLocale = ctx.require(i18nPlugin).defaultLocale();
4325
4387
  const articles = selectArticles(readCachedContent(ctx), defaultLocale);
@@ -4344,15 +4406,31 @@ async function generateOgImages(ctx, options = {}) {
4344
4406
  outDir
4345
4407
  }, tally);
4346
4408
  })));
4409
+ const defaultCard = config.defaultCard === true || typeof config.defaultCard === "function";
4410
+ if (defaultCard) {
4411
+ const png = await renderSitePng({
4412
+ title: siteName,
4413
+ description: resolveSiteDescription(ctx),
4414
+ date: "",
4415
+ tags: [],
4416
+ locale: defaultLocale,
4417
+ siteName,
4418
+ size
4419
+ });
4420
+ await mkdir(ctx.config.outDir, { recursive: true });
4421
+ await writeFile(path.join(ctx.config.outDir, "og-default.png"), png);
4422
+ }
4347
4423
  await persistDiskCache(ctx.config.outDir, cache);
4348
4424
  ctx.log.debug("build:og-images", {
4349
4425
  rendered: tally.rendered,
4350
- skipped: tally.skipped
4426
+ skipped: tally.skipped,
4427
+ defaultCard
4351
4428
  });
4352
4429
  return {
4353
4430
  rendered: tally.rendered,
4354
4431
  skipped: tally.skipped,
4355
- peakConcurrency: tally.peakConcurrency
4432
+ peakConcurrency: tally.peakConcurrency,
4433
+ defaultCard
4356
4434
  };
4357
4435
  }
4358
4436
  /**
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.0"
116
+ "version": "1.5.2"
117
117
  }