@kirigami/php-prepros 1.9.3 → 2.0.0

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/README.md CHANGED
@@ -35,6 +35,12 @@ Part of the **Kirigami** project ecosystem.
35
35
  - [@kirigami/php-prepros](#kirigamiphp-prepros)
36
36
  - [Overview](#overview)
37
37
  - [Table of contents](#table-of-contents)
38
+ - [What's new in 2.0.0](#whats-new-in-200)
39
+ - [What's new in 1.9.3](#whats-new-in-193)
40
+ - [What's new in 1.9.2](#whats-new-in-192)
41
+ - [What's new in 1.9.1](#whats-new-in-191)
42
+ - [What's new in 1.9.0](#whats-new-in-190)
43
+ - [What's new in 1.8.0](#whats-new-in-180)
38
44
  - [What's new in 1.7.2](#whats-new-in-172)
39
45
  - [What's new in 1.7.1](#whats-new-in-171)
40
46
  - [What's new in 1.7.0](#whats-new-in-170)
@@ -47,8 +53,7 @@ Part of the **Kirigami** project ecosystem.
47
53
  - [Installation](#installation)
48
54
  - [Configuration — `kirigami.yaml`](#configuration--kirigamiyaml)
49
55
  - [`kirigami` block](#kirigami-block)
50
- - [`jsonld` block](#jsonld-block)
51
- - [`meta` block](#meta-block)
56
+ - [`seo` block](#seo-block)
52
57
  - [`prepros` block](#prepros-block)
53
58
  - [`image` block](#image-block)
54
59
  - [`plugins` block](#plugins-block)
@@ -110,6 +115,60 @@ Part of the **Kirigami** project ecosystem.
110
115
 
111
116
  ---
112
117
 
118
+ ## What's new in 2.0.0
119
+
120
+ - **Breaking: `meta:` and `jsonld:` merged into one top-level `seo:` block.**
121
+ The two used to be independent siblings of `kirigami:` that happened to
122
+ share fallback data; they're now one block, one mental model for a
123
+ project's whole SEO/social surface — `META`'s own keys live directly under
124
+ `seo:`, and `jsonld` is nested inside it as its own sub-block:
125
+
126
+ ```yaml
127
+ # before (1.x)
128
+ meta:
129
+ favicon: favicon.png
130
+ jsonld: {}
131
+
132
+ # after (2.0.0)
133
+ seo:
134
+ favicon: favicon.png
135
+ jsonld: {}
136
+ ```
137
+
138
+ The two stay **independently toggled** exactly as before — a project can
139
+ have META's tags without JSON-LD, or vice versa; `seo: { jsonld: false }`
140
+ (or `{ auto: false }`) stops just the JSON-LD injection, `seo: false` (or
141
+ `{ auto: false }` at the top level) stops just META's. Every fallback chain
142
+ (a page's PHPDOC → the block → the loose `kirigami:` keys) is unchanged,
143
+ including `jsonld`'s own keys still feeding META's defaults (`jsonld.image`,
144
+ `jsonld.lang`, `jsonld.person`, …) — only *where the two blocks live* in
145
+ `kirigami.yaml` changed, not how resolution works. **Migration**: rename
146
+ `meta:` to `seo:` and move the `jsonld:` block's content under it as
147
+ `seo.jsonld:`. See [`seo` block](#seo-block) / [`meta` config](#meta-config)
148
+ / [`jsonld` config](#jsonld-config).
149
+
150
+ ---
151
+
152
+ ## What's new in 1.9.3
153
+
154
+ - Dependency bump to `@kirigami/struct-walker` 1.0.5; `homepage` + README
155
+ pointed at the site (metadata only).
156
+
157
+ ---
158
+
159
+ ## What's new in 1.9.2
160
+
161
+ - **Fixed a real Markdown bug**: a list item's source line count was 1:1
162
+ with `<li>` count, so an indented continuation line with no marker of its
163
+ own (a soft-wrapped `- **foo** text\n more text`) fell outside the
164
+ block-matching regex entirely — the list closed after the first line, the
165
+ continuation resurfaced as a stray flat `<p>`, and a new list reopened for
166
+ the next marker line. Fixed by widening the block regex to also accept a
167
+ marker-less indented line and, in the per-line loop, appending it to the
168
+ previous item instead of dropping it.
169
+
170
+ ---
171
+
113
172
  ## What's new in 1.9.1
114
173
 
115
174
  - **`{% img-asset %}` no longer crashes the build on an unresolvable path.**
@@ -377,7 +436,7 @@ npm install @kirigami/php-prepros
377
436
 
378
437
  Every project **must** have a `kirigami.yaml` at its root. The preprocessor reads it at startup and throws if it is absent or invalid.
379
438
 
380
- `@kirigami/php-prepros` itself only acts on four blocks — **`kirigami:`**, **`jsonld:`**, **`prepros:`**, and **`image:`**. The remaining blocks (**`plugins:`**, **`esbuild:`**, **`sass:`**, **`export:`**, **`scripts:`**, **`tasks:`**) are consumed by the [`kiri`](https://www.npmjs.com/package/@kirigami/kirigami) CLI that drives the build; they are documented here for completeness because everything lives in the one file. The full file is validated against [`kirigami.schema.json`](https://github.com/php-kirigami/kirigami/blob/main/packages/kirigami/kirigami.schema.json), also served for editor autocompletion:
439
+ `@kirigami/php-prepros` itself only acts on four blocks — **`kirigami:`**, **`seo:`**, **`prepros:`**, and **`image:`**. The remaining blocks (**`plugins:`**, **`esbuild:`**, **`sass:`**, **`export:`**, **`scripts:`**, **`tasks:`**) are consumed by the [`kiri`](https://www.npmjs.com/package/@kirigami/kirigami) CLI that drives the build; they are documented here for completeness because everything lives in the one file. The full file is validated against [`kirigami.schema.json`](https://github.com/php-kirigami/kirigami/blob/main/packages/kirigami/kirigami.schema.json), also served for editor autocompletion:
381
440
 
382
441
  ```yaml
383
442
  # yaml-language-server: $schema=https://cdn.jsdelivr.net/gh/php-kirigami/kirigami@main/packages/kirigami/kirigami.schema.json
@@ -405,9 +464,10 @@ kirigami:
405
464
  - keyword one
406
465
  - keyword two
407
466
 
408
- jsonld: # Presence turns on the LD schema.org JSON-LD generator.
409
- type: Organization # `jsonld: {}` alone is enough; see the jsonld block below.
410
- logo: assets/logo.png
467
+ seo: # Presence turns on the META <head> tags generator.
468
+ jsonld: # Nested, independent opt-in for the LD JSON-LD generator.
469
+ type: Organization # `jsonld: {}` alone is enough; see the seo block below.
470
+ logo: assets/logo.png
411
471
 
412
472
  prepros:
413
473
  before: _layouts/header.php # Included before every page body.
@@ -467,30 +527,31 @@ Core project settings. **Read by `php-prepros`.** The entire block is extracted
467
527
  | `baseurl` | ✅ | Root URL of the deployed site, no trailing slash. Used to build absolute `<loc>` entries in `sitemap.xml`; exposed as `$baseurl`. |
468
528
  | `root` | ✅ | Path (relative to the project root) to the directory containing your `_*.php` source pages. Build fails immediately if missing or if the path doesn't exist. |
469
529
  | `banner` | — | Path (relative to the project root) to a text file stamped as a license/copyright banner on exported `.js`/`.css`/`.html` files during `kiri export`. May contain the `###DATE###` token, replaced with today's date. Falls back to an auto-generated banner. |
470
- | *anything else* | — | Free-form key/value pairs (strings, numbers, booleans, lists, nested maps — anything valid YAML). Every key is extracted as a PHP variable (`$author`, `$gtag`, …). Use this for contact info, social links, analytics IDs, SEO keywords, or any project data you want available everywhere. When the top-level `jsonld` block is present, [`LD`](#ld) also reads some of these by convention: `person`, `jobtitle`, `email`, `area`, `knowsabout`, `keywords`, and social-network URL keys (`facebook`, `instagram`, …). |
471
-
472
- ### `jsonld` block
473
-
474
- Top-level, optional. Its **presence** switches on the [`LD`](#ld) schema.org
475
- JSON-LD generator — an `application/ld+json` graph is then injected into every
476
- page's `<head>`. An empty `jsonld: {}` is enough; its keys refine what `LD`
477
- otherwise infers from the `kirigami` block and each page's PHPDOC. `jsonld: false`
478
- (or `jsonld: { auto: false }`) keeps the config values but stops the injection;
479
- no block at all means nothing is injected. Full key reference and per-page
480
- `@ld_*` tags: [`LD` → `jsonld` config](#jsonld-config).
481
-
482
- ### `meta` block
483
-
484
- Top-level, optional. Its **presence** switches on the [`META`](#meta) generator —
485
- the standard SEO / social `<meta>` and `<link>` tags are then built for every
486
- page and injected into its `<head>`. An empty `meta: {}` is enough; everything is
487
- derived from the `kirigami` block, the `jsonld` block, and each page's PHPDOC
488
- (`@title`, `@description` / `@abstract`, `@keywords`, `@image`, `@robots`,
489
- `@og_type`, `@canonical`). Keys refine those inferences. A tag the layout already
490
- hand-writes is left untouched. `meta: false` (or `meta: { auto: false }`) keeps
491
- the config values but stops the injection; no block at all means nothing is
492
- injected (explicit `META::tag()` / `meta_tag()` calls still emit). Full key
493
- reference and per-page `@meta_*` tags: [`META` → `meta` config](#meta-config).
530
+ | *anything else* | — | Free-form key/value pairs (strings, numbers, booleans, lists, nested maps — anything valid YAML). Every key is extracted as a PHP variable (`$author`, `$gtag`, …). Use this for contact info, social links, analytics IDs, SEO keywords, or any project data you want available everywhere. When the top-level `seo` block is present, [`LD`](#ld) also reads some of these by convention: `person`, `jobtitle`, `email`, `area`, `knowsabout`, `keywords`, and social-network URL keys (`facebook`, `instagram`, …). |
531
+
532
+ ### `seo` block
533
+
534
+ Top-level, optional. The unified SEO surface — replaces the old separate
535
+ `meta:`/`jsonld:` blocks (**breaking in 2.0.0**, see
536
+ [What's new in 2.0.0](#whats-new-in-200)). Its **presence** switches on the
537
+ [`META`](#meta) generator — the standard SEO / social `<meta>` and `<link>`
538
+ tags are then built for every page and injected into its `<head>`. An empty
539
+ `seo: {}` is enough; everything is derived from the `kirigami` block and each
540
+ page's PHPDOC (`@title`, `@description` / `@abstract`, `@keywords`, `@image`,
541
+ `@robots`, `@og_type`, `@canonical`). A tag the layout already hand-writes is
542
+ left untouched. `seo: false` (or `seo: { auto: false }`) keeps the config
543
+ values but stops the injection; no block at all means nothing is injected
544
+ (explicit `META::tag()` / `meta_tag()` calls still emit). Full key reference
545
+ and per-page `@meta_*` tags: [`META` → `seo` config](#meta-config).
546
+
547
+ Nested inside it, `seo.jsonld` is its own **independent** opt-in — a project
548
+ can have META's tags without JSON-LD, or vice versa. Its presence switches on
549
+ the [`LD`](#ld) schema.org JSON-LD generator — an `application/ld+json` graph
550
+ is then injected into every page's `<head>`. An empty `seo: { jsonld: {} }` is
551
+ enough; its keys refine what `LD` otherwise infers from the rest of `seo:`,
552
+ the `kirigami` block, and each page's PHPDOC. `seo: { jsonld: false }` (or
553
+ `{ auto: false }`) keeps the config values but stops the injection. Full key
554
+ reference and per-page `@ld_*` tags: [`LD` → `jsonld` config](#jsonld-config).
494
555
 
495
556
  ### `prepros` block
496
557
 
@@ -1075,10 +1136,11 @@ than one node — in the `<head>`.
1075
1136
 
1076
1137
  #### Automatic mode
1077
1138
 
1078
- Opt in by adding a top-level `jsonld:` block to `kirigami.yaml` (a sibling of
1079
- `kirigami:`, not nested under it) — an empty `jsonld: {}` is enough. A
1080
- `post_render` hook then injects a graph built from that block, the loose keys of
1081
- the `kirigami` block, and the current page's PHPDOC:
1139
+ Opt in by adding a `jsonld` sub-block under `seo:` in `kirigami.yaml` (nested
1140
+ inside the same block [`META`](#meta) reads, independent of the rest of it) —
1141
+ an empty `seo: { jsonld: {} }` is enough. A `post_render` hook then injects a
1142
+ graph built from that sub-block, the loose keys of the `kirigami` block, and
1143
+ the current page's PHPDOC:
1082
1144
 
1083
1145
  - an `Organization` node (`@id` `#organization`) — `name`/`url`/`description`
1084
1146
  from `project`/`baseurl`/`description`, `sameAs` gathered from every
@@ -1093,13 +1155,14 @@ the `kirigami` block, and the current page's PHPDOC:
1093
1155
  - a `WebPage` node for the page — see the per-page tags below;
1094
1156
  - a `BreadcrumbList` for every non-home page, derived from the `_index.php`
1095
1157
  ancestor trail (home → each parent section → this page). No `@breadcrumb`
1096
- opt-in needed — it is always attempted while the `jsonld:` block is on.
1158
+ opt-in needed — it is always attempted while the `jsonld` sub-block is on.
1097
1159
  Disable it for one page with `@ld_breadcrumb false`.
1098
1160
 
1099
- Remove the `jsonld:` block (or set `jsonld: false` / `jsonld: { auto: false }`)
1100
- to stop the automatic pass. A page whose rendered `<head>` already contains an
1101
- `application/ld+json` script is never touched, so hand-rolled markup keeps
1102
- working.
1161
+ Remove the `jsonld` sub-block (or set `seo: { jsonld: false }` /
1162
+ `{ jsonld: { auto: false } }`) to stop the automatic pass — the rest of `seo:`
1163
+ (META's tags) keeps working either way. A page whose rendered `<head>` already
1164
+ contains an `application/ld+json` script is never touched, so hand-rolled
1165
+ markup keeps working.
1103
1166
 
1104
1167
  **Per-page PHPDOC tags** — these feed the page node (and override the generic
1105
1168
  `@title` / `@description` / `@datePublished` fallbacks):
@@ -1127,9 +1190,9 @@ working.
1127
1190
  #### Explicit builders
1128
1191
 
1129
1192
  Call these from a page template or from a `prepros.includes` file. Nodes added
1130
- this way are always emitted — with or without a `jsonld:` block — and share the
1131
- graph the automatic pass uses, so the two combine; a node with a stable `@id` is
1132
- merged on repeat calls.
1193
+ this way are always emitted — with or without a `jsonld` sub-block — and share
1194
+ the graph the automatic pass uses, so the two combine; a node with a stable
1195
+ `@id` is merged on repeat calls.
1133
1196
 
1134
1197
  ```php
1135
1198
  LD::add(string|array $type, array $props = [], ?string $id = null): array // build + register a node
@@ -1182,10 +1245,12 @@ Same API from procedural code: `ld_add()`, `ld_node()`, `ld_ref()`,
1182
1245
 
1183
1246
  #### `jsonld` config
1184
1247
 
1185
- `jsonld:` is a **top-level** block of `kirigami.yaml` (a sibling of `kirigami:`,
1186
- `prepros:`, …), and its presence is what **switches automatic injection on**. An
1187
- empty `jsonld: {}` is enough — everything is then derived from the `kirigami`
1188
- block's loose keys. Adding keys overrides those inferences; all are optional.
1248
+ `jsonld` is a sub-block of the **top-level** `seo:` block of `kirigami.yaml`
1249
+ (nested alongside [`META`](#meta)'s own keys), and its presence is what
1250
+ **switches automatic injection on** — independently of the rest of `seo:`. An
1251
+ empty `seo: { jsonld: {} }` is enough — everything is then derived from the
1252
+ `kirigami` block's loose keys. Adding keys overrides those inferences; all are
1253
+ optional.
1189
1254
 
1190
1255
  ```yaml
1191
1256
  kirigami:
@@ -1195,20 +1260,21 @@ kirigami:
1195
1260
  jobtitle: Anthropologue
1196
1261
  facebook: https://www.facebook.com/humainhumainconsultation.ethnographie/
1197
1262
 
1198
- jsonld: # top-level; the block being present is
1199
- type: ProfessionalService # the switch — `jsonld: {}` also works
1200
- lang: fr-CA # inLanguage on WebSite / WebPage (default: en)
1201
- logo: assets/logo.png # absolute, or relative to baseurl
1202
- knowsAbout: [Ethnographie, Recherche qualitative]
1203
- address:
1204
- addressLocality: Québec
1205
- addressCountry: CA
1206
- search: https://humainhumain.com/?q={search_term_string}
1263
+ seo:
1264
+ jsonld: # nested; the sub-block being present
1265
+ type: ProfessionalService # is the switch — `{}` also works
1266
+ lang: fr-CA # inLanguage on WebSite / WebPage (default: en)
1267
+ logo: assets/logo.png # absolute, or relative to baseurl
1268
+ knowsAbout: [Ethnographie, Recherche qualitative]
1269
+ address:
1270
+ addressLocality: Québec
1271
+ addressCountry: CA
1272
+ search: https://humainhumain.com/?q={search_term_string}
1207
1273
  ```
1208
1274
 
1209
1275
  | Key | Type | Description |
1210
1276
  |-----|------|-------------|
1211
- | `auto` | `bool` | Inject the `<script>` automatically. Default `true` **once the `jsonld:` block exists**. Set `auto: false` (or `jsonld: false`) to keep the block for its config values but stop the automatic injection — `LD::script()` / `ld_script()` can still place it by hand. |
1277
+ | `auto` | `bool` | Inject the `<script>` automatically. Default `true` **once the `jsonld` sub-block exists**. Set `auto: false` (or `seo: { jsonld: false }`) to keep the sub-block for its config values but stop the automatic injection — `LD::script()` / `ld_script()` can still place it by hand. |
1212
1278
  | `type` | `string` | `@type` for the main entity — `Organization`, `ProfessionalService`, `LocalBusiness`, … |
1213
1279
  | `name` / `url` / `description` | `string` | Main-entity / WebSite fields. Default to `project` / `baseurl` / `description`. |
1214
1280
  | `logo` / `image` | `string` | Absolute URL or path relative to `baseurl`. `image` defaults to `logo`. |
@@ -1231,12 +1297,13 @@ tags a browser and a link-preview crawler read: `<title>`, `<meta name="…">`,
1231
1297
  `<meta property="og:…">`, `<meta name="twitter:…">`, and a handful of `<link>`s.
1232
1298
 
1233
1299
  It draws on the same sources, in this order of precedence: the page's PHPDOC, the
1234
- top-level `meta:` block, then the loose `kirigami:` keys and the `jsonld:` block.
1235
- Every tag is emitted **only when it can be resolved** — no value, no tag — and a
1236
- tag the page's layout already writes by hand is detected and skipped, so it drops
1237
- in beside an existing `header.php` without duplicating anything.
1300
+ top-level `seo:` block, then the loose `kirigami:` keys and the `seo.jsonld`
1301
+ sub-block. Every tag is emitted **only when it can be resolved** — no value, no
1302
+ tag — and a tag the page's layout already writes by hand is detected and
1303
+ skipped, so it drops in beside an existing `header.php` without duplicating
1304
+ anything.
1238
1305
 
1239
- **Automatic mode** is opt-in: the top-level `meta:` block (empty `meta: {}` is
1306
+ **Automatic mode** is opt-in: the top-level `seo:` block (empty `seo: {}` is
1240
1307
  enough) turns on injection into every page's `<head>`, right before `</head>`.
1241
1308
 
1242
1309
  ```yaml
@@ -1248,10 +1315,10 @@ kirigami:
1248
1315
  keywords: [ethnographie, consultation publique, sciences sociales]
1249
1316
  author: Maxime Larrivée-Roy
1250
1317
 
1251
- jsonld: {} # META reads its logo / image / lang / person too
1252
- meta: # top-level; the block being present is the switch
1318
+ seo: # top-level; the block being present is the switch
1253
1319
  twitter: "@humainhumain"
1254
1320
  themeColor: "#0b7285"
1321
+ jsonld: {} # independent opt-in — META reads its logo / image / lang / person too
1255
1322
  ```
1256
1323
 
1257
1324
  Per-page, from the PHPDOC block — each falls back to the generic page tag:
@@ -1261,13 +1328,13 @@ Per-page, from the PHPDOC block — each falls back to the generic page tag:
1261
1328
  | `@meta false` | skip metadata for this page entirely | — (`@meta_ignore true` also works) |
1262
1329
  | `@meta_title` | `<title>`, `og:title`, `twitter:title` | `@title` |
1263
1330
  | `@meta_description` | `description`, `og:description`, `twitter:description` | `@description` / `@abstract` / `@excerpt` / `@summary`, then the site `description` |
1264
- | `@meta_keywords` | `<meta name="keywords">` | `@keywords`, then `meta.keywords` |
1265
- | `@meta_image` | `og:image`, `twitter:image` | `@image` / `@ogimage`, then `meta.image` |
1266
- | `@meta_robots` | `<meta name="robots">` | `@robots`, then `meta.robots` |
1267
- | `@meta_type` | `og:type` | `@og_type`, then `meta.ogType` |
1331
+ | `@meta_keywords` | `<meta name="keywords">` | `@keywords`, then `seo.keywords` |
1332
+ | `@meta_image` | `og:image`, `twitter:image` | `@image` / `@ogimage`, then `seo.image` |
1333
+ | `@meta_robots` | `<meta name="robots">` | `@robots`, then `seo.robots` |
1334
+ | `@meta_type` | `og:type` | `@og_type`, then `seo.ogType` |
1268
1335
  | `@canonical` | `<link rel="canonical">` | derived from the file path + `baseurl` |
1269
1336
 
1270
- **Manual builders** — always emitted (with or without a `meta:` block), still
1337
+ **Manual builders** — always emitted (with or without a `seo:` block), still
1271
1338
  de-duplicated against the page:
1272
1339
 
1273
1340
  ```php
@@ -1289,20 +1356,22 @@ Same API from procedural code: `meta_tag()`, `meta_link()`, `meta_raw()`,
1289
1356
 
1290
1357
  #### `meta` config
1291
1358
 
1292
- `meta:` is a **top-level** block of `kirigami.yaml` (a sibling of `kirigami:`,
1293
- `jsonld:`, `prepros:`, …). All keys are optional.
1359
+ These keys live directly under the **top-level** `seo:` block of
1360
+ `kirigami.yaml` (a sibling of `kirigami:`, `prepros:`, …) — `jsonld` is the one
1361
+ sub-block among them, documented separately in [`LD` → `jsonld` config](#jsonld-config).
1362
+ All keys are optional.
1294
1363
 
1295
1364
  | Key | Type | Description |
1296
1365
  |-----|------|-------------|
1297
- | `auto` | `bool` | Inject the tags automatically. Default `true` **once the `meta:` block exists**. `auto: false` (or `meta: false`) keeps the block for its values but stops the injection — `META::tags()` / `meta_tags()` can place them by hand. |
1366
+ | `auto` | `bool` | Inject the tags automatically. Default `true` **once the `seo:` block exists**. `auto: false` (or `seo: false`) keeps the block for its values but stops the injection — `META::tags()` / `meta_tags()` can place them by hand. Independent of `jsonld.auto`. |
1298
1367
  | `titleFormat` | `string` | `<title>` template for a normal page. Tokens `{title}`, `{project}`, `{tagline}`. Dangling separators from an empty token are trimmed. Default `{title} — {project}`. |
1299
1368
  | `titleFormatHome` | `string` | Title template when the page has no `@title` (home / section landings). Default `{project} — {tagline}`. |
1300
- | `description` | `string` | Default description for pages with no `@description` / `@abstract`. Defaults to the `jsonld` / loose `description`. |
1301
- | `keywords` | `string[]` \| `string` | Default `keywords` content (list or comma string). Defaults to the `jsonld` / loose `keywords`. |
1369
+ | `description` | `string` | Default description for pages with no `@description` / `@abstract`. Defaults to `jsonld.description` / loose `description`. |
1370
+ | `keywords` | `string[]` \| `string` | Default `keywords` content (list or comma string). Defaults to `jsonld.keywords` / loose `keywords`. |
1302
1371
  | `robots` | `string` | Default robots directive. Default `index, follow`. `robots: false` omits the tag. |
1303
1372
  | `language` | `string` | BCP-47 tag → `<meta name="language">` and, dash→underscore, `og:locale`. Defaults to `jsonld.lang` / loose `lang` / `language`, then `en`. |
1304
1373
  | `generator` | `string` \| `false` | `<meta name="generator">`. Default `Kirigami`; `false` omits it. |
1305
- | `author` / `designer` | `string` | Default to the loose `author` / `designer` keys (author also falls back to the `jsonld` person's name). `designer` is not emitted unless set. |
1374
+ | `author` / `designer` | `string` | Default to the loose `author` / `designer` keys (author also falls back to `jsonld.person`'s name). `designer` is not emitted unless set. |
1306
1375
  | `themeColor` | `string` | `<meta name="theme-color">`. Not emitted unless set. |
1307
1376
  | `image` | `string` | Default `og:image` / `twitter:image` — absolute URL or path relative to `baseurl`. Defaults to `jsonld.image` → `jsonld.logo` → loose `image` / `ogimage`. |
1308
1377
  | `ogType` | `string` | Default `og:type`. Default `website`. |
@@ -1310,6 +1379,7 @@ Same API from procedural code: `meta_tag()`, `meta_link()`, `meta_raw()`,
1310
1379
  | `twitter` | `string` \| `map` | Handle for `twitter:site` / `twitter:creator`. A bare string (with/without `@`, or a profile URL) fills both; a map takes `site` / `creator` separately. |
1311
1380
  | `canonical` | `bool` | Emit `<link rel="canonical">`. Default `true`. |
1312
1381
  | `favicon` / `appleTouchIcon` / `humans` | `string` \| `bool` | `<link rel="icon">` / `rel="apple-touch-icon"` / `rel="author"`. A path sets it (page-relative when a bare filename); `true` forces the default file (`favicon.ico` / `apple-touch-icon.png` / `humans.txt`); omitted, the default file is auto-detected on disk at the source root; `false` disables it. |
1382
+ | `jsonld` | `object` \| `bool` | Sub-block for `LD`'s schema.org JSON-LD — its own independent opt-in. See [`LD` → `jsonld` config](#jsonld-config). |
1313
1383
 
1314
1384
  ---
1315
1385
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kirigami/php-prepros",
3
- "version": "1.9.3",
3
+ "version": "2.0.0",
4
4
  "description": "PHP preprocessor for the Kirigami static site generator. Compile PHP page templates to clean, deployable HTML — with zero server dependency.",
5
5
  "keywords": [
6
6
  "kirigami",
@@ -7,21 +7,23 @@ declare(strict_types=1);
7
7
  *
8
8
  * Collects schema.org nodes during a render and emits them as a single
9
9
  * `<script type="application/ld+json">` block in the page `<head>`, built from
10
- * the top-level `jsonld:` block of `kirigami.yaml` and the loose keys of the
11
- * `kirigami:` block (`person`, `jobtitle`, `area`, `knowsabout`, `keywords`,
12
- * `facebook`, … that Kirigami projects already use).
10
+ * the `seo.jsonld` sub-block of `kirigami.yaml` — nested under the same `seo:`
11
+ * block `META` reads, one on/off switch for the whole SEO surface — and the
12
+ * loose keys of the `kirigami:` block (`person`, `jobtitle`, `area`,
13
+ * `knowsabout`, `keywords`, `facebook`, … that Kirigami projects already use).
13
14
  *
14
15
  * Two ways to use it, and they combine:
15
16
  *
16
- * 1. Automatic — opt-in. As soon as `kirigami.yaml` carries a top-level
17
- * `jsonld:` block (even an empty one, `jsonld: {}`), a `post_render` hook
18
- * injects an `Organization` (+ `Person`, `WebSite`, `WebPage`, and a
19
- * `BreadcrumbList` built from the `_index.php` ancestor trail) `@graph`
20
- * derived from the block, the loose keys and the current page's PHPDOC.
21
- * Without a `jsonld:` block nothing is injected. Turn it back off with
22
- * `jsonld: false` (or `jsonld: { auto: false }`), or per page with
23
- * `@ld false` in the template's PHPDOC. A page that already hand-writes an
24
- * `application/ld+json` script is left untouched.
17
+ * 1. Automatic — opt-in. As soon as `kirigami.yaml`'s `seo:` block carries a
18
+ * `jsonld` sub-block (even an empty one, `seo: { jsonld: {} }`), a
19
+ * `post_render` hook injects an `Organization` (+ `Person`, `WebSite`,
20
+ * `WebPage`, and a `BreadcrumbList` built from the `_index.php` ancestor
21
+ * trail) `@graph` derived from the block, the loose keys and the current
22
+ * page's PHPDOC. Without a `jsonld` sub-block nothing is injected — the
23
+ * rest of `seo:` (META's own concerns) works independently of it. Turn it
24
+ * back off with `seo: { jsonld: false }` (or `{ auto: false }`), or per
25
+ * page with `@ld false` in the template's PHPDOC. A page that already
26
+ * hand-writes an `application/ld+json` script is left untouched.
25
27
  *
26
28
  * Per-page PHPDOC tags feed the page node:
27
29
  * @ld false skip JSON-LD for this page (or @ld_ignore true)
@@ -34,7 +36,7 @@ declare(strict_types=1);
34
36
  * @ld_breadcrumb false no BreadcrumbList for this page
35
37
  *
36
38
  * 2. Explicit. Call the builders from a template or from an `includes` file.
37
- * Nodes added this way are always emitted, `jsonld:` block or not:
39
+ * Nodes added this way are always emitted, `seo.jsonld` sub-block or not:
38
40
  *
39
41
  * LD::add('Recipe', [ 'name' => 'Tarte', 'recipeYield' => '6' ]);
40
42
  * LD::article([ 'headline' => $title, 'author' => LD::ref('#person') ]);
@@ -76,8 +78,8 @@ final class LD
76
78
  // -----------------------------------------------------------------------
77
79
 
78
80
  /**
79
- * Resolved JSON-LD configuration, merging the `jsonld:` block with the
80
- * loose top-level keys of the `kirigami:` block.
81
+ * Resolved JSON-LD configuration, merging the `seo.jsonld` sub-block with
82
+ * the loose top-level keys of the `kirigami:` block.
81
83
  */
82
84
  public static function config(): object
83
85
  {
@@ -87,7 +89,7 @@ final class LD
87
89
  $raw = self::jsonldRaw();
88
90
  $j = is_object($raw) ? $raw : new stdClass;
89
91
 
90
- // Automatic injection is opt-in: it needs a top-level `jsonld:` block (an
92
+ // Automatic injection is opt-in: it needs a `seo.jsonld` sub-block (an
91
93
  // empty map counts). `jsonld: false` / `jsonld: { auto: false }` turn it off.
92
94
  $enabled = is_object($raw) || $raw === true;
93
95
  if ($enabled && isset($j->auto) && !self::truthy($j->auto)) $enabled = false;
@@ -371,8 +373,8 @@ final class LD
371
373
 
372
374
  /**
373
375
  * A `BreadcrumbList`. With no argument it is derived from the page's
374
- * ancestor `_index.php` trail (always attempted while the `jsonld:` block
375
- * is on — no `@breadcrumb` opt-in needed; disable per page with
376
+ * ancestor `_index.php` trail (always attempted while the `seo.jsonld`
377
+ * sub-block is on — no `@breadcrumb` opt-in needed; disable per page with
376
378
  * `@ld_breadcrumb false`). Pass `$items` as `[['name' => …, 'url' => …], …]`
377
379
  * to build it by hand.
378
380
  */
@@ -544,7 +546,7 @@ final class LD
544
546
  if (self::pageOptedOut()) return $html;
545
547
  if (stripos($html, 'application/ld+json') !== false) return $html;
546
548
 
547
- // script() autofills the defaults only when the `jsonld:` block
549
+ // script() autofills the defaults only when the `seo.jsonld` sub-block
548
550
  // opted in; otherwise it emits nothing unless a template added
549
551
  // nodes by hand, in which case those are still injected.
550
552
  $script = self::script();
@@ -583,10 +585,11 @@ final class LD
583
585
  return new stdClass;
584
586
  }
585
587
 
586
- /** The raw top-level `jsonld:` block: an object, `false`, `true`, or `null` when absent. */
588
+ /** The raw `seo.jsonld` sub-block: an object, `false`, `true`, or `null` when absent. */
587
589
  private static function jsonldRaw(): mixed
588
590
  {
589
- return (isset(PREPROS::$config) && is_object(PREPROS::$config)) ? (PREPROS::$config->jsonld ?? null) : null;
591
+ $seo = (isset(PREPROS::$config) && is_object(PREPROS::$config)) ? (PREPROS::$config->seo ?? null) : null;
592
+ return is_object($seo) ? ($seo->jsonld ?? null) : null;
590
593
  }
591
594
 
592
595
  private static function pageInfo(): object
@@ -10,9 +10,9 @@ declare(strict_types=1);
10
10
  * precedence:
11
11
  *
12
12
  * 1. the page's own PHPDOC block (`@title`, `@description`, `@image`, …),
13
- * 2. the top-level `meta:` block of `kirigami.yaml` (config + overrides),
14
- * 3. the loose keys of the `kirigami:` block and the top-level `jsonld:`
15
- * block (`description`, `keywords`, `author`, `person`, `lang`, `logo`,
13
+ * 2. the top-level `seo:` block of `kirigami.yaml` (config + overrides),
14
+ * 3. the loose keys of the `kirigami:` block and the `seo.jsonld` sub-block
15
+ * (`description`, `keywords`, `author`, `person`, `lang`, `logo`,
16
16
  * `image`, …) — the same values `LD` already reads.
17
17
  *
18
18
  * It only ever emits what it can resolve: a tag with no value is skipped, and a
@@ -20,11 +20,13 @@ declare(strict_types=1);
20
20
  * `<link rel="canonical">`, …) is left untouched — so it slots in next to an
21
21
  * existing `header.php` without doubling anything up.
22
22
  *
23
- * Automatic injection is **opt-in**: it needs a top-level `meta:` block (an
24
- * empty map, `meta: {}`, is enough). `meta: false` (or `meta: { auto: false }`)
23
+ * Automatic injection is **opt-in**: it needs a top-level `seo:` block (an
24
+ * empty map, `seo: {}`, is enough). `seo: false` (or `seo: { auto: false }`)
25
25
  * keeps the config values but stops the injection; no block at all means
26
26
  * nothing is injected — an explicit `META::tag()` call from a template still
27
- * emits.
27
+ * emits. `LD`'s schema.org JSON-LD lives right alongside it, under `seo.jsonld`
28
+ * — one block, one on/off switch for the whole SEO surface, `jsonld` toggled
29
+ * independently within it (see `LD`'s own docblock).
28
30
  *
29
31
  * Per-page PHPDOC tags, each falling back to the generic page tag:
30
32
  *
@@ -37,7 +39,7 @@ declare(strict_types=1);
37
39
  * @meta_type <type> og:type (default @og_type)
38
40
  * @canonical <url> <link rel="canonical"> (default: derived from the file path + baseurl)
39
41
  *
40
- * Manual use from a template or an `includes` file — always emitted, `meta:`
42
+ * Manual use from a template or an `includes` file — always emitted, `seo:`
41
43
  * block or not, and still de-duplicated against the page:
42
44
  *
43
45
  * META::tag('twitter:image', 'https://…/card.png'); // name= or property= picked from the key
@@ -64,8 +66,8 @@ final class META
64
66
  // -----------------------------------------------------------------------
65
67
 
66
68
  /**
67
- * Resolved metadata configuration, merging the `meta:` block with the loose
68
- * keys of the `kirigami:` block and the `jsonld:` block.
69
+ * Resolved metadata configuration, merging the `seo:` block with the loose
70
+ * keys of the `kirigami:` block and the `seo.jsonld` sub-block.
69
71
  */
70
72
  public static function config(): object
71
73
  {
@@ -76,7 +78,7 @@ final class META
76
78
  $m = is_object($raw) ? $raw : new stdClass;
77
79
  $ld = self::jsonld();
78
80
 
79
- // Opt-in: needs a top-level `meta:` block (an empty map counts).
81
+ // Opt-in: needs a top-level `seo:` block (an empty map counts).
80
82
  $enabled = is_object($raw) || $raw === true;
81
83
  if ($enabled && isset($m->auto) && !self::truthy($m->auto)) $enabled = false;
82
84
 
@@ -188,7 +190,7 @@ final class META
188
190
  /** @var array<int,array{0:string,1:string}> [probe regex, tag html] */
189
191
  $lines = [];
190
192
 
191
- // The auto block only builds when the `meta:` block opted in; an
193
+ // The auto block only builds when the `seo:` block opted in; an
192
194
  // explicit META::tag() call still lands through self::$extra below.
193
195
  if ($c->enabled) {
194
196
  $relroot = self::relroot();
@@ -366,16 +368,17 @@ final class META
366
368
  return new stdClass;
367
369
  }
368
370
 
369
- /** The raw top-level `meta:` block: object, `false`, `true`, or `null`. */
371
+ /** The raw top-level `seo:` block: object, `false`, `true`, or `null`. */
370
372
  private static function metaRaw(): mixed
371
373
  {
372
- return (isset(PREPROS::$config) && is_object(PREPROS::$config)) ? (PREPROS::$config->meta ?? null) : null;
374
+ return (isset(PREPROS::$config) && is_object(PREPROS::$config)) ? (PREPROS::$config->seo ?? null) : null;
373
375
  }
374
376
 
375
- /** The top-level `jsonld:` block as an object (empty when absent / disabled). */
377
+ /** The `seo.jsonld` sub-block as an object (empty when absent / disabled). */
376
378
  private static function jsonld(): object
377
379
  {
378
- $raw = (isset(PREPROS::$config) && is_object(PREPROS::$config)) ? (PREPROS::$config->jsonld ?? null) : null;
380
+ $seo = self::metaRaw();
381
+ $raw = is_object($seo) ? ($seo->jsonld ?? null) : null;
379
382
  return is_object($raw) ? $raw : new stdClass;
380
383
  }
381
384
 
package/src/prepros.js CHANGED
@@ -46,8 +46,9 @@ const getPHPInstance = async () => {
46
46
  preprosConfig.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
47
47
  preprosConfig.root = joinWith('/project/', config?.kirigami?.root);
48
48
  preprosConfig.data = config.kirigami || {};
49
- preprosConfig.jsonld = config.jsonld ?? null;
50
- preprosConfig.meta = config.meta ?? null;
49
+ // The unified SEO block — META reads it directly; LD reads its own
50
+ // `jsonld` sub-key (see META/LD's docblocks). One block, one toggle.
51
+ preprosConfig.seo = config.seo ?? null;
51
52
  // META auto-detects favicon / apple-touch-icon / humans.txt at the
52
53
  // source root; those extensions aren't mounted into the sandbox, so the
53
54
  // presence check is done here on the real filesystem instead.