astro-opengraph-images 1.18.0 → 1.19.0-dev.10933

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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.19.0](https://github.com/shepherdjerred/monorepo/compare/astro-opengraph-images-v1.18.0...astro-opengraph-images-v1.19.0) (2026-08-23)
4
+
5
+
6
+ ### Features
7
+
8
+ * **astro-opengraph-images:** add early path filtering ([#2331](https://github.com/shepherdjerred/monorepo/issues/2331)) ([63bbab7](https://github.com/shepherdjerred/monorepo/commit/63bbab7ed1deb40db3628b63a314dc82af524a5e))
9
+
3
10
  ## [1.18.0](https://github.com/shepherdjerred/monorepo/compare/astro-opengraph-images-v1.17.4...astro-opengraph-images-v1.18.0) (2026-08-03)
4
11
 
5
12
 
package/README.md CHANGED
@@ -215,9 +215,9 @@ If you're using this project, [open a PR](https://github.com/shepherdjerred/mono
215
215
 
216
216
  ## Filtering Pages
217
217
 
218
- By default, this integration generates an Open Graph image for every page in your build output. Pass an optional `filter` callback to skip pages — for example, the 404 page, utility routes, or sections where images don't apply.
218
+ By default, this integration generates an Open Graph image for every page in your build output. Pass an optional `pathFilter` callback to skip pages before their output files are read — for example, the 404 page, utility routes, or sections where images don't apply.
219
219
 
220
- The callback receives the same `RenderFunctionInput` as your renderer (pathname, parsed DOM, Open Graph metadata). Return `true` to render the image, `false` to skip it. The callback may be synchronous or return a `Promise<boolean>`.
220
+ The `pathFilter` callback receives the page pathname. Return `true` to continue processing the page and `false` to skip it. The callback may be synchronous or return a `Promise<boolean>`.
221
221
 
222
222
  ```diff
223
223
  import opengraphImages, { presets } from "astro-opengraph-images";
@@ -228,7 +228,7 @@ The callback receives the same `RenderFunctionInput` as your renderer (pathname,
228
228
  options: { fonts: [/* ... */] },
229
229
  render: presets.blackAndWhite,
230
230
  + // Only generate images for blog posts
231
- + filter: ({ pathname }) => pathname.startsWith("/blog/"),
231
+ + pathFilter: ({ pathname }) => pathname.startsWith("/blog/"),
232
232
  }),
233
233
  ],
234
234
  });
@@ -237,16 +237,15 @@ The callback receives the same `RenderFunctionInput` as your renderer (pathname,
237
237
  More examples:
238
238
 
239
239
  ```typescript
240
- // Exclude utility pages
241
- filter: ({ pathname }) => !["/404/", "/"].includes(pathname);
240
+ // Exclude utility pages before their HTML is read
241
+ pathFilter: ({ pathname }) => !["/404/", "/"].includes(pathname);
242
242
 
243
- // Filter on DOM content or async data
244
- filter: async ({ document, pathname }) =>
245
- document.querySelector('meta[property="og:image"]') !== null &&
246
- pathname.startsWith("/blog/");
243
+ // Filter on DOM content after Open Graph metadata has been extracted
244
+ filter: async ({ document, type }) =>
245
+ type === "article" && document.querySelector("article") !== null;
247
246
  ```
248
247
 
249
- When `verbose: true` is set on `options`, skipped pages are logged.
248
+ Use `pathFilter` when excluded pages may not have the required Open Graph metadata. The metadata-aware `filter` runs only after the output HTML is read, parsed, and strictly extracted, so it is never called for a page missing any of these required tags: `og:title`, `og:url`, `og:type`, or `og:image`. A missing required tag fails the build before `filter` can return `false`. When `verbose: true` is set on `options`, skipped pages are logged.
250
249
 
251
250
  ## Custom Renderers
252
251
 
@@ -564,3 +563,31 @@ Here are some similar libraries using Satori and Astro. I haven't done a feature
564
563
  - https://github.com/cijiugechu/astro-satori (Possibly dead, hasn't been updated in a year)
565
564
  - https://github.com/kevinzunigacuellar/astro-satori (Possibly dead, hasn't been updated in a year)
566
565
  - https://github.com/rumaan/astro-vercel-og (Possibly dead, hasn't been updated in a year)
566
+
567
+ ## Maintainers
568
+
569
+ This README is hand-maintained; edit it directly.
570
+
571
+ - **Tests** — `bun run test` (runs `vitest src`).
572
+ - **Preset example images** — build the preset example site first, then render
573
+ the PNGs from the package root:
574
+
575
+ ```bash
576
+ cd examples/preset && bun install && bun run build && cd ../..
577
+ bun run src/presets/render-examples.ts # writes assets/presets/<name>.png
578
+ ```
579
+
580
+ The separate `bun install` is deliberate. `examples/` is intentionally **not**
581
+ part of the root workspace: both examples declare the package name `example`,
582
+ so listing them would abort the root install with
583
+ `Workspace name "example" already exists`. Each example instead installs on
584
+ its own and pulls this package in through the `postinstall` hook
585
+ (`scripts/copy-example-deps.ts`), which copies the files named in our
586
+ `package.json` `files` field — mirroring what `npm pack` ships. That is the
587
+ point: the examples consume the package the way a published consumer does,
588
+ and it avoids the recursive symlink a `file:`/`link:` dependency would create
589
+ for a directory nested inside the package it depends on. Do not "fix" this by
590
+ adding `examples/*` to the root `workspaces`.
591
+
592
+ - **Publish** — `bun run publish:npm` (wraps `scripts/publish-npm.ts` at the
593
+ repo root).
package/dist/hook.d.ts CHANGED
@@ -1,7 +1,8 @@
1
- import type { AstroBuildDoneHookInput, FilterFunction, IntegrationOptions, RenderFunction } from "./types.ts";
2
- export declare function buildDoneHook({ logger, pages, options, dir, render, filter, }: AstroBuildDoneHookInput & {
1
+ import type { AstroBuildDoneHookInput, FilterFunction, IntegrationOptions, PathFilterFunction, RenderFunction } from "./types.ts";
2
+ export declare function buildDoneHook({ logger, pages, options, dir, render, filter, pathFilter, }: AstroBuildDoneHookInput & {
3
3
  options: IntegrationOptions;
4
4
  render: RenderFunction;
5
5
  filter: FilterFunction | undefined;
6
+ pathFilter: PathFilterFunction | undefined;
6
7
  }): Promise<void>;
7
8
  //# sourceMappingURL=hook.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hook.d.ts","sourceRoot":"","sources":["../src/hook.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,uBAAuB,EACvB,cAAc,EACd,kBAAkB,EAElB,cAAc,EACf,MAAM,YAAY,CAAC;AASpB,wBAAsB,aAAa,CAAC,EAClC,MAAM,EACN,KAAK,EACL,OAAO,EACP,GAAG,EACH,MAAM,EACN,MAAM,GACP,EAAE,uBAAuB,GAAG;IAC3B,OAAO,EAAE,kBAAkB,CAAC;IAC5B,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,cAAc,GAAG,SAAS,CAAC;CACpC,iBAMA"}
1
+ {"version":3,"file":"hook.d.ts","sourceRoot":"","sources":["../src/hook.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,uBAAuB,EACvB,cAAc,EACd,kBAAkB,EAElB,kBAAkB,EAClB,cAAc,EACf,MAAM,YAAY,CAAC;AASpB,wBAAsB,aAAa,CAAC,EAClC,MAAM,EACN,KAAK,EACL,OAAO,EACP,GAAG,EACH,MAAM,EACN,MAAM,EACN,UAAU,GACX,EAAE,uBAAuB,GAAG;IAC3B,OAAO,EAAE,kBAAkB,CAAC;IAC5B,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,cAAc,GAAG,SAAS,CAAC;IACnC,UAAU,EAAE,kBAAkB,GAAG,SAAS,CAAC;CAC5C,iBAMA"}
package/dist/hook.js CHANGED
@@ -6,12 +6,21 @@ import { getFilePath } from "./util.js";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import * as jsdom from "jsdom";
8
8
  import path from "node:path";
9
- export async function buildDoneHook({ logger, pages, options, dir, render, filter, }) {
9
+ export async function buildDoneHook({ logger, pages, options, dir, render, filter, pathFilter, }) {
10
10
  logger.info("Generating Open Graph images");
11
- const promises = pages.map((page) => handlePage({ page, options, render, dir, logger, filter }));
11
+ const promises = pages.map((page) => handlePage({ page, options, render, dir, logger, filter, pathFilter }));
12
12
  await Promise.all(promises);
13
13
  }
14
- async function handlePage({ page, options, render, dir, logger, filter, }) {
14
+ async function handlePage({ page, options, render, dir, logger, filter, pathFilter, }) {
15
+ if (pathFilter) {
16
+ const shouldRender = await pathFilter(page);
17
+ if (!shouldRender) {
18
+ if (options.verbose) {
19
+ logger.info(`Skipping page ${page.pathname}.`);
20
+ }
21
+ return;
22
+ }
23
+ }
15
24
  // gets the absolute path to the HTML file. E.g. /home/user/project/dist/blog/index.html
16
25
  // fileURLToPath() converts the URL to a file path. Without it, the path would start with a leading slash on Windows
17
26
  // systems, resulting in an invalid path.
package/dist/hook.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"hook.js","sourceRoot":"","sources":["../src/hook.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hook.js","sourceRoot":"","sources":["../src/hook.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=hook.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook.test.d.ts","sourceRoot":"","sources":["../src/hook.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,217 @@
1
+ import { access, mkdir, mkdtemp, readFile, rm, writeFile, } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import path from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+ import { createElement } from "react";
6
+ import { expect, test } from "vitest";
7
+ import { buildDoneHook } from "./hook.js";
8
+ import { astroOpenGraphImages } from "./integration.js";
9
+ class TestLogger {
10
+ options = {
11
+ destination: {
12
+ write: (message) => {
13
+ void message;
14
+ },
15
+ },
16
+ level: "silent",
17
+ };
18
+ label = "test";
19
+ fork(_label) {
20
+ return new TestLogger();
21
+ }
22
+ info(message) {
23
+ void message;
24
+ }
25
+ warn(message) {
26
+ void message;
27
+ }
28
+ error(message) {
29
+ void message;
30
+ }
31
+ debug(message) {
32
+ void message;
33
+ }
34
+ flush() {
35
+ void this.label;
36
+ }
37
+ close() {
38
+ void this.label;
39
+ }
40
+ }
41
+ test("an async path filter skips a page before reading its output", async () => {
42
+ const outputDir = await createTempDir();
43
+ let pathFilterCalls = 0;
44
+ let filterCalls = 0;
45
+ let renderCalls = 0;
46
+ const pathFilter = async ({ pathname }) => {
47
+ pathFilterCalls += 1;
48
+ await Promise.resolve();
49
+ return pathname !== "/excluded/";
50
+ };
51
+ const filter = ({ pathname }) => {
52
+ filterCalls += 1;
53
+ return pathname !== "/excluded/";
54
+ };
55
+ const render = () => {
56
+ renderCalls += 1;
57
+ return null;
58
+ };
59
+ const integration = astroOpenGraphImages({
60
+ options: { fonts: testOptions.fonts },
61
+ render,
62
+ filter,
63
+ pathFilter,
64
+ });
65
+ const hook = integration.hooks["astro:build:done"];
66
+ if (hook === undefined) {
67
+ throw new Error("Expected astro:build:done hook");
68
+ }
69
+ try {
70
+ await expect(hook({
71
+ pages: [{ pathname: "/excluded/" }],
72
+ dir: pathToFileURL(`${outputDir}/`),
73
+ assets: new Map(),
74
+ logger: new TestLogger(),
75
+ })).resolves.toBeUndefined();
76
+ }
77
+ finally {
78
+ await rm(outputDir, { force: true, recursive: true });
79
+ }
80
+ expect(pathFilterCalls).toBe(1);
81
+ expect(filterCalls).toBe(0);
82
+ expect(renderCalls).toBe(0);
83
+ });
84
+ test("a path-filtered-in page still requires Open Graph metadata", async () => {
85
+ const outputDir = await createOutputDir("accepted", "<html></html>");
86
+ let filterCalls = 0;
87
+ const pathFilter = alwaysPathFilter;
88
+ const filter = () => {
89
+ filterCalls += 1;
90
+ return true;
91
+ };
92
+ const render = emptyRender;
93
+ try {
94
+ await expect(buildDoneHook({
95
+ pages: [{ pathname: "/accepted/" }],
96
+ dir: pathToFileURL(`${outputDir}/`),
97
+ assets: new Map(),
98
+ logger: new TestLogger(),
99
+ options: testOptions,
100
+ render,
101
+ filter,
102
+ pathFilter,
103
+ })).rejects.toThrow("Missing required meta tags");
104
+ }
105
+ finally {
106
+ await rm(outputDir, { force: true, recursive: true });
107
+ }
108
+ expect(filterCalls).toBe(0);
109
+ });
110
+ test("an accepted page with Open Graph metadata renders an image", async () => {
111
+ const outputDir = await createOutputDir("rendered", `<!doctype html>
112
+ <html>
113
+ <head>
114
+ <meta property="og:title" content="Rendered page" />
115
+ <meta property="og:url" content="http://example.com/rendered/" />
116
+ <meta property="og:type" content="website" />
117
+ <meta property="og:image" content="http://example.com/rendered/index.png" />
118
+ </head>
119
+ </html>`);
120
+ let filterCalls = 0;
121
+ let renderCalls = 0;
122
+ const pathFilter = alwaysPathFilter;
123
+ const filter = ({ pathname }) => {
124
+ filterCalls += 1;
125
+ return pathname === "/rendered/";
126
+ };
127
+ const render = () => {
128
+ renderCalls += 1;
129
+ return createElement("div");
130
+ };
131
+ try {
132
+ await expect(buildDoneHook({
133
+ pages: [{ pathname: "/rendered/" }],
134
+ dir: pathToFileURL(`${outputDir}/`),
135
+ assets: new Map(),
136
+ logger: new TestLogger(),
137
+ options: testOptions,
138
+ render,
139
+ filter,
140
+ pathFilter,
141
+ })).resolves.toBeUndefined();
142
+ await access(path.join(outputDir, "rendered", "index.png"));
143
+ }
144
+ finally {
145
+ await rm(outputDir, { force: true, recursive: true });
146
+ }
147
+ expect(filterCalls).toBe(1);
148
+ expect(renderCalls).toBe(1);
149
+ });
150
+ test("the metadata-aware filter can skip a valid page", async () => {
151
+ const outputDir = await createOutputDir("metadata-skipped", renderedPageHtml("metadata-skipped"));
152
+ let filterCalls = 0;
153
+ let renderCalls = 0;
154
+ const pathFilter = alwaysPathFilter;
155
+ const filter = ({ title }) => {
156
+ filterCalls += 1;
157
+ expect(title).toBe("Rendered page");
158
+ return false;
159
+ };
160
+ const render = () => {
161
+ renderCalls += 1;
162
+ return createElement("div");
163
+ };
164
+ try {
165
+ await expect(buildDoneHook({
166
+ pages: [{ pathname: "/metadata-skipped/" }],
167
+ dir: pathToFileURL(`${outputDir}/`),
168
+ assets: new Map(),
169
+ logger: new TestLogger(),
170
+ options: testOptions,
171
+ render,
172
+ filter,
173
+ pathFilter,
174
+ })).resolves.toBeUndefined();
175
+ await expect(access(path.join(outputDir, "metadata-skipped", "index.png"))).rejects.toThrow();
176
+ }
177
+ finally {
178
+ await rm(outputDir, { force: true, recursive: true });
179
+ }
180
+ expect(filterCalls).toBe(1);
181
+ expect(renderCalls).toBe(0);
182
+ });
183
+ const testOptions = {
184
+ width: 1200,
185
+ height: 630,
186
+ verbose: false,
187
+ fonts: [
188
+ {
189
+ data: await readFile(path.join(import.meta.dirname, "../node_modules/@fontsource/roboto/files/roboto-latin-400-normal.woff")),
190
+ name: "Roboto",
191
+ weight: 400,
192
+ },
193
+ ],
194
+ };
195
+ const alwaysPathFilter = () => true;
196
+ const emptyRender = () => null;
197
+ async function createOutputDir(page, html = renderedPageHtml(page)) {
198
+ const outputDir = await createTempDir();
199
+ await mkdir(path.join(outputDir, page), { recursive: true });
200
+ await writeFile(path.join(outputDir, page, "index.html"), html);
201
+ return outputDir;
202
+ }
203
+ async function createTempDir() {
204
+ return await mkdtemp(path.join(tmpdir(), "astro-opengraph-images-"));
205
+ }
206
+ function renderedPageHtml(page) {
207
+ return `<!doctype html>
208
+ <html>
209
+ <head>
210
+ <meta property="og:title" content="Rendered page" />
211
+ <meta property="og:url" content="http://example.com/${page}/" />
212
+ <meta property="og:type" content="website" />
213
+ <meta property="og:image" content="http://example.com/${page}/index.png" />
214
+ </head>
215
+ </html>`;
216
+ }
217
+ //# sourceMappingURL=hook.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook.test.js","sourceRoot":"","sources":["../src/hook.test.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { getImagePath as importedGetImagePath } from "./util.ts";
2
2
  import { astroOpenGraphImages } from "./integration.ts";
3
- import type { IntegrationInput as _IntegrationInput, IntegrationDefaults as _IntegrationDefaults, PartialIntegrationOptions as _PartialIntegrationOptions, IntegrationOptions as _IntegrationOptions, Page as _Page, AstroBuildDoneHookInput as _AstroBuildDoneHookInput, RenderFunctionInput as _RenderFunctionInput, RenderFunction as _RenderFunction, FilterFunction as _FilterFunction, PageDetails as _PageDetails, SatoriWeight as _SatoriWeight, SatoriFontStyle as _SatoriFontStyle, SatoriFontOptions as _SatoriFontOptions, SatoriOptions as _SatoriOptions } from "./types.ts";
3
+ import type { IntegrationInput as _IntegrationInput, IntegrationDefaults as _IntegrationDefaults, PartialIntegrationOptions as _PartialIntegrationOptions, IntegrationOptions as _IntegrationOptions, Page as _Page, AstroBuildDoneHookInput as _AstroBuildDoneHookInput, RenderFunctionInput as _RenderFunctionInput, RenderFunction as _RenderFunction, FilterFunction as _FilterFunction, PathFilterFunction as _PathFilterFunction, PageDetails as _PageDetails, SatoriWeight as _SatoriWeight, SatoriFontStyle as _SatoriFontStyle, SatoriFontOptions as _SatoriFontOptions, SatoriOptions as _SatoriOptions } from "./types.ts";
4
4
  export declare const presets: {
5
5
  blackAndWhite: typeof import("./presets/black-and-white.tsx").blackAndWhite;
6
6
  gradients: typeof import("./presets/gradients.tsx").gradients;
@@ -25,6 +25,7 @@ export type AstroBuildDoneHookInput = _AstroBuildDoneHookInput;
25
25
  export type RenderFunctionInput = _RenderFunctionInput;
26
26
  export type RenderFunction = _RenderFunction;
27
27
  export type FilterFunction = _FilterFunction;
28
+ export type PathFilterFunction = _PathFilterFunction;
28
29
  export type PageDetails = _PageDetails;
29
30
  export type SatoriWeight = _SatoriWeight;
30
31
  export type SatoriFontStyle = _SatoriFontStyle;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,IAAI,oBAAoB,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,KAAK,EACV,gBAAgB,IAAI,iBAAiB,EACrC,mBAAmB,IAAI,oBAAoB,EAC3C,yBAAyB,IAAI,0BAA0B,EACvD,kBAAkB,IAAI,mBAAmB,EACzC,IAAI,IAAI,KAAK,EACb,uBAAuB,IAAI,wBAAwB,EACnD,mBAAmB,IAAI,oBAAoB,EAC3C,cAAc,IAAI,eAAe,EACjC,cAAc,IAAI,eAAe,EACjC,WAAW,IAAI,YAAY,EAC3B,YAAY,IAAI,aAAa,EAC7B,eAAe,IAAI,gBAAgB,EACnC,iBAAiB,IAAI,kBAAkB,EACvC,aAAa,IAAI,cAAc,EAChC,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,OAAO;;;;;;;;;;;;CAAkB,CAAC;AACvC,eAAO,MAAM,YAAY,6BAAuB,CAAC;eAClC,oBAAoB;AAEnC,MAAM,MAAM,gBAAgB,GAAG,iBAAiB,CAAC;AACjD,MAAM,MAAM,mBAAmB,GAAG,oBAAoB,CAAC;AACvD,MAAM,MAAM,yBAAyB,GAAG,0BAA0B,CAAC;AACnE,MAAM,MAAM,kBAAkB,GAAG,mBAAmB,CAAC;AACrD,MAAM,MAAM,IAAI,GAAG,KAAK,CAAC;AACzB,MAAM,MAAM,uBAAuB,GAAG,wBAAwB,CAAC;AAC/D,MAAM,MAAM,mBAAmB,GAAG,oBAAoB,CAAC;AACvD,MAAM,MAAM,cAAc,GAAG,eAAe,CAAC;AAC7C,MAAM,MAAM,cAAc,GAAG,eAAe,CAAC;AAC7C,MAAM,MAAM,WAAW,GAAG,YAAY,CAAC;AACvC,MAAM,MAAM,YAAY,GAAG,aAAa,CAAC;AACzC,MAAM,MAAM,eAAe,GAAG,gBAAgB,CAAC;AAC/C,MAAM,MAAM,iBAAiB,GAAG,kBAAkB,CAAC;AACnD,MAAM,MAAM,aAAa,GAAG,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,IAAI,oBAAoB,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,KAAK,EACV,gBAAgB,IAAI,iBAAiB,EACrC,mBAAmB,IAAI,oBAAoB,EAC3C,yBAAyB,IAAI,0BAA0B,EACvD,kBAAkB,IAAI,mBAAmB,EACzC,IAAI,IAAI,KAAK,EACb,uBAAuB,IAAI,wBAAwB,EACnD,mBAAmB,IAAI,oBAAoB,EAC3C,cAAc,IAAI,eAAe,EACjC,cAAc,IAAI,eAAe,EACjC,kBAAkB,IAAI,mBAAmB,EACzC,WAAW,IAAI,YAAY,EAC3B,YAAY,IAAI,aAAa,EAC7B,eAAe,IAAI,gBAAgB,EACnC,iBAAiB,IAAI,kBAAkB,EACvC,aAAa,IAAI,cAAc,EAChC,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,OAAO;;;;;;;;;;;;CAAkB,CAAC;AACvC,eAAO,MAAM,YAAY,6BAAuB,CAAC;eAClC,oBAAoB;AAEnC,MAAM,MAAM,gBAAgB,GAAG,iBAAiB,CAAC;AACjD,MAAM,MAAM,mBAAmB,GAAG,oBAAoB,CAAC;AACvD,MAAM,MAAM,yBAAyB,GAAG,0BAA0B,CAAC;AACnE,MAAM,MAAM,kBAAkB,GAAG,mBAAmB,CAAC;AACrD,MAAM,MAAM,IAAI,GAAG,KAAK,CAAC;AACzB,MAAM,MAAM,uBAAuB,GAAG,wBAAwB,CAAC;AAC/D,MAAM,MAAM,mBAAmB,GAAG,oBAAoB,CAAC;AACvD,MAAM,MAAM,cAAc,GAAG,eAAe,CAAC;AAC7C,MAAM,MAAM,cAAc,GAAG,eAAe,CAAC;AAC7C,MAAM,MAAM,kBAAkB,GAAG,mBAAmB,CAAC;AACrD,MAAM,MAAM,WAAW,GAAG,YAAY,CAAC;AACvC,MAAM,MAAM,YAAY,GAAG,aAAa,CAAC;AACzC,MAAM,MAAM,eAAe,GAAG,gBAAgB,CAAC;AAC/C,MAAM,MAAM,iBAAiB,GAAG,kBAAkB,CAAC;AACnD,MAAM,MAAM,aAAa,GAAG,cAAc,CAAC"}
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,YAAY,IAAI,oBAAoB,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAkBxD,MAAM,CAAC,MAAM,OAAO,GAAG,eAAe,CAAC;AACvC,MAAM,CAAC,MAAM,YAAY,GAAG,oBAAoB,CAAC;AACjD,eAAe,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,YAAY,IAAI,oBAAoB,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAmBxD,MAAM,CAAC,MAAM,OAAO,GAAG,eAAe,CAAC;AACvC,MAAM,CAAC,MAAM,YAAY,GAAG,oBAAoB,CAAC;AACjD,eAAe,oBAAoB,CAAC"}
@@ -1,4 +1,4 @@
1
1
  import type { AstroIntegration } from "astro";
2
2
  import type { IntegrationInput } from "./types.ts";
3
- export declare function astroOpenGraphImages({ options, render, filter, }: IntegrationInput): AstroIntegration;
3
+ export declare function astroOpenGraphImages({ options, render, filter, pathFilter, }: IntegrationInput): AstroIntegration;
4
4
  //# sourceMappingURL=integration.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../src/integration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAC9C,OAAO,KAAK,EAGV,gBAAgB,EAEjB,MAAM,YAAY,CAAC;AASpB,wBAAgB,oBAAoB,CAAC,EACnC,OAAO,EACP,MAAM,EACN,MAAM,GACP,EAAE,gBAAgB,GAAG,gBAAgB,CAgBrC"}
1
+ {"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../src/integration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAC9C,OAAO,KAAK,EAGV,gBAAgB,EAEjB,MAAM,YAAY,CAAC;AASpB,wBAAgB,oBAAoB,CAAC,EACnC,OAAO,EACP,MAAM,EACN,MAAM,EACN,UAAU,GACX,EAAE,gBAAgB,GAAG,gBAAgB,CAiBrC"}
@@ -4,7 +4,7 @@ const defaults = {
4
4
  height: 630,
5
5
  verbose: false,
6
6
  };
7
- export function astroOpenGraphImages({ options, render, filter, }) {
7
+ export function astroOpenGraphImages({ options, render, filter, pathFilter, }) {
8
8
  const optionsWithDefaults = { ...defaults, ...options };
9
9
  return {
10
10
  name: "astro-opengraph-images",
@@ -15,6 +15,7 @@ export function astroOpenGraphImages({ options, render, filter, }) {
15
15
  options: optionsWithDefaults,
16
16
  render,
17
17
  filter,
18
+ pathFilter,
18
19
  });
19
20
  },
20
21
  },
@@ -1 +1 @@
1
- {"version":3,"file":"integration.js","sourceRoot":"","sources":["../src/integration.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE1C,MAAM,QAAQ,GAAwB;IACpC,KAAK,EAAE,IAAI;IACX,MAAM,EAAE,GAAG;IACX,OAAO,EAAE,KAAK;CACf,CAAC;AAEF,MAAM,UAAU,oBAAoB,CAAC,EACnC,OAAO,EACP,MAAM,EACN,MAAM,GACW;IACjB,MAAM,mBAAmB,GAAuB,EAAE,GAAG,QAAQ,EAAE,GAAG,OAAO,EAAE,CAAC;IAE5E,OAAO;QACL,IAAI,EAAE,wBAAwB;QAC9B,KAAK,EAAE;YACL,kBAAkB,EAAE,KAAK,EAAE,KAA8B,EAAE,EAAE;gBAC3D,OAAO,aAAa,CAAC;oBACnB,GAAG,KAAK;oBACR,OAAO,EAAE,mBAAmB;oBAC5B,MAAM;oBACN,MAAM;iBACP,CAAC,CAAC;YACL,CAAC;SACF;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"integration.js","sourceRoot":"","sources":["../src/integration.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE1C,MAAM,QAAQ,GAAwB;IACpC,KAAK,EAAE,IAAI;IACX,MAAM,EAAE,GAAG;IACX,OAAO,EAAE,KAAK;CACf,CAAC;AAEF,MAAM,UAAU,oBAAoB,CAAC,EACnC,OAAO,EACP,MAAM,EACN,MAAM,EACN,UAAU,GACO;IACjB,MAAM,mBAAmB,GAAuB,EAAE,GAAG,QAAQ,EAAE,GAAG,OAAO,EAAE,CAAC;IAE5E,OAAO;QACL,IAAI,EAAE,wBAAwB;QAC9B,KAAK,EAAE;YACL,kBAAkB,EAAE,KAAK,EAAE,KAA8B,EAAE,EAAE;gBAC3D,OAAO,aAAa,CAAC;oBACnB,GAAG,KAAK;oBACR,OAAO,EAAE,mBAAmB;oBAC5B,MAAM;oBACN,MAAM;oBACN,UAAU;iBACX,CAAC,CAAC;YACL,CAAC;SACF;KACF,CAAC;AACJ,CAAC"}
package/dist/types.d.ts CHANGED
@@ -4,6 +4,7 @@ export type IntegrationInput = {
4
4
  options: PartialIntegrationOptions;
5
5
  render: RenderFunction;
6
6
  filter?: FilterFunction;
7
+ pathFilter?: PathFilterFunction;
7
8
  };
8
9
  /** When applied to PartialIntegrationOptions this type equals IntegrationOptions */
9
10
  export type IntegrationDefaults = {
@@ -25,6 +26,8 @@ export type IntegrationOptions = PartialIntegrationOptions & IntegrationDefaults
25
26
  export type Page = {
26
27
  pathname: string;
27
28
  };
29
+ /** A function that filters pages before their output files are read */
30
+ export type PathFilterFunction = (input: Page) => Promise<boolean> | boolean;
28
31
  /** The input Astro passes to the build done hook */
29
32
  export type AstroBuildDoneHookInput = Parameters<BaseIntegrationHooks["astro:build:done"]>[0];
30
33
  /** The input arguments to a `RenderFunction` */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,yBAAyB,CAAC;IACnC,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,CAAC,EAAE,cAAc,CAAC;CACzB,CAAC;AAEF,oFAAoF;AACpF,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAC1C,IAAI,CAAC,aAAa,EAAE,OAAO,CAAC,EAC5B,QAAQ,CACT,GACC,OAAO,CAAC,mBAAmB,CAAC,CAAC;AAE/B;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG,yBAAyB,GACxD,mBAAmB,CAAC;AAEtB,+CAA+C;AAC/C,MAAM,MAAM,IAAI,GAAG;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,oDAAoD;AACpD,MAAM,MAAM,uBAAuB,GAAG,UAAU,CAC9C,oBAAoB,CAAC,kBAAkB,CAAC,CACzC,CAAC,CAAC,CAAC,CAAC;AAEL,gDAAgD;AAChD,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,GAAG,CAAC;IACT,QAAQ,EAAE,QAAQ,CAAC;CACpB,GAAG,WAAW,CAAC;AAEhB,4FAA4F;AAC5F,MAAM,MAAM,cAAc,GAAG,CAC3B,KAAK,EAAE,mBAAmB,KACvB,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;AAEhC,uDAAuD;AACvD,MAAM,MAAM,cAAc,GAAG,CAC3B,KAAK,EAAE,mBAAmB,KACvB,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;AAEpC,qCAAqC;AACrC,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,KAAK,aAAa,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;AAEpC,+BAA+B;AAC/B,MAAM,MAAM,YAAY,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAC/E,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAClD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AACF,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,aAAa,CAAC,iBAAiB,CAAC,CAAC;IACxC,6EAA6E;IAC7E,mBAAmB,CAAC,EAAE,CACpB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM,KACZ,OAAO,CAAC,MAAM,GAAG,iBAAiB,EAAE,CAAC,CAAC;CAC5C,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -17,17 +17,16 @@
17
17
  "url": "https://github.com/shepherdjerred/monorepo/issues"
18
18
  },
19
19
  "type": "module",
20
- "version": "1.18.0",
20
+ "version": "1.19.0-dev.10933",
21
21
  "imports": {
22
22
  "#src/*": "./src/*"
23
23
  },
24
24
  "scripts": {
25
- "lint": "eslint src generate-readme.ts generate-readme-core.ts generate-readme*.test.ts --cache",
25
+ "lint": "eslint src --cache",
26
26
  "build": "PATH=node_modules/@typescript/native/bin:$PATH tsc",
27
27
  "watch": "PATH=node_modules/@typescript/native/bin:$PATH tsc -w",
28
- "test": "vitest src && bun test generate-readme*.test.ts",
29
- "test:coverage": "bun test --coverage-reporter=text --coverage-reporter=lcov --coverage-dir=coverage --config ../../scripts/bunfig.coverage.toml --coverage generate-readme.test.ts",
30
- "typecheck": "PATH=node_modules/@typescript/native/bin:$PATH tsc --noEmit && PATH=node_modules/@typescript/native/bin:$PATH tsc --noEmit -p tsconfig.scripts.json",
28
+ "test": "bun --no-install --bun vitest --config ../../vitest.config.ts run src",
29
+ "typecheck": "PATH=node_modules/@typescript/native/bin:$PATH tsc --noEmit",
31
30
  "publish:npm": "bun ../../scripts/publish-npm.ts .",
32
31
  "test:ci": "bun ../../scripts/run-ci-test.ts",
33
32
  "test:report": "CI_TEST_COVERAGE=1 bun ../../scripts/run-ci-test.ts"
@@ -73,17 +72,17 @@
73
72
  "@shepherdjerred/eslint-config": "0.3.0",
74
73
  "@tsconfig/node24": "^24.0.4",
75
74
  "@tsconfig/strictest": "^2.0.8",
76
- "@types/bun": "^1.3.13",
77
- "@types/jsdom": "^28.0.1",
75
+ "@types/bun": "1.4.0",
76
+ "@types/jsdom": "^30.0.0",
78
77
  "@types/node": "^26.1.1",
79
- "@types/react": "^19.2.17",
78
+ "@types/react": "^19.2.18",
80
79
  "@typescript/native": "npm:typescript@7.0.2",
81
80
  "astro": "^7.1.5",
82
81
  "eslint": "^10.3.0",
83
82
  "jiti": "^2.7.0",
84
83
  "typescript": "^6.0.3",
85
- "vitest": "^4.1.5",
86
- "@vitest/coverage-istanbul": "4.1.9"
84
+ "vitest": "4.1.10",
85
+ "@vitest/coverage-istanbul": "4.1.10"
87
86
  },
88
87
  "files": [
89
88
  "dist",
@@ -0,0 +1,277 @@
1
+ import type { AstroIntegrationLogger } from "astro";
2
+ import {
3
+ access,
4
+ mkdir,
5
+ mkdtemp,
6
+ readFile,
7
+ rm,
8
+ writeFile,
9
+ } from "node:fs/promises";
10
+ import { tmpdir } from "node:os";
11
+ import path from "node:path";
12
+ import { pathToFileURL } from "node:url";
13
+ import { createElement } from "react";
14
+ import { expect, test } from "vitest";
15
+ import { buildDoneHook } from "./hook.ts";
16
+ import { astroOpenGraphImages } from "./integration.ts";
17
+ import type {
18
+ FilterFunction,
19
+ IntegrationOptions,
20
+ PathFilterFunction,
21
+ RenderFunction,
22
+ } from "./types.ts";
23
+
24
+ class TestLogger implements AstroIntegrationLogger {
25
+ readonly options = {
26
+ destination: {
27
+ write: (message: unknown) => {
28
+ void message;
29
+ },
30
+ },
31
+ level: "silent" as const,
32
+ };
33
+ readonly label = "test";
34
+
35
+ fork(_label: string): TestLogger {
36
+ return new TestLogger();
37
+ }
38
+
39
+ info(message: string): void {
40
+ void message;
41
+ }
42
+
43
+ warn(message: string): void {
44
+ void message;
45
+ }
46
+
47
+ error(message: string): void {
48
+ void message;
49
+ }
50
+
51
+ debug(message: string): void {
52
+ void message;
53
+ }
54
+
55
+ flush(): void {
56
+ void this.label;
57
+ }
58
+
59
+ close(): void {
60
+ void this.label;
61
+ }
62
+ }
63
+
64
+ test("an async path filter skips a page before reading its output", async () => {
65
+ const outputDir = await createTempDir();
66
+ let pathFilterCalls = 0;
67
+ let filterCalls = 0;
68
+ let renderCalls = 0;
69
+
70
+ const pathFilter: PathFilterFunction = async ({ pathname }) => {
71
+ pathFilterCalls += 1;
72
+ await Promise.resolve();
73
+ return pathname !== "/excluded/";
74
+ };
75
+ const filter: FilterFunction = ({ pathname }) => {
76
+ filterCalls += 1;
77
+ return pathname !== "/excluded/";
78
+ };
79
+ const render: RenderFunction = () => {
80
+ renderCalls += 1;
81
+ return null;
82
+ };
83
+ const integration = astroOpenGraphImages({
84
+ options: { fonts: testOptions.fonts },
85
+ render,
86
+ filter,
87
+ pathFilter,
88
+ });
89
+ const hook = integration.hooks["astro:build:done"];
90
+
91
+ if (hook === undefined) {
92
+ throw new Error("Expected astro:build:done hook");
93
+ }
94
+
95
+ try {
96
+ await expect(
97
+ hook({
98
+ pages: [{ pathname: "/excluded/" }],
99
+ dir: pathToFileURL(`${outputDir}/`),
100
+ assets: new Map(),
101
+ logger: new TestLogger(),
102
+ }),
103
+ ).resolves.toBeUndefined();
104
+ } finally {
105
+ await rm(outputDir, { force: true, recursive: true });
106
+ }
107
+
108
+ expect(pathFilterCalls).toBe(1);
109
+ expect(filterCalls).toBe(0);
110
+ expect(renderCalls).toBe(0);
111
+ });
112
+
113
+ test("a path-filtered-in page still requires Open Graph metadata", async () => {
114
+ const outputDir = await createOutputDir("accepted", "<html></html>");
115
+ let filterCalls = 0;
116
+ const pathFilter = alwaysPathFilter;
117
+ const filter: FilterFunction = () => {
118
+ filterCalls += 1;
119
+ return true;
120
+ };
121
+ const render = emptyRender;
122
+
123
+ try {
124
+ await expect(
125
+ buildDoneHook({
126
+ pages: [{ pathname: "/accepted/" }],
127
+ dir: pathToFileURL(`${outputDir}/`),
128
+ assets: new Map(),
129
+ logger: new TestLogger(),
130
+ options: testOptions,
131
+ render,
132
+ filter,
133
+ pathFilter,
134
+ }),
135
+ ).rejects.toThrow("Missing required meta tags");
136
+ } finally {
137
+ await rm(outputDir, { force: true, recursive: true });
138
+ }
139
+
140
+ expect(filterCalls).toBe(0);
141
+ });
142
+
143
+ test("an accepted page with Open Graph metadata renders an image", async () => {
144
+ const outputDir = await createOutputDir(
145
+ "rendered",
146
+ `<!doctype html>
147
+ <html>
148
+ <head>
149
+ <meta property="og:title" content="Rendered page" />
150
+ <meta property="og:url" content="http://example.com/rendered/" />
151
+ <meta property="og:type" content="website" />
152
+ <meta property="og:image" content="http://example.com/rendered/index.png" />
153
+ </head>
154
+ </html>`,
155
+ );
156
+ let filterCalls = 0;
157
+ let renderCalls = 0;
158
+ const pathFilter = alwaysPathFilter;
159
+ const filter: FilterFunction = ({ pathname }) => {
160
+ filterCalls += 1;
161
+ return pathname === "/rendered/";
162
+ };
163
+ const render: RenderFunction = () => {
164
+ renderCalls += 1;
165
+ return createElement("div");
166
+ };
167
+
168
+ try {
169
+ await expect(
170
+ buildDoneHook({
171
+ pages: [{ pathname: "/rendered/" }],
172
+ dir: pathToFileURL(`${outputDir}/`),
173
+ assets: new Map(),
174
+ logger: new TestLogger(),
175
+ options: testOptions,
176
+ render,
177
+ filter,
178
+ pathFilter,
179
+ }),
180
+ ).resolves.toBeUndefined();
181
+ await access(path.join(outputDir, "rendered", "index.png"));
182
+ } finally {
183
+ await rm(outputDir, { force: true, recursive: true });
184
+ }
185
+
186
+ expect(filterCalls).toBe(1);
187
+ expect(renderCalls).toBe(1);
188
+ });
189
+
190
+ test("the metadata-aware filter can skip a valid page", async () => {
191
+ const outputDir = await createOutputDir(
192
+ "metadata-skipped",
193
+ renderedPageHtml("metadata-skipped"),
194
+ );
195
+ let filterCalls = 0;
196
+ let renderCalls = 0;
197
+ const pathFilter = alwaysPathFilter;
198
+ const filter: FilterFunction = ({ title }) => {
199
+ filterCalls += 1;
200
+ expect(title).toBe("Rendered page");
201
+ return false;
202
+ };
203
+ const render: RenderFunction = () => {
204
+ renderCalls += 1;
205
+ return createElement("div");
206
+ };
207
+
208
+ try {
209
+ await expect(
210
+ buildDoneHook({
211
+ pages: [{ pathname: "/metadata-skipped/" }],
212
+ dir: pathToFileURL(`${outputDir}/`),
213
+ assets: new Map(),
214
+ logger: new TestLogger(),
215
+ options: testOptions,
216
+ render,
217
+ filter,
218
+ pathFilter,
219
+ }),
220
+ ).resolves.toBeUndefined();
221
+ await expect(
222
+ access(path.join(outputDir, "metadata-skipped", "index.png")),
223
+ ).rejects.toThrow();
224
+ } finally {
225
+ await rm(outputDir, { force: true, recursive: true });
226
+ }
227
+
228
+ expect(filterCalls).toBe(1);
229
+ expect(renderCalls).toBe(0);
230
+ });
231
+
232
+ const testOptions: IntegrationOptions = {
233
+ width: 1200,
234
+ height: 630,
235
+ verbose: false,
236
+ fonts: [
237
+ {
238
+ data: await readFile(
239
+ path.join(
240
+ import.meta.dirname,
241
+ "../node_modules/@fontsource/roboto/files/roboto-latin-400-normal.woff",
242
+ ),
243
+ ),
244
+ name: "Roboto",
245
+ weight: 400,
246
+ },
247
+ ],
248
+ };
249
+
250
+ const alwaysPathFilter: PathFilterFunction = () => true;
251
+ const emptyRender: RenderFunction = () => null;
252
+
253
+ async function createOutputDir(
254
+ page: string,
255
+ html = renderedPageHtml(page),
256
+ ): Promise<string> {
257
+ const outputDir = await createTempDir();
258
+ await mkdir(path.join(outputDir, page), { recursive: true });
259
+ await writeFile(path.join(outputDir, page, "index.html"), html);
260
+ return outputDir;
261
+ }
262
+
263
+ async function createTempDir(): Promise<string> {
264
+ return await mkdtemp(path.join(tmpdir(), "astro-opengraph-images-"));
265
+ }
266
+
267
+ function renderedPageHtml(page: string): string {
268
+ return `<!doctype html>
269
+ <html>
270
+ <head>
271
+ <meta property="og:title" content="Rendered page" />
272
+ <meta property="og:url" content="http://example.com/${page}/" />
273
+ <meta property="og:type" content="website" />
274
+ <meta property="og:image" content="http://example.com/${page}/index.png" />
275
+ </head>
276
+ </html>`;
277
+ }
package/src/hook.ts CHANGED
@@ -5,6 +5,7 @@ import type {
5
5
  FilterFunction,
6
6
  IntegrationOptions,
7
7
  Page,
8
+ PathFilterFunction,
8
9
  RenderFunction,
9
10
  } from "./types.ts";
10
11
  import * as fs from "node:fs/promises";
@@ -22,14 +23,16 @@ export async function buildDoneHook({
22
23
  dir,
23
24
  render,
24
25
  filter,
26
+ pathFilter,
25
27
  }: AstroBuildDoneHookInput & {
26
28
  options: IntegrationOptions;
27
29
  render: RenderFunction;
28
30
  filter: FilterFunction | undefined;
31
+ pathFilter: PathFilterFunction | undefined;
29
32
  }) {
30
33
  logger.info("Generating Open Graph images");
31
34
  const promises = pages.map((page) =>
32
- handlePage({ page, options, render, dir, logger, filter }),
35
+ handlePage({ page, options, render, dir, logger, filter, pathFilter }),
33
36
  );
34
37
  await Promise.all(promises);
35
38
  }
@@ -41,6 +44,7 @@ type HandlePageInput = {
41
44
  dir: URL;
42
45
  logger: AstroIntegrationLogger;
43
46
  filter: FilterFunction | undefined;
47
+ pathFilter: PathFilterFunction | undefined;
44
48
  };
45
49
 
46
50
  async function handlePage({
@@ -50,7 +54,19 @@ async function handlePage({
50
54
  dir,
51
55
  logger,
52
56
  filter,
57
+ pathFilter,
53
58
  }: HandlePageInput) {
59
+ if (pathFilter) {
60
+ const shouldRender = await pathFilter(page);
61
+
62
+ if (!shouldRender) {
63
+ if (options.verbose) {
64
+ logger.info(`Skipping page ${page.pathname}.`);
65
+ }
66
+ return;
67
+ }
68
+ }
69
+
54
70
  // gets the absolute path to the HTML file. E.g. /home/user/project/dist/blog/index.html
55
71
  // fileURLToPath() converts the URL to a file path. Without it, the path would start with a leading slash on Windows
56
72
  // systems, resulting in an invalid path.
package/src/index.ts CHANGED
@@ -11,6 +11,7 @@ import type {
11
11
  RenderFunctionInput as _RenderFunctionInput,
12
12
  RenderFunction as _RenderFunction,
13
13
  FilterFunction as _FilterFunction,
14
+ PathFilterFunction as _PathFilterFunction,
14
15
  PageDetails as _PageDetails,
15
16
  SatoriWeight as _SatoriWeight,
16
17
  SatoriFontStyle as _SatoriFontStyle,
@@ -31,6 +32,7 @@ export type AstroBuildDoneHookInput = _AstroBuildDoneHookInput;
31
32
  export type RenderFunctionInput = _RenderFunctionInput;
32
33
  export type RenderFunction = _RenderFunction;
33
34
  export type FilterFunction = _FilterFunction;
35
+ export type PathFilterFunction = _PathFilterFunction;
34
36
  export type PageDetails = _PageDetails;
35
37
  export type SatoriWeight = _SatoriWeight;
36
38
  export type SatoriFontStyle = _SatoriFontStyle;
@@ -17,6 +17,7 @@ export function astroOpenGraphImages({
17
17
  options,
18
18
  render,
19
19
  filter,
20
+ pathFilter,
20
21
  }: IntegrationInput): AstroIntegration {
21
22
  const optionsWithDefaults: IntegrationOptions = { ...defaults, ...options };
22
23
 
@@ -29,6 +30,7 @@ export function astroOpenGraphImages({
29
30
  options: optionsWithDefaults,
30
31
  render,
31
32
  filter,
33
+ pathFilter,
32
34
  });
33
35
  },
34
36
  },
package/src/types.ts CHANGED
@@ -5,6 +5,7 @@ export type IntegrationInput = {
5
5
  options: PartialIntegrationOptions;
6
6
  render: RenderFunction;
7
7
  filter?: FilterFunction;
8
+ pathFilter?: PathFilterFunction;
8
9
  };
9
10
 
10
11
  /** When applied to PartialIntegrationOptions this type equals IntegrationOptions */
@@ -36,6 +37,9 @@ export type Page = {
36
37
  pathname: string;
37
38
  };
38
39
 
40
+ /** A function that filters pages before their output files are read */
41
+ export type PathFilterFunction = (input: Page) => Promise<boolean> | boolean;
42
+
39
43
  /** The input Astro passes to the build done hook */
40
44
  export type AstroBuildDoneHookInput = Parameters<
41
45
  BaseIntegrationHooks["astro:build:done"]
package/README.md.tmpl DELETED
@@ -1,308 +0,0 @@
1
- <div align="center">
2
- <picture>
3
- <source media="(prefers-color-scheme: dark)" srcset="https://cdn.rawgit.com/shepherdjerred/astro-opengraph-images/main/assets/logo-dark.png">
4
- <source media="(prefers-color-scheme: light)" srcset="https://cdn.rawgit.com/shepherdjerred/astro-opengraph-images/main/assets/logo-light.png">
5
- <img alt="project logo" src="https://cdn.rawgit.com/shepherdjerred/astro-opengraph-images/main/assets/logo-light.png" height=75>
6
- </picture>
7
-
8
- [![astro-opengraph-images](https://img.shields.io/npm/v/astro-opengraph-images.svg)](https://www.npmjs.com/package/astro-opengraph-images)
9
-
10
- Generate Open Graph images for your Astro site.
11
-
12
- This project is actively maintained. If you have a feature request or need help, please [create an issue](https://github.com/shepherdjerred/monorepo/issues/new).
13
-
14
- </div>
15
-
16
- ## What is Open Graph?
17
-
18
- [Open Graph](https://ogp.me/) is a protocol created by Facebook. It allows pages on your site to be richly embedded into other sites and applications.
19
-
20
- You've probably seen this in action when posting a link on Facebook, Twitter, Slack, iMessage, or Discord. Links posted in supported applications will display the Open Graph metadata which often includes an image. This library will generate those images for you.
21
-
22
- ## Features
23
-
24
- > [!WARNING]
25
- > This integration has only been tested with statically rendered sites. It is untested with server-side rendering.
26
-
27
- - Written in TypeScript
28
- - Generate Open Graph images for every page on your site.
29
- - Use a preset renderer to get started quickly.
30
- - Images are fully customizable using [Satori](https://github.com/vercel/satori).
31
- - Use React/JSX + Tailwind syntax or vanilla JavaScript to define your own custom images.
32
- - Supports both static pages and Astro content collections.
33
- - Pages can be written in Markdown, MDX, HTML, or any other format.
34
-
35
- ## Quick Start
36
-
37
- To better illustrate these steps, I've created a [video](https://www.loom.com/share/a66a65be8a4e48ec8612b78489db590d?sid=3e9f7cd6-68ec-49da-b942-75eb4608cb5e) following them to help others follow along.
38
-
39
- 1. Add this integration to your Astro config:
40
-
41
- - Option 1: use the `astro` command:
42
-
43
- ```bash
44
- npx astro add astro-opengraph-images
45
- ```
46
-
47
- - Option 2: install the package and add the integration to your Astro config:
48
-
49
- ```bash
50
- npm i astro-opengraph-images
51
- ```
52
-
53
- ```diff
54
- +import opengraphImages from "astro-opengraph-images";
55
-
56
- export default defineConfig({
57
- integrations: [
58
- + opengraphImages()
59
- ],
60
- });
61
- ```
62
-
63
- 1. Install React. React is used by the presets, and can be used to easily author custom images. Note that React is only used for generating the images and will not be shipped to clients.
64
-
65
- ```bash
66
- npm i -D react
67
- ```
68
-
69
- 1. Install the fonts you want to use. Fonts must be explicitly declared to be used for images. System fonts are _not_ available. For this quick start guide, we'll install the [Roboto](https://fontsource.org/fonts/roboto) font:
70
-
71
- ```bash
72
- npm i @fontsource/roboto
73
- ```
74
-
75
- You can find more fonts on [Fontsource](https://fontsource.org/), or you can use any font file that you have. See [Satori's font documentation](https://github.com/vercel/satori?tab=readme-ov-file#fonts) for more information.
76
-
77
- 1. Configure the integration in your Astro config:
78
-
79
- ```diff
80
- -import opengraphImages from "astro-opengraph-images";
81
- +import opengraphImages, { presets } from "astro-opengraph-images";
82
-
83
- export default defineConfig({
84
- integrations: [
85
- - opengraphImages()
86
- + opengraphImages({
87
- + options: {
88
- + fonts: [
89
- + {
90
- + name: "Roboto",
91
- + weight: 400,
92
- + style: "normal",
93
- + data: fs.readFileSync("node_modules/@fontsource/roboto/files/roboto-latin-400-normal.woff"),
94
- + },
95
- + ],
96
- + },
97
- + render: presets.blackAndWhite,
98
- + }),
99
- ],
100
- });
101
- ```
102
-
103
- 1. Set the [`site`](https://docs.astro.build/en/reference/configuration-reference/#site) property in your Astro config:
104
-
105
- Open Graph requires URLs to be absolute, including the domain your site is hosted at. This integration uses the site defined in your Astro config to create the correct URLs for Open Graph which is `site` must be defined.
106
-
107
- ```diff
108
- export default defineConfig({
109
- + site: "https://<your site>.com",
110
- integrations: [
111
- opengraphImages({
112
- options: {
113
- fonts: [
114
- {
115
- name: "Roboto",
116
- weight: 400,
117
- style: "normal",
118
- data: fs.readFileSync("node_modules/@fontsource/roboto/files/roboto-latin-400-normal.woff"),
119
- },
120
- ],
121
- },
122
- render: presets.blackAndWhite,
123
- }),
124
- ],
125
- });
126
- ```
127
-
128
- 1. Update your main Astro layout with the appropriate `meta` tags. The [Open Graph site](https://ogp.me/) has more information possible tags.
129
-
130
- The following `meta` tags must be defined:
131
-
132
- * `og:title`
133
- * This field may be used when generating images.
134
- * `og:type`
135
- * See the [Open Graph documentation](https://ogp.me/#types) for valid values.
136
- * `og:image`
137
- * Set this to the return value of `getImagePath` (example shown below).
138
- * If the value of `og:image` does not match what this integration expects then your site will fail to build. This will ensure your site is correctly configured to display Open Graph images.
139
- * `og:description`
140
- * Optional. This field may be used when generating images.
141
-
142
- Your site will fail to build if the tags above are not set.
143
-
144
- * Option 1: Use the [`astro-seo`](https://github.com/jonasmerlin/astro-seo) package:
145
-
146
- Install the `astro-seo` package:
147
-
148
- ```bash
149
- npm i astro-seo
150
- ```
151
-
152
- Update your Astro layout to use the `SEO` component:
153
-
154
- ```diff
155
- ---
156
- +import { SEO } from "astro-seo";
157
- +import { getImagePath } from "astro-opengraph-images";
158
-
159
- interface Props {
160
- title: string;
161
- }
162
-
163
- const { title } = Astro.props;
164
- +const { url, site } = Astro;
165
- +const openGraphImageUrl = getImagePath({ url, site });
166
- ---
167
-
168
- <!doctype html>
169
- <html lang="en">
170
- <head>
171
- <meta charset="UTF-8" />
172
- <meta name="description" content="Astro description" />
173
- <meta name="viewport" content="width=device-width" />
174
- <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
175
- <meta name="generator" content={Astro.generator} />
176
- <title>{title}</title>
177
-
178
- + <SEO
179
- + openGraph={
180
- + {
181
- + basic: {
182
- + title: title,
183
- + type: "website",
184
- + image: openGraphImageUrl,
185
- + url: url,
186
- + },
187
- + optional: {
188
- + description: "My page description",
189
- + },
190
- + }
191
- + }
192
- + />
193
- </head>
194
- <body>
195
- <slot />
196
- </body>
197
- </html>
198
- ```
199
-
200
- * Option 2: Manually add the `meta` tags to your Astro layout.
201
-
202
- 1. Build your site. You should see a `.png` file next to each `.html` page in your `dist` folder. Double-check that the `og:image` proprety in your `.html` file matches the path to the `.png` file.
203
-
204
- 1. Deploy your site. You can verify that your images are correct by:
205
-
206
- * Sending a link to your site in an application that supports Open Graph like iMessage, Slack, Discord, etc.
207
- * Visit [opengraph.xyz](http://opengraph.xyz/) and test your site.
208
-
209
- ## Examples
210
-
211
- There are example sites using this integration under [`examples/`](https://github.com/shepherdjerred/monorepo/tree/main/packages/astro-opengraph-images/examples).
212
-
213
- ### Sites Using This
214
-
215
- If you're using this project, [open a PR](https://github.com/shepherdjerred/monorepo/compare) to add your site to this list.
216
-
217
- * [sjer.red](https://sjer.red) ([source](https://github.com/shepherdjerred/monorepo))
218
-
219
- ## Filtering Pages
220
-
221
- By default, this integration generates an Open Graph image for every page in your build output. Pass an optional `filter` callback to skip pages — for example, the 404 page, utility routes, or sections where images don't apply.
222
-
223
- The callback receives the same `RenderFunctionInput` as your renderer (pathname, parsed DOM, Open Graph metadata). Return `true` to render the image, `false` to skip it. The callback may be synchronous or return a `Promise<boolean>`.
224
-
225
- ```diff
226
- import opengraphImages, { presets } from "astro-opengraph-images";
227
-
228
- export default defineConfig({
229
- integrations: [
230
- opengraphImages({
231
- options: { fonts: [/* ... */] },
232
- render: presets.blackAndWhite,
233
- + // Only generate images for blog posts
234
- + filter: ({ pathname }) => pathname.startsWith("/blog/"),
235
- }),
236
- ],
237
- });
238
- ```
239
-
240
- More examples:
241
-
242
- ```typescript
243
- // Exclude utility pages
244
- filter: ({ pathname }) => !["/404/", "/"].includes(pathname);
245
-
246
- // Filter on DOM content or async data
247
- filter: async ({ document, pathname }) =>
248
- document.querySelector('meta[property="og:image"]') !== null &&
249
- pathname.startsWith("/blog/");
250
- ```
251
-
252
- When `verbose: true` is set on `options`, skipped pages are logged.
253
-
254
- ## Custom Renderers
255
-
256
- You can create your own custom images with a render function. Take a look at how [a preset](https://github.com/shepherdjerred/monorepo/blob/main/packages/astro-opengraph-images/src/presets/blackAndWhite.tsx) works.
257
-
258
- Renderers have access to the page's DOM using [jsdom](https://github.com/jsdom/jsdom). You can use this to render your Open Graph image using any of the content from the associated HTML page. An example of this is shown in the [custom property preset](https://github.com/shepherdjerred/monorepo/blob/main/packages/astro-opengraph-images/src/presets/customProperty.tsx) which shows a preview of the page's body text in the Open Graph image.
259
-
260
- This library uses [Satori](https://github.com/vercel/satori) to convert React components to SVG. The SVG is then converted to a PNG using [resvg-js](https://github.com/yisibl/resvg-js).
261
-
262
- > [!TIP]
263
- > Satori supports [a subset of CSS](https://github.com/vercel/satori?tab=readme-ov-file#css). Be sure to familiarize yourself with its limitations.
264
- >
265
- > You can use the [Satori playground](https://og-playground.vercel.app/) to work on your images.
266
- >
267
- > You can use Tailwind syntax with [tw-to-css](https://github.com/vinicoder/tw-to-css). An example is the [Tailwind preset](https://github.com/shepherdjerred/monorepo/blob/main/packages/astro-opengraph-images/src/presets/tailwind.tsx). You'll need to install this package yourself.
268
-
269
- ## Presets
270
-
271
- Presets are located in [`src/presets/`](https://github.com/shepherdjerred/monorepo/tree/main/packages/astro-opengraph-images/src/presets). [Open a pull request](https://github.com/shepherdjerred/monorepo/compare) to contribute a preset you've created.
272
-
273
- Note: some presets use the [`tw-to-css`](https://github.com/vinicoder/tw-to-css) library. You'll need to install this dependency separately when using one of these presets. You'll see an error if the library is not already installed.
274
-
275
- ```bash
276
- npm i tw-to-css
277
- ```
278
-
279
- {{ range $preset := (ds "presets") -}}
280
- ### `{{ strings.ReplaceAll ".png" "" $preset }}`
281
-
282
- ```diff
283
- import opengraphImages, { presets } from "astro-opengraph-images";
284
-
285
- export default defineConfig({
286
- integrations: [
287
- opengraphImages({
288
- + render: presets.{{ strings.ReplaceAll ".png" "" $preset }},
289
- }),
290
- ],
291
- });
292
- ```
293
-
294
- ![](assets/presets/{{ $preset }})
295
-
296
- {{ end }}
297
-
298
- ## Alternatives
299
-
300
- Here are some similar libraries using Satori and Astro. I haven't done a feature comparison.
301
-
302
- - https://github.com/florian-lefebvre/satori-astro (This library looks excellent)
303
- - https://github.com/delucis/astro-og-canvas (Doesn't allow arbitrary layouts)
304
- - https://github.com/thewebforge/astro-og-images (Only allows you to choose from a list of templates)
305
- - https://github.com/tomaskebrle/astro-og-image (Seems limited)
306
- - https://github.com/cijiugechu/astro-satori (Possibly dead, hasn't been updated in a year)
307
- - https://github.com/kevinzunigacuellar/astro-satori (Possibly dead, hasn't been updated in a year)
308
- - https://github.com/rumaan/astro-vercel-og (Possibly dead, hasn't been updated in a year)