@mannisto/astro-metadata 1.0.1 → 2.0.0-alpha.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.
Files changed (102) hide show
  1. package/README.md +427 -82
  2. package/package.json +35 -22
  3. package/src/assets/icons/LICENSE.md +35 -0
  4. package/src/assets/icons/binoculars.svg +1 -0
  5. package/src/assets/icons/caret-right.svg +1 -0
  6. package/src/assets/icons/check.svg +1 -0
  7. package/src/assets/icons/copy.svg +1 -0
  8. package/src/assets/icons/seal-check-duotone.svg +1 -0
  9. package/src/assets/icons/seal-question-duotone.svg +1 -0
  10. package/src/assets/icons/warning-circle-duotone.svg +1 -0
  11. package/src/assets/icons/warning-duotone.svg +1 -0
  12. package/src/assets/icons/x.svg +1 -0
  13. package/src/components/Metadata.astro +145 -0
  14. package/src/components/index.ts +2 -0
  15. package/src/integration.d.ts +112 -0
  16. package/src/integration.js +508 -0
  17. package/src/lib/checks.d.ts +70 -0
  18. package/src/lib/checks.js +193 -0
  19. package/src/lib/config.ts +111 -0
  20. package/src/lib/favicon-files.d.ts +29 -0
  21. package/src/lib/favicon-files.js +103 -0
  22. package/src/lib/favicon-image.ts +124 -0
  23. package/src/lib/favicon.ts +167 -0
  24. package/src/lib/head.d.ts +25 -0
  25. package/src/lib/head.js +141 -0
  26. package/src/lib/manifest.ts +126 -0
  27. package/src/lib/rules.d.ts +73 -0
  28. package/src/lib/rules.js +902 -0
  29. package/src/lib/schema.ts +208 -0
  30. package/src/lib/site.ts +72 -0
  31. package/src/lib/url.ts +79 -0
  32. package/src/routes/favicon.ts +14 -0
  33. package/src/routes/manifest.ts +18 -0
  34. package/src/runtime.ts +16 -0
  35. package/src/toolbar/app.ts +72 -0
  36. package/src/toolbar/assets.ts +81 -0
  37. package/src/toolbar/data.ts +282 -0
  38. package/src/toolbar/define.ts +7 -0
  39. package/src/toolbar/env.d.ts +10 -0
  40. package/src/toolbar/highlight.ts +83 -0
  41. package/src/toolbar/styles/shared.css +18 -0
  42. package/src/toolbar/styles/tokens.css +182 -0
  43. package/src/toolbar/toolbar-badge.ts +78 -0
  44. package/src/toolbar/ui/alert/alert.css +126 -0
  45. package/src/toolbar/ui/alert/alert.ts +102 -0
  46. package/src/toolbar/ui/badge/badge.css +41 -0
  47. package/src/toolbar/ui/badge/badge.ts +24 -0
  48. package/src/toolbar/ui/code-block/code-block.css +90 -0
  49. package/src/toolbar/ui/code-block/code-block.ts +68 -0
  50. package/src/toolbar/ui/color-swatch/color-swatch.css +15 -0
  51. package/src/toolbar/ui/color-swatch/color-swatch.ts +31 -0
  52. package/src/toolbar/ui/icon/icon.css +11 -0
  53. package/src/toolbar/ui/icon/icon.ts +46 -0
  54. package/src/toolbar/ui/image/image.css +18 -0
  55. package/src/toolbar/ui/image/image.ts +58 -0
  56. package/src/toolbar/ui/inline-code/inline-code.css +16 -0
  57. package/src/toolbar/ui/inline-code/inline-code.ts +41 -0
  58. package/src/toolbar/ui/section/section.css +88 -0
  59. package/src/toolbar/ui/section/section.ts +78 -0
  60. package/src/toolbar/ui/tab/tab.css +7 -0
  61. package/src/toolbar/ui/tab/tab.ts +47 -0
  62. package/src/toolbar/ui/tab-group/tab-group.css +42 -0
  63. package/src/toolbar/ui/tab-group/tab-group.ts +101 -0
  64. package/src/toolbar/ui/table/table.css +124 -0
  65. package/src/toolbar/ui/table/table.ts +155 -0
  66. package/src/toolbar/views/code-blocks.ts +35 -0
  67. package/src/toolbar/views/metadata-panel.css +101 -0
  68. package/src/toolbar/views/metadata-panel.ts +125 -0
  69. package/src/toolbar/views/overview-view.css +27 -0
  70. package/src/toolbar/views/overview-view.ts +401 -0
  71. package/src/toolbar/views/previews/discord.css +70 -0
  72. package/src/toolbar/views/previews/discord.ts +60 -0
  73. package/src/toolbar/views/previews/facebook.css +65 -0
  74. package/src/toolbar/views/previews/facebook.ts +36 -0
  75. package/src/toolbar/views/previews/google.css +75 -0
  76. package/src/toolbar/views/previews/google.ts +36 -0
  77. package/src/toolbar/views/previews/linkedin.css +58 -0
  78. package/src/toolbar/views/previews/linkedin.ts +34 -0
  79. package/src/toolbar/views/previews/page.ts +129 -0
  80. package/src/toolbar/views/previews/platform.ts +14 -0
  81. package/src/toolbar/views/previews/previews-view.css +37 -0
  82. package/src/toolbar/views/previews/previews-view.ts +52 -0
  83. package/src/toolbar/views/previews/slack.css +61 -0
  84. package/src/toolbar/views/previews/slack.ts +47 -0
  85. package/src/toolbar/views/previews/twitter.css +89 -0
  86. package/src/toolbar/views/previews/twitter.ts +57 -0
  87. package/src/toolbar/views/previews/whatsapp.css +88 -0
  88. package/src/toolbar/views/previews/whatsapp.ts +65 -0
  89. package/src/toolbar/views/raw-view.ts +41 -0
  90. package/index.ts +0 -31
  91. package/src/components/Canonical.astro +0 -11
  92. package/src/components/Description.astro +0 -9
  93. package/src/components/Favicon.astro +0 -88
  94. package/src/components/Head.astro +0 -96
  95. package/src/components/Keywords.astro +0 -9
  96. package/src/components/LanguageAlternates.astro +0 -18
  97. package/src/components/OpenGraph.astro +0 -75
  98. package/src/components/Robots.astro +0 -25
  99. package/src/components/Schema.astro +0 -9
  100. package/src/components/Title.astro +0 -10
  101. package/src/components/Twitter.astro +0 -25
  102. package/src/lib/metadata.ts +0 -77
