@kirigami/php-prepros 1.9.2 → 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
@@ -12,6 +12,7 @@ PHP preprocessor for the **Kirigami** static site generator.
12
12
  [![npm version](https://img.shields.io/npm/v/@kirigami/php-prepros)](https://www.npmjs.com/package/@kirigami/php-prepros)
13
13
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](./LICENSE)
14
14
  [![Node.js >=24.0.0](https://img.shields.io/badge/node-%3E%3D24.0.0-brightgreen)](https://nodejs.org)
15
+ [![Website](https://img.shields.io/badge/website-php--kirigami.github.io-1f6b4a)](https://php-kirigami.github.io)
15
16
 
16
17
  </div>
17
18
 
@@ -34,6 +35,12 @@ Part of the **Kirigami** project ecosystem.
34
35
  - [@kirigami/php-prepros](#kirigamiphp-prepros)
35
36
  - [Overview](#overview)
36
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)
37
44
  - [What's new in 1.7.2](#whats-new-in-172)
38
45
  - [What's new in 1.7.1](#whats-new-in-171)
39
46
  - [What's new in 1.7.0](#whats-new-in-170)
@@ -46,8 +53,7 @@ Part of the **Kirigami** project ecosystem.
46
53
  - [Installation](#installation)
47
54
  - [Configuration — `kirigami.yaml`](#configuration--kirigamiyaml)
48
55
  - [`kirigami` block](#kirigami-block)
49
- - [`jsonld` block](#jsonld-block)
50
- - [`meta` block](#meta-block)
56
+ - [`seo` block](#seo-block)
51
57
  - [`prepros` block](#prepros-block)
52
58
  - [`image` block](#image-block)
53
59
  - [`plugins` block](#plugins-block)
@@ -109,6 +115,60 @@ Part of the **Kirigami** project ecosystem.
109
115
 
110
116
  ---
111
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
+
112
172
  ## What's new in 1.9.1
113
173
 
114
174
  - **`{% img-asset %}` no longer crashes the build on an unresolvable path.**
@@ -376,7 +436,7 @@ npm install @kirigami/php-prepros
376
436
 
377
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.
378
438
 
379
- `@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:
380
440
 
381
441
  ```yaml
382
442
  # yaml-language-server: $schema=https://cdn.jsdelivr.net/gh/php-kirigami/kirigami@main/packages/kirigami/kirigami.schema.json
@@ -404,9 +464,10 @@ kirigami:
404
464
  - keyword one
405
465
  - keyword two
406
466
 
407
- jsonld: # Presence turns on the LD schema.org JSON-LD generator.
408
- type: Organization # `jsonld: {}` alone is enough; see the jsonld block below.
409
- 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
410
471
 
411
472
  prepros:
412
473
  before: _layouts/header.php # Included before every page body.
@@ -466,30 +527,31 @@ Core project settings. **Read by `php-prepros`.** The entire block is extracted
466
527
  | `baseurl` | ✅ | Root URL of the deployed site, no trailing slash. Used to build absolute `<loc>` entries in `sitemap.xml`; exposed as `$baseurl`. |
467
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. |
468
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. |
469
- | *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`, …). |
470
-
471
- ### `jsonld` block
472
-
473
- Top-level, optional. Its **presence** switches on the [`LD`](#ld) schema.org
474
- JSON-LD generator — an `application/ld+json` graph is then injected into every
475
- page's `<head>`. An empty `jsonld: {}` is enough; its keys refine what `LD`
476
- otherwise infers from the `kirigami` block and each page's PHPDOC. `jsonld: false`
477
- (or `jsonld: { auto: false }`) keeps the config values but stops the injection;
478
- no block at all means nothing is injected. Full key reference and per-page
479
- `@ld_*` tags: [`LD` → `jsonld` config](#jsonld-config).
480
-
481
- ### `meta` block
482
-
483
- Top-level, optional. Its **presence** switches on the [`META`](#meta) generator —
484
- the standard SEO / social `<meta>` and `<link>` tags are then built for every
485
- page and injected into its `<head>`. An empty `meta: {}` is enough; everything is
486
- derived from the `kirigami` block, the `jsonld` block, and each page's PHPDOC
487
- (`@title`, `@description` / `@abstract`, `@keywords`, `@image`, `@robots`,
488
- `@og_type`, `@canonical`). Keys refine those inferences. A tag the layout already
489
- hand-writes is left untouched. `meta: false` (or `meta: { auto: false }`) keeps
490
- the config values but stops the injection; no block at all means nothing is
491
- injected (explicit `META::tag()` / `meta_tag()` calls still emit). Full key
492
- 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).
493
555
 
494
556
  ### `prepros` block
495
557
 
@@ -1074,10 +1136,11 @@ than one node — in the `<head>`.
1074
1136
 
1075
1137
  #### Automatic mode
1076
1138
 
1077
- Opt in by adding a top-level `jsonld:` block to `kirigami.yaml` (a sibling of
1078
- `kirigami:`, not nested under it) — an empty `jsonld: {}` is enough. A
1079
- `post_render` hook then injects a graph built from that block, the loose keys of
1080
- 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:
1081
1144
 
1082
1145
  - an `Organization` node (`@id` `#organization`) — `name`/`url`/`description`
1083
1146
  from `project`/`baseurl`/`description`, `sameAs` gathered from every
@@ -1092,13 +1155,14 @@ the `kirigami` block, and the current page's PHPDOC:
1092
1155
  - a `WebPage` node for the page — see the per-page tags below;
1093
1156
  - a `BreadcrumbList` for every non-home page, derived from the `_index.php`
1094
1157
  ancestor trail (home → each parent section → this page). No `@breadcrumb`
1095
- 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.
1096
1159
  Disable it for one page with `@ld_breadcrumb false`.
1097
1160
 
1098
- Remove the `jsonld:` block (or set `jsonld: false` / `jsonld: { auto: false }`)
1099
- to stop the automatic pass. A page whose rendered `<head>` already contains an
1100
- `application/ld+json` script is never touched, so hand-rolled markup keeps
1101
- 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.
1102
1166
 
1103
1167
  **Per-page PHPDOC tags** — these feed the page node (and override the generic
1104
1168
  `@title` / `@description` / `@datePublished` fallbacks):
@@ -1126,9 +1190,9 @@ working.
1126
1190
  #### Explicit builders
1127
1191
 
1128
1192
  Call these from a page template or from a `prepros.includes` file. Nodes added
1129
- this way are always emitted — with or without a `jsonld:` block — and share the
1130
- graph the automatic pass uses, so the two combine; a node with a stable `@id` is
1131
- 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.
1132
1196
 
1133
1197
  ```php
1134
1198
  LD::add(string|array $type, array $props = [], ?string $id = null): array // build + register a node
@@ -1181,10 +1245,12 @@ Same API from procedural code: `ld_add()`, `ld_node()`, `ld_ref()`,
1181
1245
 
1182
1246
  #### `jsonld` config
1183
1247
 
1184
- `jsonld:` is a **top-level** block of `kirigami.yaml` (a sibling of `kirigami:`,
1185
- `prepros:`, …), and its presence is what **switches automatic injection on**. An
1186
- empty `jsonld: {}` is enough — everything is then derived from the `kirigami`
1187
- 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.
1188
1254
 
1189
1255
  ```yaml
1190
1256
  kirigami:
@@ -1194,20 +1260,21 @@ kirigami:
1194
1260
  jobtitle: Anthropologue
1195
1261
  facebook: https://www.facebook.com/humainhumainconsultation.ethnographie/
1196
1262
 
1197
- jsonld: # top-level; the block being present is
1198
- type: ProfessionalService # the switch — `jsonld: {}` also works
1199
- lang: fr-CA # inLanguage on WebSite / WebPage (default: en)
1200
- logo: assets/logo.png # absolute, or relative to baseurl
1201
- knowsAbout: [Ethnographie, Recherche qualitative]
1202
- address:
1203
- addressLocality: Québec
1204
- addressCountry: CA
1205
- 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}
1206
1273
  ```
1207
1274
 
1208
1275
  | Key | Type | Description |
1209
1276
  |-----|------|-------------|
1210
- | `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. |
1211
1278
  | `type` | `string` | `@type` for the main entity — `Organization`, `ProfessionalService`, `LocalBusiness`, … |
1212
1279
  | `name` / `url` / `description` | `string` | Main-entity / WebSite fields. Default to `project` / `baseurl` / `description`. |
1213
1280
  | `logo` / `image` | `string` | Absolute URL or path relative to `baseurl`. `image` defaults to `logo`. |
@@ -1230,12 +1297,13 @@ tags a browser and a link-preview crawler read: `<title>`, `<meta name="…">`,
1230
1297
  `<meta property="og:…">`, `<meta name="twitter:…">`, and a handful of `<link>`s.
1231
1298
 
1232
1299
  It draws on the same sources, in this order of precedence: the page's PHPDOC, the
1233
- top-level `meta:` block, then the loose `kirigami:` keys and the `jsonld:` block.
1234
- Every tag is emitted **only when it can be resolved** — no value, no tag — and a
1235
- tag the page's layout already writes by hand is detected and skipped, so it drops
1236
- 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.
1237
1305
 
1238
- **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
1239
1307
  enough) turns on injection into every page's `<head>`, right before `</head>`.
1240
1308
 
1241
1309
  ```yaml
@@ -1247,10 +1315,10 @@ kirigami:
1247
1315
  keywords: [ethnographie, consultation publique, sciences sociales]
1248
1316
  author: Maxime Larrivée-Roy
1249
1317
 
1250
- jsonld: {} # META reads its logo / image / lang / person too
1251
- meta: # top-level; the block being present is the switch
1318
+ seo: # top-level; the block being present is the switch
1252
1319
  twitter: "@humainhumain"
1253
1320
  themeColor: "#0b7285"
1321
+ jsonld: {} # independent opt-in — META reads its logo / image / lang / person too
1254
1322
  ```
1255
1323
 
1256
1324
  Per-page, from the PHPDOC block — each falls back to the generic page tag:
@@ -1260,13 +1328,13 @@ Per-page, from the PHPDOC block — each falls back to the generic page tag:
1260
1328
  | `@meta false` | skip metadata for this page entirely | — (`@meta_ignore true` also works) |
1261
1329
  | `@meta_title` | `<title>`, `og:title`, `twitter:title` | `@title` |
1262
1330
  | `@meta_description` | `description`, `og:description`, `twitter:description` | `@description` / `@abstract` / `@excerpt` / `@summary`, then the site `description` |
1263
- | `@meta_keywords` | `<meta name="keywords">` | `@keywords`, then `meta.keywords` |
1264
- | `@meta_image` | `og:image`, `twitter:image` | `@image` / `@ogimage`, then `meta.image` |
1265
- | `@meta_robots` | `<meta name="robots">` | `@robots`, then `meta.robots` |
1266
- | `@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` |
1267
1335
  | `@canonical` | `<link rel="canonical">` | derived from the file path + `baseurl` |
1268
1336
 
1269
- **Manual builders** — always emitted (with or without a `meta:` block), still
1337
+ **Manual builders** — always emitted (with or without a `seo:` block), still
1270
1338
  de-duplicated against the page:
1271
1339
 
1272
1340
  ```php
@@ -1288,20 +1356,22 @@ Same API from procedural code: `meta_tag()`, `meta_link()`, `meta_raw()`,
1288
1356
 
1289
1357
  #### `meta` config
1290
1358
 
1291
- `meta:` is a **top-level** block of `kirigami.yaml` (a sibling of `kirigami:`,
1292
- `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.
1293
1363
 
1294
1364
  | Key | Type | Description |
1295
1365
  |-----|------|-------------|
1296
- | `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`. |
1297
1367
  | `titleFormat` | `string` | `<title>` template for a normal page. Tokens `{title}`, `{project}`, `{tagline}`. Dangling separators from an empty token are trimmed. Default `{title} — {project}`. |
1298
1368
  | `titleFormatHome` | `string` | Title template when the page has no `@title` (home / section landings). Default `{project} — {tagline}`. |
1299
- | `description` | `string` | Default description for pages with no `@description` / `@abstract`. Defaults to the `jsonld` / loose `description`. |
1300
- | `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`. |
1301
1371
  | `robots` | `string` | Default robots directive. Default `index, follow`. `robots: false` omits the tag. |
1302
1372
  | `language` | `string` | BCP-47 tag → `<meta name="language">` and, dash→underscore, `og:locale`. Defaults to `jsonld.lang` / loose `lang` / `language`, then `en`. |
1303
1373
  | `generator` | `string` \| `false` | `<meta name="generator">`. Default `Kirigami`; `false` omits it. |
1304
- | `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. |
1305
1375
  | `themeColor` | `string` | `<meta name="theme-color">`. Not emitted unless set. |
1306
1376
  | `image` | `string` | Default `og:image` / `twitter:image` — absolute URL or path relative to `baseurl`. Defaults to `jsonld.image` → `jsonld.logo` → loose `image` / `ogimage`. |
1307
1377
  | `ogType` | `string` | Default `og:type`. Default `website`. |
@@ -1309,6 +1379,7 @@ Same API from procedural code: `meta_tag()`, `meta_link()`, `meta_raw()`,
1309
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. |
1310
1380
  | `canonical` | `bool` | Emit `<link rel="canonical">`. Default `true`. |
1311
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). |
1312
1383
 
1313
1384
  ---
1314
1385
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kirigami/php-prepros",
3
- "version": "1.9.2",
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",
@@ -45,14 +45,14 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@kirigami/php-wasm": "8.5.10-5",
48
- "@kirigami/struct-walker": "1.0.4",
48
+ "@kirigami/struct-walker": "1.0.5",
49
49
  "picomatch": "^4.0.7"
50
50
  },
51
51
  "repository": {
52
52
  "type": "git",
53
53
  "url": "git+https://github.com/php-kirigami/kirigami.git"
54
54
  },
55
- "homepage": "https://github.com/php-kirigami/kirigami/tree/main/packages/php-prepros#readme",
55
+ "homepage": "https://php-kirigami.github.io",
56
56
  "bugs": {
57
57
  "url": "https://github.com/php-kirigami/kirigami/issues"
58
58
  }
@@ -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.