@moku-labs/web 1.5.0 → 1.5.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/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,10 @@ async function generateOgImages(ctx, options = {}) {
4333
4390
  fonts,
4334
4391
  ...renderHook
4335
4392
  });
4393
+ const renderSitePng = options.renderPng ?? makeDefaultRenderer({
4394
+ fonts,
4395
+ render: defaultSiteCard
4396
+ });
4336
4397
  const siteName = resolveSiteName(ctx);
4337
4398
  const defaultLocale = ctx.require(i18nPlugin).defaultLocale();
4338
4399
  const articles = selectArticles(readCachedContent(ctx), defaultLocale);
@@ -4357,15 +4418,31 @@ async function generateOgImages(ctx, options = {}) {
4357
4418
  outDir
4358
4419
  }, tally);
4359
4420
  })));
4421
+ const defaultCard = config.defaultCard === true;
4422
+ if (defaultCard) {
4423
+ const png = await renderSitePng({
4424
+ title: siteName,
4425
+ description: resolveSiteDescription(ctx),
4426
+ date: "",
4427
+ tags: [],
4428
+ locale: defaultLocale,
4429
+ siteName,
4430
+ size
4431
+ });
4432
+ await (0, node_fs_promises.mkdir)(ctx.config.outDir, { recursive: true });
4433
+ await (0, node_fs_promises.writeFile)(node_path.default.join(ctx.config.outDir, "og-default.png"), png);
4434
+ }
4360
4435
  await persistDiskCache(ctx.config.outDir, cache);
4361
4436
  ctx.log.debug("build:og-images", {
4362
4437
  rendered: tally.rendered,
4363
- skipped: tally.skipped
4438
+ skipped: tally.skipped,
4439
+ defaultCard
4364
4440
  });
4365
4441
  return {
4366
4442
  rendered: tally.rendered,
4367
4443
  skipped: tally.skipped,
4368
- peakConcurrency: tally.peakConcurrency
4444
+ peakConcurrency: tally.peakConcurrency,
4445
+ defaultCard
4369
4446
  };
4370
4447
  }
4371
4448
  /**
package/dist/index.d.cts CHANGED
@@ -1632,6 +1632,13 @@ 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
+ * 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`.
1640
+ */
1641
+ defaultCard?: boolean;
1635
1642
  }
1636
1643
  /**
1637
1644
  * Public configuration for the `build` plugin. Flags give opt-in granularity over
package/dist/index.d.mts CHANGED
@@ -1632,6 +1632,13 @@ 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
+ * 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`.
1640
+ */
1641
+ defaultCard?: boolean;
1635
1642
  }
1636
1643
  /**
1637
1644
  * 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,10 @@ async function generateOgImages(ctx, options = {}) {
4320
4377
  fonts,
4321
4378
  ...renderHook
4322
4379
  });
4380
+ const renderSitePng = options.renderPng ?? makeDefaultRenderer({
4381
+ fonts,
4382
+ render: defaultSiteCard
4383
+ });
4323
4384
  const siteName = resolveSiteName(ctx);
4324
4385
  const defaultLocale = ctx.require(i18nPlugin).defaultLocale();
4325
4386
  const articles = selectArticles(readCachedContent(ctx), defaultLocale);
@@ -4344,15 +4405,31 @@ async function generateOgImages(ctx, options = {}) {
4344
4405
  outDir
4345
4406
  }, tally);
4346
4407
  })));
4408
+ const defaultCard = config.defaultCard === true;
4409
+ if (defaultCard) {
4410
+ const png = await renderSitePng({
4411
+ title: siteName,
4412
+ description: resolveSiteDescription(ctx),
4413
+ date: "",
4414
+ tags: [],
4415
+ locale: defaultLocale,
4416
+ siteName,
4417
+ size
4418
+ });
4419
+ await mkdir(ctx.config.outDir, { recursive: true });
4420
+ await writeFile(path.join(ctx.config.outDir, "og-default.png"), png);
4421
+ }
4347
4422
  await persistDiskCache(ctx.config.outDir, cache);
4348
4423
  ctx.log.debug("build:og-images", {
4349
4424
  rendered: tally.rendered,
4350
- skipped: tally.skipped
4425
+ skipped: tally.skipped,
4426
+ defaultCard
4351
4427
  });
4352
4428
  return {
4353
4429
  rendered: tally.rendered,
4354
4430
  skipped: tally.skipped,
4355
- peakConcurrency: tally.peakConcurrency
4431
+ peakConcurrency: tally.peakConcurrency,
4432
+ defaultCard
4356
4433
  };
4357
4434
  }
4358
4435
  /**
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.1"
117
117
  }