@ewanc26/og 0.1.3 → 0.1.5

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
@@ -48,33 +48,82 @@ __export(index_exports, {
48
48
  });
49
49
  module.exports = __toCommonJS(index_exports);
50
50
 
51
+ // ../../node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.8_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js
52
+ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
53
+ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
54
+
51
55
  // src/generate.ts
52
56
  var import_satori = __toESM(require("satori"), 1);
53
57
  var import_resvg_js = require("@resvg/resvg-js");
54
58
 
55
59
  // src/fonts.ts
56
- var import_promises = require("fs/promises");
60
+ var import_promises2 = require("fs/promises");
57
61
  var import_node_fs = require("fs");
62
+ var import_node_path2 = require("path");
63
+ var import_node_url2 = require("url");
64
+
65
+ // src/fonts-data.ts
66
+ var import_promises = require("fs/promises");
58
67
  var import_node_path = require("path");
59
68
  var import_node_url = require("url");
60
69
  var import_meta = {};
61
70
  function getModuleDir() {
62
- if (typeof import_meta !== "undefined" && import_meta.url) {
63
- return (0, import_node_path.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
71
+ if (typeof import_meta !== "undefined" && importMetaUrl) {
72
+ return (0, import_node_path.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl));
64
73
  }
65
74
  if (typeof __dirname !== "undefined") {
66
75
  return __dirname;
67
76
  }
68
77
  return (0, import_node_path.resolve)(process.cwd(), "node_modules/@ewanc26/og/dist");
69
78
  }
79
+ async function loadEmbeddedFonts() {
80
+ const moduleDir = getModuleDir();
81
+ const paths = [
82
+ {
83
+ heading: (0, import_node_path.resolve)(moduleDir, "fonts/Inter-Bold.ttf"),
84
+ body: (0, import_node_path.resolve)(moduleDir, "fonts/Inter-Regular.ttf")
85
+ },
86
+ {
87
+ heading: (0, import_node_path.resolve)(moduleDir, "../fonts/Inter-Bold.ttf"),
88
+ body: (0, import_node_path.resolve)(moduleDir, "../fonts/Inter-Regular.ttf")
89
+ }
90
+ ];
91
+ for (const p of paths) {
92
+ try {
93
+ const [headingBuf, bodyBuf] = await Promise.all([
94
+ (0, import_promises.readFile)(p.heading),
95
+ (0, import_promises.readFile)(p.body)
96
+ ]);
97
+ return {
98
+ heading: headingBuf.buffer.slice(headingBuf.byteOffset, headingBuf.byteOffset + headingBuf.byteLength),
99
+ body: bodyBuf.buffer.slice(bodyBuf.byteOffset, bodyBuf.byteOffset + bodyBuf.byteLength)
100
+ };
101
+ } catch {
102
+ continue;
103
+ }
104
+ }
105
+ return null;
106
+ }
107
+
108
+ // src/fonts.ts
109
+ var import_meta2 = {};
110
+ function getModuleDir2() {
111
+ if (typeof import_meta2 !== "undefined" && importMetaUrl) {
112
+ return (0, import_node_path2.dirname)((0, import_node_url2.fileURLToPath)(importMetaUrl));
113
+ }
114
+ if (typeof __dirname !== "undefined") {
115
+ return __dirname;
116
+ }
117
+ return (0, import_node_path2.resolve)(process.cwd(), "node_modules/@ewanc26/og/dist");
118
+ }
70
119
  function getFontsDir() {
71
120
  const candidates = [
72
121
  // Standard: fonts next to dist
73
- (0, import_node_path.resolve)(getModuleDir(), "../fonts"),
122
+ (0, import_node_path2.resolve)(getModuleDir2(), "../fonts"),
74
123
  // Vercel serverless: fonts inside dist
75
- (0, import_node_path.resolve)(getModuleDir(), "fonts"),
124
+ (0, import_node_path2.resolve)(getModuleDir2(), "fonts"),
76
125
  // Fallback: node_modules path
77
- (0, import_node_path.resolve)(process.cwd(), "node_modules/@ewanc26/og/fonts")
126
+ (0, import_node_path2.resolve)(process.cwd(), "node_modules/@ewanc26/og/fonts")
78
127
  ];
79
128
  for (const dir of candidates) {
80
129
  if ((0, import_node_fs.existsSync)(dir)) {
@@ -85,47 +134,35 @@ function getFontsDir() {
85
134
  }
86
135
  var BUNDLED_FONTS = {
87
136
  get heading() {
88
- return (0, import_node_path.resolve)(getFontsDir(), "Inter-Bold.ttf");
137
+ return (0, import_node_path2.resolve)(getFontsDir(), "Inter-Bold.ttf");
89
138
  },
90
139
  get body() {
91
- return (0, import_node_path.resolve)(getFontsDir(), "Inter-Regular.ttf");
140
+ return (0, import_node_path2.resolve)(getFontsDir(), "Inter-Regular.ttf");
92
141
  }
93
142
  };
94
- var FONT_FALLBACKS = {
95
- heading: "https://github.com/rsms/inter/raw/refs/heads/main/docs/font-files/Inter-Bold.ttf",
96
- body: "https://github.com/rsms/inter/raw/refs/heads/main/docs/font-files/Inter-Regular.ttf"
97
- };
143
+ function toArrayBuffer(buf) {
144
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
145
+ }
98
146
  async function loadFonts(config) {
99
147
  const headingPath = config?.heading ?? BUNDLED_FONTS.heading;
100
148
  const bodyPath = config?.body ?? BUNDLED_FONTS.body;
101
149
  const [heading, body] = await Promise.all([
102
- loadFontFileWithFallback(headingPath, FONT_FALLBACKS.heading),
103
- loadFontFileWithFallback(bodyPath, FONT_FALLBACKS.body)
150
+ loadFontFile(headingPath),
151
+ loadFontFile(bodyPath)
104
152
  ]);
105
153
  return { heading, body };
106
154
  }
107
- async function loadFontFileWithFallback(path, fallbackUrl) {
155
+ async function loadFontFile(source) {
108
156
  try {
109
- return await loadFontFile(path);
157
+ const buffer = await (0, import_promises2.readFile)(source);
158
+ return toArrayBuffer(buffer);
110
159
  } catch (error) {
111
- console.warn(`Failed to load local font at ${path}, trying CDN fallback:`, error);
112
- try {
113
- return await loadFontFile(fallbackUrl);
114
- } catch (fallbackError) {
115
- throw new Error(`Failed to load font from both local path (${path}) and CDN (${fallbackUrl}): ${fallbackError}`);
116
- }
117
- }
118
- }
119
- async function loadFontFile(source) {
120
- if (source.startsWith("http://") || source.startsWith("https://")) {
121
- const response = await fetch(source);
122
- if (!response.ok) {
123
- throw new Error(`Failed to load font from URL: ${source}`);
160
+ const embedded = await loadEmbeddedFonts();
161
+ if (embedded) {
162
+ return source.includes("Bold") ? embedded.heading : embedded.body;
124
163
  }
125
- return response.arrayBuffer();
164
+ throw new Error(`Failed to load font from ${source}`);
126
165
  }
127
- const buffer = await (0, import_promises.readFile)(source);
128
- return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
129
166
  }
130
167
  function createSatoriFonts(fonts) {
131
168
  return [
@@ -270,6 +307,7 @@ function blogTemplate({
270
307
  description,
271
308
  siteName,
272
309
  colors,
310
+ noiseDataUrl,
273
311
  width,
274
312
  height
275
313
  }) {
@@ -277,6 +315,7 @@ function blogTemplate({
277
315
  type: "div",
278
316
  props: {
279
317
  style: {
318
+ position: "relative",
280
319
  display: "flex",
281
320
  flexDirection: "column",
282
321
  alignItems: "center",
@@ -286,51 +325,83 @@ function blogTemplate({
286
325
  backgroundColor: colors.background
287
326
  },
288
327
  children: [
289
- {
290
- type: "h1",
291
- props: {
292
- style: {
293
- fontSize: 64,
294
- fontWeight: 700,
295
- color: colors.text,
296
- letterSpacing: "-0.02em",
297
- margin: 0,
298
- textAlign: "center",
299
- lineHeight: 1.1,
300
- maxWidth: 1e3
301
- },
302
- children: title
303
- }
304
- },
305
- description ? {
306
- type: "p",
328
+ noiseDataUrl ? {
329
+ type: "img",
307
330
  props: {
331
+ src: noiseDataUrl,
332
+ width,
333
+ height,
308
334
  style: {
309
- fontSize: 28,
310
- fontWeight: 400,
311
- color: colors.accent,
312
- marginTop: 28,
313
- marginBottom: 0,
314
- textAlign: "center",
315
- lineHeight: 1.4,
316
- maxWidth: 900
317
- },
318
- children: description
335
+ position: "absolute",
336
+ top: 0,
337
+ left: 0,
338
+ width,
339
+ height
340
+ }
319
341
  }
320
342
  } : null,
321
343
  {
322
- type: "p",
344
+ type: "div",
323
345
  props: {
324
346
  style: {
325
- fontSize: 24,
326
- fontWeight: 400,
327
- color: colors.accent,
328
- marginTop: 56,
329
- marginBottom: 0,
330
- textAlign: "center",
331
- opacity: 0.7
347
+ position: "relative",
348
+ display: "flex",
349
+ flexDirection: "column",
350
+ alignItems: "center",
351
+ justifyContent: "center",
352
+ width,
353
+ height,
354
+ padding: "0 60px"
332
355
  },
333
- children: siteName
356
+ children: [
357
+ {
358
+ type: "h1",
359
+ props: {
360
+ style: {
361
+ fontSize: 64,
362
+ fontWeight: 700,
363
+ color: colors.text,
364
+ letterSpacing: "-0.02em",
365
+ margin: 0,
366
+ textAlign: "center",
367
+ lineHeight: 1.1,
368
+ maxWidth: 1e3
369
+ },
370
+ children: title
371
+ }
372
+ },
373
+ description ? {
374
+ type: "p",
375
+ props: {
376
+ style: {
377
+ fontSize: 28,
378
+ fontWeight: 400,
379
+ color: colors.accent,
380
+ marginTop: 28,
381
+ marginBottom: 0,
382
+ textAlign: "center",
383
+ lineHeight: 1.4,
384
+ maxWidth: 900
385
+ },
386
+ children: description
387
+ }
388
+ } : null,
389
+ {
390
+ type: "p",
391
+ props: {
392
+ style: {
393
+ fontSize: 24,
394
+ fontWeight: 400,
395
+ color: colors.accent,
396
+ marginTop: 56,
397
+ marginBottom: 0,
398
+ textAlign: "center",
399
+ opacity: 0.7
400
+ },
401
+ children: siteName
402
+ }
403
+ }
404
+ ].filter(Boolean)
334
405
  }
335
406
  }
336
407
  ].filter(Boolean)
@@ -345,12 +416,13 @@ function profileTemplate({
345
416
  siteName,
346
417
  image,
347
418
  colors,
419
+ noiseDataUrl,
348
420
  width,
349
421
  height
350
422
  }) {
351
- const children = [];
423
+ const contentChildren = [];
352
424
  if (image) {
353
- children.push({
425
+ contentChildren.push({
354
426
  type: "img",
355
427
  props: {
356
428
  src: image,
@@ -364,7 +436,7 @@ function profileTemplate({
364
436
  }
365
437
  });
366
438
  }
367
- children.push({
439
+ contentChildren.push({
368
440
  type: "h1",
369
441
  props: {
370
442
  style: {
@@ -381,7 +453,7 @@ function profileTemplate({
381
453
  }
382
454
  });
383
455
  if (description) {
384
- children.push({
456
+ contentChildren.push({
385
457
  type: "p",
386
458
  props: {
387
459
  style: {
@@ -398,7 +470,7 @@ function profileTemplate({
398
470
  }
399
471
  });
400
472
  }
401
- children.push({
473
+ contentChildren.push({
402
474
  type: "p",
403
475
  props: {
404
476
  style: {
@@ -417,6 +489,7 @@ function profileTemplate({
417
489
  type: "div",
418
490
  props: {
419
491
  style: {
492
+ position: "relative",
420
493
  display: "flex",
421
494
  flexDirection: "column",
422
495
  alignItems: "center",
@@ -425,7 +498,39 @@ function profileTemplate({
425
498
  height,
426
499
  backgroundColor: colors.background
427
500
  },
428
- children
501
+ children: [
502
+ noiseDataUrl ? {
503
+ type: "img",
504
+ props: {
505
+ src: noiseDataUrl,
506
+ width,
507
+ height,
508
+ style: {
509
+ position: "absolute",
510
+ top: 0,
511
+ left: 0,
512
+ width,
513
+ height
514
+ }
515
+ }
516
+ } : null,
517
+ {
518
+ type: "div",
519
+ props: {
520
+ style: {
521
+ position: "relative",
522
+ display: "flex",
523
+ flexDirection: "column",
524
+ alignItems: "center",
525
+ justifyContent: "center",
526
+ width,
527
+ height,
528
+ padding: "0 60px"
529
+ },
530
+ children: contentChildren
531
+ }
532
+ }
533
+ ].filter(Boolean)
429
534
  }
430
535
  };
431
536
  }
@@ -436,6 +541,7 @@ function defaultTemplate({
436
541
  description,
437
542
  siteName,
438
543
  colors,
544
+ noiseDataUrl,
439
545
  width,
440
546
  height
441
547
  }) {
@@ -443,6 +549,7 @@ function defaultTemplate({
443
549
  type: "div",
444
550
  props: {
445
551
  style: {
552
+ position: "relative",
446
553
  display: "flex",
447
554
  flexDirection: "column",
448
555
  alignItems: "center",
@@ -452,48 +559,80 @@ function defaultTemplate({
452
559
  backgroundColor: colors.background
453
560
  },
454
561
  children: [
455
- {
456
- type: "h1",
562
+ noiseDataUrl ? {
563
+ type: "img",
457
564
  props: {
565
+ src: noiseDataUrl,
566
+ width,
567
+ height,
458
568
  style: {
459
- fontSize: 72,
460
- fontWeight: 700,
461
- color: colors.text,
462
- letterSpacing: "-0.02em",
463
- margin: 0,
464
- textAlign: "center"
465
- },
466
- children: title
467
- }
468
- },
469
- description ? {
470
- type: "p",
471
- props: {
472
- style: {
473
- fontSize: 32,
474
- fontWeight: 400,
475
- color: colors.accent,
476
- marginTop: 24,
477
- marginBottom: 0,
478
- textAlign: "center",
479
- maxWidth: 900
480
- },
481
- children: description
569
+ position: "absolute",
570
+ top: 0,
571
+ left: 0,
572
+ width,
573
+ height
574
+ }
482
575
  }
483
576
  } : null,
484
577
  {
485
- type: "p",
578
+ type: "div",
486
579
  props: {
487
580
  style: {
488
- fontSize: 28,
489
- fontWeight: 400,
490
- color: colors.accent,
491
- marginTop: 64,
492
- marginBottom: 0,
493
- textAlign: "center",
494
- opacity: 0.7
581
+ position: "relative",
582
+ display: "flex",
583
+ flexDirection: "column",
584
+ alignItems: "center",
585
+ justifyContent: "center",
586
+ width,
587
+ height,
588
+ padding: "0 60px"
495
589
  },
496
- children: siteName
590
+ children: [
591
+ {
592
+ type: "h1",
593
+ props: {
594
+ style: {
595
+ fontSize: 72,
596
+ fontWeight: 700,
597
+ color: colors.text,
598
+ letterSpacing: "-0.02em",
599
+ margin: 0,
600
+ textAlign: "center"
601
+ },
602
+ children: title
603
+ }
604
+ },
605
+ description ? {
606
+ type: "p",
607
+ props: {
608
+ style: {
609
+ fontSize: 32,
610
+ fontWeight: 400,
611
+ color: colors.accent,
612
+ marginTop: 24,
613
+ marginBottom: 0,
614
+ textAlign: "center",
615
+ maxWidth: 900
616
+ },
617
+ children: description
618
+ }
619
+ } : null,
620
+ {
621
+ type: "p",
622
+ props: {
623
+ style: {
624
+ fontSize: 28,
625
+ fontWeight: 400,
626
+ color: colors.accent,
627
+ marginTop: 64,
628
+ marginBottom: 0,
629
+ textAlign: "center",
630
+ opacity: 0.7
631
+ },
632
+ children: siteName
633
+ }
634
+ }
635
+ ].filter(Boolean)
497
636
  }
498
637
  }
499
638
  ].filter(Boolean)
@@ -621,6 +760,8 @@ function createOgEndpoint(options) {
621
760
  const description = url.searchParams.get("description") ?? void 0;
622
761
  const image = url.searchParams.get("image") ?? void 0;
623
762
  const noiseSeed = url.searchParams.get("seed") ?? void 0;
763
+ const templateParam = url.searchParams.get("template");
764
+ const resolvedTemplate = templateParam ?? template;
624
765
  if (!title) {
625
766
  return new Response("Missing title parameter", { status: 400 });
626
767
  }
@@ -631,7 +772,7 @@ function createOgEndpoint(options) {
631
772
  description,
632
773
  siteName,
633
774
  image,
634
- template,
775
+ template: resolvedTemplate,
635
776
  colors,
636
777
  fonts,
637
778
  noise,
@@ -642,8 +783,9 @@ function createOgEndpoint(options) {
642
783
  cacheMaxAge
643
784
  );
644
785
  } catch (error) {
786
+ const errorMessage = error instanceof Error ? error.message : String(error);
645
787
  console.error("Failed to generate OG image:", error);
646
- return new Response("Failed to generate image", { status: 500 });
788
+ return new Response(`Failed to generate image: ${errorMessage}`, { status: 500 });
647
789
  }
648
790
  };
649
791
  }