package/README.md CHANGED
@@ -2,131 +2,476 @@
2
2
 
3
3
  ![banner](./assets/banner.png)
4
4
 
5
- ![npm version](https://img.shields.io/npm/v/@mannisto/astro-metadata)
6
- ![license](https://img.shields.io/badge/license-MIT-green)
7
- ![astro peer dependency](https://img.shields.io/npm/dependency-version/@mannisto/astro-metadata/peer/astro)
5
+ ![Astro](https://img.shields.io/badge/astro-%232C2052.svg?style=for-the-badge&logo=astro&logoColor=white)
6
+ ![npm version](https://img.shields.io/npm/v/@mannisto/astro-metadata?style=for-the-badge)
7
+ ![license](https://img.shields.io/badge/license-MIT-green?style=for-the-badge)
8
8
 
9
- Astro components for managing your page head — metadata, social sharing, favicons, and SEO.
9
+ The page metadata for Astro: the title, the description, the canonical URL, social cards, favicons, the web app manifest and structured data. Set the site values one time, and give each page only its own values.
10
10
 
11
- ## Installation
12
- ```bash
13
- # pnpm
14
- pnpm add @mannisto/astro-metadata
11
+ - [Install](#install)
12
+ - [Configuration](#configuration)
13
+ - [Metadata component](#metadata-component)
14
+ - [Favicons](#favicons)
15
+ - [Web app manifest](#web-app-manifest)
16
+ - [Structured data](#structured-data)
17
+ - [Checks](#checks)
18
+ - [API](#api)
19
+ - [Good to know](#good-to-know)
20
+ - [Migrate from v1](#migrate-from-v1)
21
+ - [Contributing](#contributing)
22
+ - [License](#license)
23
+
24
+ ## Install
15
25
 
16
- # npm
26
+ ```bash
17
27
  npm install @mannisto/astro-metadata
28
+ ```
29
+
30
+ The integration requires Astro 6.4.6 or later, or Astro 7.
31
+
32
+ ## Configuration
33
+
34
+ ```typescript
35
+ // astro.config.ts
36
+ import metadata from "@mannisto/astro-metadata"
37
+ import { defineConfig } from "astro/config"
38
+
39
+ export default defineConfig({
40
+ site: "https://example.com",
41
+ integrations: [
42
+ metadata({
43
+ siteName: "Acme Studio",
44
+ titleTemplate: "%s | Acme Studio",
45
+ description: "Tools for small teams.",
46
+ image: { url: "/og.jpg", alt: "Acme Studio", width: 1200, height: 630 },
47
+ twitter: { site: "@acmestudio" },
48
+ themeColor: "#111111",
49
+ feeds: [{ href: "/rss.xml", title: "News" }],
50
+ favicon: { source: "./src/assets/logo.svg" },
51
+ manifest: { name: "Acme Studio" },
52
+ }),
53
+ ],
54
+ })
55
+ ```
56
+
57
+ Set `site` in the Astro config. The canonical URL and the social images must be absolute URLs, and the package uses `site` to make them.
58
+
59
+ | Option | Default | Description |
60
+ |---|---|---|
61
+ | `siteName` | — | The name of the site: the title of a page without a title, and `og:site_name`. |
62
+ | `titleTemplate` | — | The title template, for example `"%s \| My Site"`. `%s` is the page title. |
63
+ | `description` | — | The description of a page without a description. |
64
+ | `image` | — | The social image of a page without an image: `url`, `alt`, `width` and `height`. |
65
+ | `twitter` | — | `site`: the Twitter (X) handle of the site. `card`: `"summary"` or `"summary_large_image"` (default) for pages with an image. |
66
+ | `themeColor` | — | The color of the browser interface. Give one color, or `{ light, dark }`. |
67
+ | `feeds` | — | The RSS, Atom or JSON feeds: `href`, `title` and `type` (`"rss"` by default). |
68
+ | `robots` | — | `index: false` or `follow: false` for all pages, for example on a staging site. `extra`: other directives, for example `"max-image-preview:large"`. |
69
+ | `favicon` | — | Generates the favicons from one image. See [Favicons](#favicons). |
70
+ | `manifest` | — | Generates the web app manifest, or links your own. See [Web app manifest](#web-app-manifest). |
71
+ | `debug` | `true` | The Metadata app in the dev toolbar (`client`) and the checks in the build log (`build`). Set `false` to turn off both, or `{ client: false }` or `{ build: false }` to turn off one. See [Checks](#checks). |
72
+ | `rules` | — | `ignore`: the ids of the check rules to turn off. `custom`: your own rules. See [Checks](#checks). |
73
+ | `pages` | — | `ignore`: the pages without checks, as path patterns, for example `["/404", "/drafts/**"]`. See [Checks](#checks). |
18
74
 
19
- # yarn
20
- yarn add @mannisto/astro-metadata
75
+ ### Translations
76
+
77
+ Each text can be one string, or an object with one string for each locale:
78
+
79
+ ```typescript
80
+ metadata({
81
+ siteName: { en: "Acme Studio", fi: "Acme Studio Suomi" },
82
+ titleTemplate: { en: "%s | Acme Studio", fi: "%s | Acme Studio Suomi" },
83
+ })
21
84
  ```
22
85
 
23
- ## Usage
86
+ `<Metadata>` uses the text of `Astro.currentLocale`, or of its `locale` prop. A locale without a text uses the first text.
24
87
 
25
- There are three ways to use this package. Pick what suits your project, or combine them freely.
88
+ ## Metadata component
26
89
 
27
- ### Head component
90
+ Put `<Metadata>` into the `<head>` of your layout, and give it the values of the page:
28
91
 
29
- The simplest approach — use `Head` in your layout and pass props down from pages. Title, description and image flow into Open Graph and Twitter automatically.
30
92
  ```astro
31
93
  ---
32
- import { Head } from "@mannisto/astro-metadata"
94
+ // src/layouts/Layout.astro
95
+ import { Metadata, type MetadataProps } from "@mannisto/astro-metadata/components"
96
+
97
+ type Props = MetadataProps
33
98
  ---
34
99
 
35
- <html>
36
- <Head
37
- title="Home"
38
- titleTemplate="%s | My Site"
39
- description="Welcome to my site"
40
- image={{ url: "/og.jpg", alt: "My Site", width: 1200, height: 630 }}
41
- />
100
+ <html lang="en">
101
+ <head>
102
+ <Metadata {...Astro.props} />
103
+ </head>
42
104
  <body>
43
105
  <slot />
44
106
  </body>
45
107
  </html>
46
108
  ```
47
109
 
48
- [Read more →](docs/usage/head.md)
49
-
50
- ### Metadata API
51
-
52
- Set metadata in pages, resolve in layouts — no prop drilling.
53
110
  ```astro
54
111
  ---
55
- // pages/about.astro
56
- import { Metadata } from "@mannisto/astro-metadata"
112
+ // src/pages/about.astro
113
+ import Layout from "../layouts/Layout.astro"
114
+ ---
115
+
116
+ <Layout title="About" description="The story of Acme Studio.">
117
+ <h1>About</h1>
118
+ </Layout>
119
+ ```
120
+
121
+ The page values override the site values. For example, `title="About"` gives:
57
122
 
58
- Metadata.set({
59
- title: "About",
60
- description: "Learn more about us",
123
+ ```html
124
+ <meta charset="UTF-8">
125
+ <meta name="viewport" content="width=device-width, initial-scale=1">
126
+ <title>About | Acme Studio</title>
127
+ <meta name="description" content="The story of Acme Studio.">
128
+ <link rel="canonical" href="https://example.com/about/">
129
+ <meta property="og:title" content="About">
130
+ <meta property="og:description" content="The story of Acme Studio.">
131
+ <meta property="og:url" content="https://example.com/about/">
132
+ <meta property="og:type" content="website">
133
+ <meta property="og:site_name" content="Acme Studio">
134
+ <meta property="og:image" content="https://example.com/og.jpg">
135
+ <meta name="twitter:card" content="summary_large_image">
136
+ <meta name="twitter:site" content="@acmestudio">
137
+ ...
138
+ ```
139
+
140
+ | Prop | Default | Description |
141
+ |---|---|---|
142
+ | `title` | `siteName` | The page title. The title template adds the site name. |
143
+ | `description` | `description` | The page description. |
144
+ | `image` | `image` | The social image: `url`, `alt`, `width` and `height`. |
145
+ | `type` | `"website"` | The Open Graph type, for example `"article"`. |
146
+ | `author` | — | The Twitter (X) handle of the author, for example `"@acmewriter"`. |
147
+ | `canonical` | The current page | The canonical path or URL. |
148
+ | `index` | `true` | `false` keeps the page out of search results. |
149
+ | `follow` | `true` | `false` tells search engines not to follow the links. |
150
+ | `hreflang` | — | The hreflang links: the page in each locale, and `x-default`: `{ hreflang, href }[]`. |
151
+ | `schema` | — | Structured data. See [Structured data](#structured-data). |
152
+ | `locale` | `Astro.currentLocale` | The locale of the translated site values. |
153
+
154
+ A tag is rendered only when it has a value. For example, the robots tag is necessary only with `index` or `follow` set to `false`.
155
+
156
+ ## Favicons
157
+
158
+ Give one square source image. An SVG gives the best result. Use a PNG, WebP or JPEG of at least 512 × 512 px, or the large icons become blurred.
159
+
160
+ ```typescript
161
+ metadata({
162
+ favicon: {
163
+ source: "./src/assets/logo.svg",
164
+ background: "#ffffff",
165
+ },
61
166
  })
62
- ---
63
167
  ```
64
- ```astro
65
- ---
66
- // layouts/Layout.astro
67
- import { Head, Metadata } from "@mannisto/astro-metadata"
68
168
 
69
- const meta = Metadata.resolve({
70
- title: "My Site",
71
- titleTemplate: "%s | My Site",
169
+ The integration generates these files, and `<Metadata>` renders their tags in the recommended order:
170
+
171
+ | File | Use |
172
+ |---|---|
173
+ | `/favicon.ico` | 16 and 32 px, for old browsers and tools |
174
+ | `/favicon.svg` | Modern browsers. Only for an SVG source. |
175
+ | `/favicon-16.png`, `/favicon-32.png` | Browser tabs |
176
+ | `/apple-touch-icon.png` | 180 px, for the iOS home screen |
177
+ | `/favicon-192.png`, `/favicon-512.png` | The web app manifest |
178
+
179
+ iOS shows transparent pixels as black, so the Apple icon shows the logo on the `background` color (white by default).
180
+
181
+ To generate other files, give a `files` list. It replaces the default files:
182
+
183
+ ```typescript
184
+ favicon: {
185
+ source: "./src/assets/logo.svg",
186
+ files: [
187
+ { path: "/favicon.ico", sizes: [16, 32, 48] },
188
+ { path: "/favicon.svg" },
189
+ { path: "/apple-touch-icon.png", size: 180 },
190
+ { path: "/favicon-96.png", size: 96 },
191
+ { path: "/favicon-512.png", size: 512 },
192
+ ],
193
+ }
194
+ ```
195
+
196
+ The extension sets the format: `.ico`, `.png` or `.svg`. The name and the size set the use:
197
+
198
+ - `.ico` and `.svg` files, and `.png` files smaller than 192 px, get an icon link tag.
199
+ - `apple-touch-icon.png` gets the Apple link tag.
200
+ - `.png` files of 192 px or larger go into the web app manifest.
201
+
202
+ Keep the names `favicon.ico` and `apple-touch-icon.png`. Browsers and iOS ask for these URLs directly.
203
+
204
+ ## Web app manifest
205
+
206
+ Give the manifest as an object, and the integration serves it at `/manifest.webmanifest` with the generated icons:
207
+
208
+ ```typescript
209
+ metadata({
210
+ manifest: {
211
+ name: "Acme Studio",
212
+ shortName: "Acme",
213
+ backgroundColor: "#ffffff",
214
+ },
72
215
  })
73
- ---
216
+ ```
74
217
 
75
- <html>
76
- <Head {...meta} />
77
- <body>
78
- <slot />
79
- </body>
80
- </html>
218
+ | Value | Default | Description |
219
+ |---|---|---|
220
+ | `name` | — | The name of the app. |
221
+ | `shortName` | — | The name on the home screen. |
222
+ | `description` | — | The description of the app. |
223
+ | `startUrl` | `"/"` | The page that the installed app opens. |
224
+ | `display` | `"standalone"` | `"standalone"`, `"fullscreen"`, `"minimal-ui"` or `"browser"`. |
225
+ | `themeColor` | `themeColor` | The color of the app interface. |
226
+ | `backgroundColor` | — | The color of the start screen. |
227
+ | `extra` | — | Other manifest fields, copied into the file unchanged. |
228
+
229
+ With translated texts, each locale gets its own manifest: the first locale at `/manifest.webmanifest`, the others at `/fi/manifest.webmanifest` and so on.
230
+
231
+ To use your own manifest, give its path. The integration then only renders the link tag:
232
+
233
+ ```typescript
234
+ metadata({
235
+ manifest: "/site.webmanifest",
236
+ })
81
237
  ```
82
238
 
83
- [Read more →](docs/usage/metadata.md)
239
+ Your own manifest endpoint can use the generated icons with `Favicon.icons()`.
240
+
241
+ ## Structured data
84
242
 
85
- ### Individual components
243
+ The `Schema` builders make the most common JSON-LD types. They fill in the site name and make all URLs absolute.
86
244
 
87
- Use components directly in your `<head>` for full control.
88
245
  ```astro
89
246
  ---
90
- import { Title, Description, OpenGraph } from "@mannisto/astro-metadata"
247
+ import { Metadata } from "@mannisto/astro-metadata/components"
248
+ import { Schema } from "@mannisto/astro-metadata/runtime"
91
249
  ---
92
250
 
93
- <html>
94
- <head>
95
- <meta charset="UTF-8" />
96
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
97
- <Title value="My Page" template="%s | My Site" />
98
- <Description value="Welcome to my site" />
99
- <OpenGraph
100
- title="My Page"
101
- image={{ url: "/og.jpg", alt: "My Site" }}
102
- />
103
- </head>
104
- <body>
105
- <slot />
106
- </body>
107
- </html>
251
+ <Metadata
252
+ title="Hello"
253
+ type="article"
254
+ schema={[
255
+ Schema.article({
256
+ title: "Hello",
257
+ published: new Date("2026-09-01"),
258
+ author: { name: "Acme Writer", url: "/about" },
259
+ url: "/blog/hello",
260
+ }),
261
+ Schema.breadcrumbs([
262
+ { name: "Home", url: "/" },
263
+ { name: "Blog", url: "/blog" },
264
+ ]),
265
+ ]}
266
+ />
108
267
  ```
109
268
 
110
- [Read more →](docs/usage/components.md)
269
+ The `schema` prop also takes your own objects. The types come from [`schema-dts`](https://github.com/google/schema-dts), so your editor completes the schema.org fields.
270
+
271
+ ## Checks
111
272
 
112
- ## Components
273
+ The integration checks the metadata of each page. Each problem has a level:
113
274
 
114
- - [Canonical](docs/components/canonical.md)
115
- - [Description](docs/components/description.md)
116
- - [Favicon](docs/components/favicon.md)
117
- - [Head](docs/components/head.md)
118
- - [Keywords](docs/components/keywords.md)
119
- - [LanguageAlternates](docs/components/language-alternates.md)
120
- - [OpenGraph](docs/components/open-graph.md)
121
- - [Robots](docs/components/robots.md)
122
- - [Schema](docs/components/schema.md)
123
- - [Title](docs/components/title.md)
124
- - [Twitter](docs/components/twitter.md)
275
+ | Level | Meaning | Examples |
276
+ | --- | --- | --- |
277
+ | Error | The metadata is broken. | No title, placeholder text such as `undefined`, two canonical links, invalid JSON-LD, an `og:image` that does not load |
278
+ | Warning | Fix it for better results. | A long title, the same title on two pages, no `twitter:card`, an `og:image` over 600 KB, an hreflang page that does not link back |
279
+ | Info | Good to know. | `noindex`, a short description, no `og:image:alt` |
280
+
281
+ The checks look at three things:
282
+
283
+ - **The head of each page:** the tags, the structured data and the hreflang links. A page with `noindex` gets no search and sharing checks.
284
+ - **The files of each page:** the `og:image` and the favicons. The checks load them, and compare the real image size, format and file size with the tags. Files on other sites are not checked.
285
+ - **All pages together:** the same title or description on two pages, and hreflang links that do not link back. Only the build can compare the pages.
286
+
287
+ The results show in two places:
288
+
289
+ - **In dev:** Open the Metadata app in the Astro dev toolbar. It shows the problems of the current page. Click a problem to open the row of its tag. The app also shows the link previews of Google, Twitter (X), Facebook, LinkedIn, WhatsApp, Discord and Slack, and all metadata tags.
290
+ - **In the build:** The integration logs the errors and the warnings of the prerendered pages:
291
+
292
+ ```
293
+ [WARN] [@mannisto/astro-metadata] /about/index.html
294
+ - The page has no description.
295
+ ```
296
+
297
+ ### Turn off rules
298
+
299
+ All rules and their ids are in [`src/lib/rules.js`](./src/lib/rules.js). Give the ids of the rules to turn off in `rules.ignore`. This applies to the dev toolbar app and to the build:
300
+
301
+ ```typescript
302
+ metadata({
303
+ rules: {
304
+ ignore: ["description-short", "og-image-alt-missing"],
305
+ },
306
+ })
307
+ ```
308
+
309
+ ### Custom rules
310
+
311
+ Add your own rules in `rules.custom`. A rule has an `id`, a `level` (`"error"`, `"warning"` or `"info"`), a `message`, and a `check` function that returns `true` when the page has the problem:
312
+
313
+ ```typescript
314
+ metadata({
315
+ rules: {
316
+ custom: [
317
+ {
318
+ id: "title-brand",
319
+ level: "warning",
320
+ message: "The title must contain the brand name.",
321
+ check(head) {
322
+ return !head.titles[0]?.includes("Acme Studio")
323
+ },
324
+ },
325
+ ],
326
+ },
327
+ })
328
+ ```
329
+
330
+ The `head` has the metadata of the page:
331
+
332
+ | Value | Description |
333
+ |---|---|
334
+ | `titles` | The texts of all title tags. |
335
+ | `meta` | The first `content` of each meta tag, by its `name` or `property`, for example `head.meta["og:image"]`. |
336
+ | `metaCount` | The number of meta tags with each name. |
337
+ | `links` | The attributes of all link tags, for example `{ rel: "canonical", href: "…" }`. |
338
+ | `schemas` | The texts of the JSON-LD scripts. |
339
+ | `lang`, `charset` | The `lang` attribute of the html tag, and the charset. |
340
+
341
+ The rule can also have:
342
+ - `message` as a function, `(head) => string`, for a message with values of the page.
343
+ - `field`, the tag of the problem, for example `"og:title"`. A click on the problem in the dev toolbar app opens its row.
344
+ - `indexed: true`, to skip the rule on a noindex page.
345
+
346
+ The custom rules run in the build and in the dev toolbar app. In dev, the dev server runs them, because they are functions in the Astro config. Text between backticks in a message shows as code in the dev toolbar app. The `MetadataRule` type gives your editor completion.
347
+
348
+ ### Ignore pages
349
+
350
+ Some pages do not need checks, for example a 404 page or the drafts. Give their paths in `pages.ignore`, without the base. In a pattern, `*` matches one part of the path, and `**` matches any number of parts:
351
+
352
+ ```typescript
353
+ metadata({
354
+ pages: {
355
+ ignore: ["/404", "/drafts/**"],
356
+ },
357
+ })
358
+ ```
359
+
360
+ The build does not check these pages, and does not compare them with the other pages. The dev toolbar app shows all values of these pages, but no problems.
361
+
362
+ Set `debug: { build: false }` to hide the build warnings, `debug: { client: false }` to remove the dev toolbar app, and `debug: false` to turn off both.
363
+
364
+ ## API
365
+
366
+ ```typescript
367
+ import { Favicon, Manifest, Schema, Site } from "@mannisto/astro-metadata/runtime"
368
+ ```
369
+
370
+ | Method | Returns | Description |
371
+ |---|---|---|
372
+ | `Site.config` | `MetadataConfig` | The site values of the integration |
373
+ | `Site.name("fi")` | `string` | The site name of a locale |
374
+ | `Site.title("About", "fi")` | `string` | The page title in the title template |
375
+ | `Site.url("/og.jpg")` | `string` | The absolute URL of a path |
376
+ | `Site.pageUrl("/about")` | `string` | The absolute URL of a page, with the trailing slash of Astro |
377
+ | `Favicon.config` | `FaviconConfig` | The favicon config, or `undefined` without the `favicon` option |
378
+ | `Favicon.files` | `ResolvedFaviconFile[]` | The generated files |
379
+ | `Favicon.links()` | `FaviconLink[]` | The link tags of the generated files |
380
+ | `Favicon.icons()` | `ManifestIcon[]` | The icons for a web app manifest |
381
+ | `Favicon.url("/favicon.ico")` | `string` | The URL of a file, with the base and a hash |
382
+ | `Favicon.generate("/favicon-32.png")` | `Promise<Response>` | Generates a file of the config |
383
+ | `Manifest.config` | `ManifestConfig \| string` | The `manifest` option |
384
+ | `Manifest.url("fi")` | `string` | The URL of the manifest of a locale |
385
+ | `Manifest.build("fi")` | `object` | The manifest of a locale |
386
+ | `Manifest.paths()` | `ManifestPath[]` | The generated manifest files |
387
+ | `Schema.website()` | `WebSite` | The website, with the site name and URL |
388
+ | `Schema.organization()` | `Organization` | The organization: `name`, `url`, `logo`, `sameAs` |
389
+ | `Schema.article()` | `Article` | An article: `title`, `description`, `image`, `published`, `modified`, `author`, `url`, `type` |
390
+ | `Schema.breadcrumbs()` | `BreadcrumbList` | The path to the current page: `{ name, url }[]` |
391
+ | `Schema.stringify(data)` | `string` | JSON that is safe inside a script tag |
392
+
393
+ | Component | Props |
394
+ |---|---|
395
+ | `<Metadata>` | See [Metadata component](#metadata-component) |
396
+
397
+ Import the types from `@mannisto/astro-metadata/runtime`, for example `import type { MetadataImage } from "@mannisto/astro-metadata/runtime"`.
398
+
399
+ ## Good to know
400
+
401
+ - **CMS data.** Give CMS values to `<Metadata>` as props. The site values of the integration are fixed at startup, and a change restarts the dev server.
402
+ - **Trailing slash.** The canonical URL and the `hreflang` links follow the `trailingSlash` setting of Astro. With `"ignore"`, `build.format` decides.
403
+ - **`sharp`.** The favicons use the `sharp` package that Astro installs. If you install Astro without its optional dependencies, install `sharp` yourself.
404
+ - **Files in `public/`.** A file in `public/` with the same path as a generated favicon replaces the generated file. The integration shows a warning.
405
+ - **With @mannisto/astro-i18n.** Give the locale and the hreflang links to `<Metadata>`:
406
+
407
+ ```astro
408
+ ---
409
+ import { Locale } from "@mannisto/astro-i18n/runtime"
410
+ import { Metadata } from "@mannisto/astro-metadata/components"
411
+
412
+ const { code } = Locale.use(Astro)
413
+ ---
414
+
415
+ <Metadata locale={code} hreflang={Locale.hreflang(Astro.url, Astro.site!)} />
416
+ ```
417
+
418
+ ## Migrate from v1
419
+
420
+ Version 2 is a new API. The main changes:
421
+
422
+ ### Move the site values into the integration
423
+
424
+ ```diff
425
+ - <Head title="About" titleTemplate="%s | My Site" description="..." />
426
+ + metadata({ titleTemplate: "%s | My Site", description: "..." })
427
+ ```
428
+
429
+ ### Use `<Metadata>` inside your own `<head>`
430
+
431
+ `<Head>` rendered the `<head>` element. `<Metadata>` renders only the tags.
432
+
433
+ ```diff
434
+ - import { Head } from "@mannisto/astro-metadata"
435
+ + import { Metadata } from "@mannisto/astro-metadata/components"
436
+
437
+ - <Head title="About" />
438
+ + <head>
439
+ + <Metadata title="About" />
440
+ + </head>
441
+ ```
442
+
443
+ ### Replace the removed components
444
+
445
+ `Title`, `Description`, `Canonical`, `Robots`, `OpenGraph`, `Twitter`, `Favicon`, `LanguageAlternates`, `Keywords` and `Schema` are removed. `<Metadata>` renders their tags.
446
+
447
+ ```diff
448
+ - <LanguageAlternates alternates={links} />
449
+ - <Schema schema={data} />
450
+ + <Metadata hreflang={links} schema={data} />
451
+ ```
452
+
453
+ ### Replace the `Metadata` store
454
+
455
+ `Metadata.set()` and `Metadata.resolve()` are removed. Give the values as props to your layout.
456
+
457
+ ### Replace the robots object and the `false` props
458
+
459
+ ```diff
460
+ - <Head robots={{ index: false }} twitter={false} />
461
+ + <Metadata index={false} />
462
+ ```
463
+
464
+ ### Generate the favicons
465
+
466
+ ```diff
467
+ - <Head favicon={{ icons: [{ path: "/favicon.ico" }] }} />
468
+ + metadata({ favicon: { source: "./src/assets/logo.svg" } })
469
+ ```
125
470
 
126
471
  ## Contributing
127
472
 
128
- See [CONTRIBUTING.md](CONTRIBUTING.md) for setup and contribution guidelines.
473
+ Read [CONTRIBUTING.md](./CONTRIBUTING.md) for the setup and the test commands. Report bugs and ideas in [GitHub issues](https://github.com/eremannisto/astro-metadata/issues).
129
474
 
130
475
  ## License
131
476
 
132
- MIT © [Ere Männistö](https://github.com/eremannisto)
477
+ MIT © [Ere Männistö](https://github.com/eremannisto)
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@mannisto/astro-metadata",
3
- "version": "1.0.1",
4
- "description": "Astro components for managing your page head — metadata, social sharing, favicons, and SEO.",
3
+ "version": "2.0.0-alpha.1",
4
+ "description": "Metadata for Astro: page tags, social cards, favicons, the web app manifest and structured data.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "homepage": "https://github.com/eremannisto/astro-metadata#readme",
8
8
  "repository": {
9
9
  "type": "git",
10
- "url": "https://github.com/eremannisto/astro-metadata"
10
+ "url": "git+https://github.com/eremannisto/astro-metadata.git"
11
11
  },
12
12
  "bugs": {
13
13
  "url": "https://github.com/eremannisto/astro-metadata/issues"
@@ -18,34 +18,47 @@
18
18
  "seo",
19
19
  "metadata"
20
20
  ],
21
- "exports": "./index.ts",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./src/integration.d.ts",
24
+ "default": "./src/integration.js"
25
+ },
26
+ "./runtime": "./src/runtime.ts",
27
+ "./components": "./src/components/index.ts"
28
+ },
22
29
  "files": [
23
- "src",
24
- "index.ts"
30
+ "src"
25
31
  ],
32
+ "scripts": {
33
+ "lint": "eslint . --fix",
34
+ "format": "prettier --write .",
35
+ "check": "eslint . && prettier --check .",
36
+ "test": "pnpm run test:unit && pnpm run test:e2e",
37
+ "test:unit": "vitest run",
38
+ "test:e2e": "pnpm test:fixtures && playwright test",
39
+ "test:fixtures": "pnpm --filter \"./tests/e2e/fixtures/**\" exec astro build"
40
+ },
26
41
  "peerDependencies": {
27
- "astro": "^4.3.0 || ^5.0.0 || ^6.0.0"
42
+ "astro": "^6.4.6 || ^7.0.0"
28
43
  },
29
44
  "devDependencies": {
30
45
  "@eslint/js": "^10.0.1",
31
46
  "@ianvs/prettier-plugin-sort-imports": "^4.7.1",
32
- "@playwright/test": "^1.58.2",
33
- "@types/node": "^22.19.15",
34
- "astro": "^5.18.1",
35
- "eslint": "^10.0.3",
47
+ "@playwright/test": "^1.63.0",
48
+ "@types/node": "^22.20.5",
49
+ "astro": "^7.3.5",
50
+ "eslint": "^10.11.0",
36
51
  "eslint-config-prettier": "^10.1.8",
37
- "prettier": "^3.8.1",
52
+ "prettier": "^3.9.9",
38
53
  "prettier-plugin-astro": "^0.14.1",
54
+ "sharp": "^0.35.5",
39
55
  "typescript": "^5.9.3",
40
- "typescript-eslint": "^8.57.0",
41
- "vitest": "^4.1.0"
56
+ "typescript-eslint": "^8.71.0",
57
+ "vitest": "^4.1.11"
42
58
  },
43
- "scripts": {
44
- "lint": "eslint . --fix",
45
- "format": "prettier --write .",
46
- "check": "eslint . && prettier --check .",
47
- "test": "pnpm run test:unit && pnpm run test:e2e",
48
- "test:unit": "vitest run",
49
- "test:e2e": "playwright test"
59
+ "packageManager": "pnpm@10.32.1",
60
+ "dependencies": {
61
+ "lit": "^3.3.3",
62
+ "schema-dts": "^2.0.0"
50
63
  }
51
- }
64
+ }