@pagenary/publisher 2026.5.1 → 2026.5.3
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 +177 -143
- package/package.json +5 -3
- package/scripts/build-tenants.js +14 -3
- package/scripts/lib/collections-generator.js +173 -0
- package/scripts/lib/frontmatter.js +80 -0
- package/scripts/lib/seo-generator.js +78 -14
- package/site/app.js +1 -1
- package/site/index.html +1 -1
- package/site/llms.txt +9 -9
- package/site/pages/api.html +16 -12
- package/site/pages/architecture.html +16 -12
- package/site/pages/deployment.html +16 -12
- package/site/pages/developer-guide.html +16 -12
- package/site/pages/extending.html +16 -12
- package/site/pages/quickstart.html +50 -38
- package/site/pages/seo-strategy.html +47 -26
- package/site/pages/tenant-config.html +91 -12
- package/site/pages/welcome.html +15 -11
- package/site/robots.txt +2 -2
- package/site/sections/quickstart.js +1 -1
- package/site/sections/seo-strategy.js +1 -1
- package/site/sections/tenant-config.js +1 -1
- package/site/seo.js +1 -1
- package/site/sitemap.xml +20 -20
- package/src/app.js +2 -1
- package/src/seo.js +28 -7
package/README.md
CHANGED
|
@@ -1,85 +1,104 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
1
3
|
# Pagenary Publisher
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
**Where documentation takes shape.**
|
|
6
|
+
|
|
7
|
+
`@pagenary/publisher` is the static site generator behind Pagenary — it turns one shared template catalog into many branded, tenant-specific documentation sites. Zero runtime dependencies, hash-based routing, full-text search, and a Git-aware build pipeline. Install it as a dev dependency and drive it with the `pagenary` CLI.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install --save-dev @pagenary/publisher # add Pagenary to your project
|
|
11
|
+
npx pagenary build:tenants my-docs # build your docs tenant
|
|
12
|
+
npx pagenary serve # serve on http://localhost:5173
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
[](https://www.npmjs.com/package/@pagenary/publisher)
|
|
16
|
+
[](https://www.npmjs.com/package/@pagenary/publisher)
|
|
17
|
+
[](https://docs.pagenary.com)
|
|
18
|
+
[](../../LICENSE)
|
|
19
|
+
[](https://nodejs.org)
|
|
20
|
+
|
|
21
|
+
[**Docs Site**](https://docs.pagenary.com) · [**Quick Start**](#quick-start) · [**Features**](#features) · [**Tenant Workflow**](#tenant-content-workflow) · [**Documentation**](#documentation)
|
|
22
|
+
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
---
|
|
4
26
|
|
|
5
|
-
|
|
27
|
+
## What It Is
|
|
28
|
+
|
|
29
|
+
The publisher takes a catalog of shared section templates plus per-tenant content and configuration and produces a self-contained documentation bundle for each tenant. Each bundle is a static single-page app — hash-based routing (`#/page-id`), no server-side rendering, no runtime dependencies — that you build once and host anywhere that serves files. Tenants share the template catalog but keep isolated content, branding, navigation, and domains, so one repository can publish a dozen distinct sites.
|
|
30
|
+
|
|
31
|
+
---
|
|
6
32
|
|
|
7
33
|
## Quick Start
|
|
8
34
|
|
|
35
|
+
Install the package and drive it with the `pagenary` CLI — **no clone required**.
|
|
36
|
+
New here? Follow the **[Getting Started guide](docs/GETTING-STARTED.md)**.
|
|
37
|
+
|
|
9
38
|
```bash
|
|
10
|
-
npm install
|
|
11
|
-
npm run dev # Build + serve with watch mode
|
|
39
|
+
npm install --save-dev @pagenary/publisher
|
|
12
40
|
|
|
13
|
-
#
|
|
14
|
-
|
|
15
|
-
npm run serve # Preview on http://localhost:5173
|
|
41
|
+
npx pagenary build:tenants my-docs # build your tenant (see Tenant Registry below)
|
|
42
|
+
npx pagenary serve # preview on http://localhost:5173
|
|
16
43
|
```
|
|
17
44
|
|
|
18
|
-
|
|
45
|
+
Commands: `build`, `build:tenants [id]`, `tenants:list`, `serve` (run
|
|
46
|
+
`npx pagenary --help`). The package also ships a compiled reference site under `site/` — the Pagenary docs, built by Pagenary itself.
|
|
19
47
|
|
|
20
|
-
|
|
21
|
-
- **Markdown** - Write in `.md` files with full CommonMark support
|
|
22
|
-
- **HTML** - Direct markup control with `.html` files
|
|
23
|
-
- **JavaScript Modules** - Dynamic content with `.js` files returning `{ html, afterRender? }`
|
|
24
|
-
- **Nested Directories** - Organize content in subdirectories (`content/guides/setup.md`)
|
|
48
|
+
**Building from source** (contributors / modifying Pagenary):
|
|
25
49
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
- **Internal Links** - Auto-resolved `#section-id` links in Markdown
|
|
50
|
+
```bash
|
|
51
|
+
npm install
|
|
52
|
+
npm run dev # build + serve with watch mode
|
|
53
|
+
npm run build # build default bundle to dist/
|
|
54
|
+
```
|
|
32
55
|
|
|
33
|
-
|
|
34
|
-
- **Navigation Links** - Add external URLs directly in manifest with `url` property
|
|
35
|
-
- **Smart Link Handling** - All external links open in new tab with security headers
|
|
36
|
-
- **Visual Indicators** - Subtle ↗ icon shows external destinations
|
|
37
|
-
- **CTA Styling** - Button-like `external-cta` class for prominent external links
|
|
56
|
+
---
|
|
38
57
|
|
|
39
|
-
|
|
40
|
-
```json
|
|
41
|
-
[
|
|
42
|
-
{ "id": "welcome", "title": "Welcome", "file": "welcome.md" },
|
|
43
|
-
{ "title": "External Resource", "url": "https://example.com" }
|
|
44
|
-
]
|
|
45
|
-
```
|
|
58
|
+
## Features
|
|
46
59
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
60
|
+
### Content Authoring
|
|
61
|
+
- **Markdown** — write in `.md` files with full CommonMark support
|
|
62
|
+
- **HTML** — direct markup control with `.html` files
|
|
63
|
+
- **JavaScript Modules** — dynamic content with `.js` files returning `{ html, afterRender? }`
|
|
64
|
+
- **Nested Directories** — organize content in subdirectories (`content/guides/setup.md`)
|
|
51
65
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
66
|
+
### Rich Content
|
|
67
|
+
- **Mermaid Diagrams** — flowcharts, sequence diagrams, state machines, and more
|
|
68
|
+
- **Syntax Highlighting** — Prism.js with 10+ language support
|
|
69
|
+
- **Markdown Tables** — full table syntax with alignment support
|
|
70
|
+
- **HTML Components** — spec tables, layer stacks, box diagrams, cards
|
|
71
|
+
- **Internal Links** — auto-resolved `#section-id` links in Markdown
|
|
58
72
|
|
|
59
|
-
|
|
60
|
-
-
|
|
61
|
-
-
|
|
62
|
-
-
|
|
63
|
-
-
|
|
73
|
+
### External Links
|
|
74
|
+
- **Navigation Links** — add external URLs directly in the manifest with a `url` property
|
|
75
|
+
- **Smart Link Handling** — external links open in a new tab with security headers (`rel="noopener noreferrer"`)
|
|
76
|
+
- **Visual Indicators** — a subtle ↗ icon marks external destinations
|
|
77
|
+
- **CTA Styling** — button-like `external-cta` class for prominent external links
|
|
64
78
|
|
|
65
79
|
### Navigation & Search
|
|
66
|
-
- **Command Palette**
|
|
67
|
-
- **Full-Text Search**
|
|
68
|
-
- **Manifest-Driven Nav**
|
|
69
|
-
- **Keyboard Navigation**
|
|
80
|
+
- **Command Palette** — `Ctrl/Cmd+K` or `/` opens a global finder
|
|
81
|
+
- **Full-Text Search** — searches all content, not just titles
|
|
82
|
+
- **Manifest-Driven Nav** — declarative navigation structure
|
|
83
|
+
- **Keyboard Navigation** — arrow keys, Enter to select
|
|
70
84
|
|
|
71
85
|
### Theming & Branding
|
|
72
|
-
- **Custom Colors**
|
|
73
|
-
- **Brand Identity**
|
|
74
|
-
- **Typography**
|
|
86
|
+
- **Custom Colors** — `accentColor` and `surfaceColor` per tenant
|
|
87
|
+
- **Brand Identity** — logo text, tagline, copyright
|
|
88
|
+
- **Typography** — IBM Plex Sans/Mono defaults, customizable
|
|
89
|
+
|
|
90
|
+
### SEO (built in)
|
|
91
|
+
- **Absolute URLs** — declare a `domain` (or `seo.siteUrl`) and the sitemap, canonical, `og:url`, and `robots` URLs become fully-qualified
|
|
92
|
+
- **Static snapshots** — crawler-friendly `/pages/<id>.html` for every section, self-canonical (the SPA hash route isn't crawlable)
|
|
93
|
+
- **`sitemap.xml`, `robots.txt`, `llms.txt`** — generated automatically
|
|
94
|
+
- **JSON-LD + Open Graph** — `TechArticle`/`BreadcrumbList` per page, optional Organization data, and `og:image`/`twitter:image` via `seo.ogImage`
|
|
75
95
|
|
|
76
96
|
### Export & Sharing
|
|
77
|
-
- **Export Options**
|
|
78
|
-
- **Branded Exports**
|
|
79
|
-
- **Document Export**
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
- **Table Rendering** - Markdown tables render correctly in exports
|
|
97
|
+
- **Export Options** — Current Page or Entire Site
|
|
98
|
+
- **Branded Exports** — tenant logo, brand name, and tagline in the export header
|
|
99
|
+
- **Document Export** — one-click HTML export with a table of contents, print-optimized for PDF
|
|
100
|
+
|
|
101
|
+
---
|
|
83
102
|
|
|
84
103
|
## Tenant Content Workflow
|
|
85
104
|
|
|
@@ -87,7 +106,7 @@ Visit our [support portal](https://support.example.com) for help.
|
|
|
87
106
|
|
|
88
107
|
```
|
|
89
108
|
my-tenant/
|
|
90
|
-
├── config.json # Branding and
|
|
109
|
+
├── config.json # Branding, theme, and SEO settings
|
|
91
110
|
├── manifest.json # Navigation structure (optional)
|
|
92
111
|
├── content/ # Content files
|
|
93
112
|
│ ├── welcome.md # Root-level content
|
|
@@ -148,14 +167,10 @@ export async function load() {
|
|
|
148
167
|
|
|
149
168
|
### Manifest Configuration
|
|
150
169
|
|
|
151
|
-
**Root manifest.json** (optional
|
|
170
|
+
**Root manifest.json** (optional — auto-generated from `content/` if omitted):
|
|
152
171
|
```json
|
|
153
172
|
[
|
|
154
|
-
{
|
|
155
|
-
"id": "welcome",
|
|
156
|
-
"title": "Welcome",
|
|
157
|
-
"file": "welcome.md"
|
|
158
|
-
},
|
|
173
|
+
{ "id": "welcome", "title": "Welcome", "file": "welcome.md" },
|
|
159
174
|
{
|
|
160
175
|
"id": "guides",
|
|
161
176
|
"title": "Guides",
|
|
@@ -178,23 +193,15 @@ export async function load() {
|
|
|
178
193
|
}
|
|
179
194
|
```
|
|
180
195
|
|
|
181
|
-
**External links in manifest** (use `url` instead of `
|
|
196
|
+
**External links in the manifest** (use `url` instead of `file`):
|
|
182
197
|
```json
|
|
183
198
|
[
|
|
184
199
|
{ "id": "welcome", "title": "Welcome", "file": "welcome.md" },
|
|
185
|
-
{ "title": "Support Portal", "url": "https://support.example.com" }
|
|
186
|
-
{
|
|
187
|
-
"id": "resources",
|
|
188
|
-
"title": "Resources",
|
|
189
|
-
"subsections": [
|
|
190
|
-
{ "id": "guides/overview", "title": "Overview", "file": "guides/overview.md" },
|
|
191
|
-
{ "title": "API Docs", "url": "https://api.example.com/docs" }
|
|
192
|
-
]
|
|
193
|
-
}
|
|
200
|
+
{ "title": "Support Portal", "url": "https://support.example.com" }
|
|
194
201
|
]
|
|
195
202
|
```
|
|
196
203
|
|
|
197
|
-
### Branding Configuration
|
|
204
|
+
### Branding & SEO Configuration
|
|
198
205
|
|
|
199
206
|
**config.json**:
|
|
200
207
|
```json
|
|
@@ -207,73 +214,86 @@ export async function load() {
|
|
|
207
214
|
"copyright": "ACME Corp",
|
|
208
215
|
"accentColor": "#6366F1",
|
|
209
216
|
"surfaceColor": "#F7FAFC",
|
|
210
|
-
"
|
|
211
|
-
|
|
212
|
-
"
|
|
213
|
-
"
|
|
214
|
-
"
|
|
217
|
+
"domain": "docs.acme.com",
|
|
218
|
+
"seo": {
|
|
219
|
+
"siteUrl": "https://docs.acme.com",
|
|
220
|
+
"ogImage": "/assets/og-card.png",
|
|
221
|
+
"structuredData": { "organizationName": "ACME Corporation" }
|
|
215
222
|
}
|
|
216
223
|
}
|
|
217
224
|
```
|
|
218
225
|
|
|
219
226
|
| Property | Description | Default |
|
|
220
227
|
|----------|-------------|---------|
|
|
221
|
-
| `title` | Browser tab title | "
|
|
228
|
+
| `title` | Browser tab title | "Documentation" |
|
|
222
229
|
| `description` | Meta description for SEO | - |
|
|
223
230
|
| `brandMark` | Primary brand text (bold) | "DOCS" |
|
|
224
231
|
| `brandSub` | Secondary brand text (light) | "TOOLKIT" |
|
|
225
232
|
| `tagline` | Subtitle under brand | - |
|
|
226
|
-
| `copyright` | Footer copyright text |
|
|
233
|
+
| `copyright` | Footer copyright text | - |
|
|
227
234
|
| `accentColor` | Links, buttons, highlights | `#111111` |
|
|
228
235
|
| `surfaceColor` | Background color (hex) | `#ffffff` |
|
|
229
|
-
| `
|
|
230
|
-
| `
|
|
231
|
-
|
|
232
|
-
|
|
236
|
+
| `domain` | Canonical domain; also the SEO base URL when `seo.siteUrl` is unset | - |
|
|
237
|
+
| `seo` | SEO block — see [Tenant Configuration](docs/TENANT-CONFIG.md#seo-seo) | - |
|
|
238
|
+
|
|
239
|
+
---
|
|
233
240
|
|
|
234
241
|
## Build Commands
|
|
235
242
|
|
|
243
|
+
With the package installed (the default):
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
npx pagenary build # build the default bundle to dist/
|
|
247
|
+
npx pagenary build:tenants # build all enabled tenants
|
|
248
|
+
npx pagenary build:tenants my-tenant # build a specific tenant
|
|
249
|
+
npx pagenary tenants:list # list configured tenants
|
|
250
|
+
npx pagenary serve # serve dist/ on localhost:5173
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
From source (clone — adds dev/utility scripts):
|
|
254
|
+
|
|
236
255
|
```bash
|
|
237
|
-
#
|
|
238
|
-
npm run
|
|
239
|
-
npm run
|
|
240
|
-
npm run
|
|
241
|
-
|
|
242
|
-
#
|
|
243
|
-
npm run build:incremental my-tenant # Only rebuild changed files
|
|
244
|
-
|
|
245
|
-
# Development
|
|
246
|
-
npm run dev # Build + serve with watch
|
|
247
|
-
npm run serve # Serve dist/ on localhost:5173
|
|
248
|
-
|
|
249
|
-
# Utilities
|
|
250
|
-
npm run lint:content # Check for trailing whitespace/tabs
|
|
251
|
-
npm run check:seo # Verify SEO metadata
|
|
252
|
-
npm run check # Run all checks
|
|
253
|
-
npm run sync:docs # Regenerate section templates
|
|
254
|
-
npm test # Run test suite
|
|
256
|
+
npm run build:incremental my-tenant # git-aware incremental rebuild
|
|
257
|
+
npm run dev # build + serve with watch
|
|
258
|
+
npm run lint:content # check trailing whitespace/tabs
|
|
259
|
+
npm run check:seo # verify SEO metadata
|
|
260
|
+
npm run check # run all checks
|
|
261
|
+
npm test # run test suite
|
|
255
262
|
```
|
|
256
263
|
|
|
264
|
+
---
|
|
265
|
+
|
|
257
266
|
## Tenant Registry
|
|
258
267
|
|
|
259
|
-
Register tenants in `tenants.json
|
|
268
|
+
Register tenants in a `tenants.json` at your project root (validated by the
|
|
269
|
+
bundled `tenants.schema.json`):
|
|
260
270
|
|
|
261
271
|
```json
|
|
262
272
|
{
|
|
263
|
-
"
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
273
|
+
"tenants": [
|
|
274
|
+
{
|
|
275
|
+
"id": "my-docs",
|
|
276
|
+
"source": { "type": "local", "path": "./docs" },
|
|
277
|
+
"strictLinks": true
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
"id": "client-portal",
|
|
281
|
+
"source": { "type": "git", "url": "https://github.com/org/client-docs.git", "ref": "main" },
|
|
282
|
+
"domains": ["docs.client.com"]
|
|
283
|
+
}
|
|
284
|
+
]
|
|
271
285
|
}
|
|
272
286
|
```
|
|
273
287
|
|
|
274
288
|
**Source types:**
|
|
275
|
-
- **Local path
|
|
276
|
-
- **Git
|
|
289
|
+
- **Local**: `{ "type": "local", "path": "./relative/or/abs/path" }`
|
|
290
|
+
- **Git**: `{ "type": "git", "url": "https://…", "ref": "main", "path": "subdir" }`
|
|
291
|
+
|
|
292
|
+
Per-tenant options include `enabled` (default `true`), `strictLinks` (default
|
|
293
|
+
`true` — fail the build on broken internal links), and `domains` (for the
|
|
294
|
+
multi-tenant Caddy router). See [Tenant Configuration](docs/TENANT-CONFIG.md).
|
|
295
|
+
|
|
296
|
+
---
|
|
277
297
|
|
|
278
298
|
## Docker Caddy Workflow
|
|
279
299
|
|
|
@@ -283,20 +303,19 @@ For multi-tenant domain testing:
|
|
|
283
303
|
# Add to /etc/hosts:
|
|
284
304
|
# 127.0.0.1 my-docs.local client-portal.local
|
|
285
305
|
|
|
286
|
-
|
|
287
|
-
npm run
|
|
288
|
-
npm run caddy:up
|
|
289
|
-
|
|
306
|
+
npm run build:tenants # build tenants
|
|
307
|
+
npm run caddy:up # start Caddy
|
|
290
308
|
# Visit http://my-docs.local or http://client-portal.local
|
|
291
309
|
|
|
292
|
-
#
|
|
293
|
-
npm run caddy:
|
|
294
|
-
npm run caddy:
|
|
295
|
-
npm run caddy:
|
|
296
|
-
npm run caddy:down # Stop container
|
|
310
|
+
npm run caddy:logs # tail logs
|
|
311
|
+
npm run caddy:reload # reload config without restart
|
|
312
|
+
npm run caddy:restart # full restart
|
|
313
|
+
npm run caddy:down # stop container
|
|
297
314
|
```
|
|
298
315
|
|
|
299
|
-
Use non-privileged port: `DOCS_TOOLKIT_PORT=5173 npm run caddy:up`
|
|
316
|
+
Use a non-privileged port: `DOCS_TOOLKIT_PORT=5173 npm run caddy:up`
|
|
317
|
+
|
|
318
|
+
---
|
|
300
319
|
|
|
301
320
|
## Repository Layout
|
|
302
321
|
|
|
@@ -307,31 +326,46 @@ apps/publisher/
|
|
|
307
326
|
│ ├── app.js # Router and core logic
|
|
308
327
|
│ ├── styles.css # All styling
|
|
309
328
|
│ ├── manifest.js # Default navigation
|
|
310
|
-
│ ├── seo.js #
|
|
329
|
+
│ ├── seo.js # Runtime meta tag management
|
|
311
330
|
│ ├── mermaid-init.js # Diagram rendering
|
|
312
331
|
│ ├── syntax-highlight.js # Code highlighting
|
|
313
|
-
│
|
|
314
|
-
│ │ ├── search.js # Full-text search
|
|
315
|
-
│ │ ├── router.js # Hash routing
|
|
316
|
-
│ │ └── export.js # Document export
|
|
317
|
-
│ └── sections/ # Default section modules
|
|
332
|
+
│ └── lib/ # search, router, export
|
|
318
333
|
├── scripts/
|
|
319
334
|
│ ├── build.js # Core build script
|
|
320
335
|
│ ├── build-tenants.js # Multi-tenant builder
|
|
321
336
|
│ ├── serve.js # Dev server
|
|
322
|
-
│ └──
|
|
337
|
+
│ └── lib/seo-generator.js # Sitemap, robots, snapshots, JSON-LD
|
|
323
338
|
├── tenants/ # Built-in example tenants
|
|
324
339
|
├── docs/ # Documentation
|
|
325
|
-
|
|
326
|
-
├── Caddyfile # Multi-tenant routing
|
|
327
|
-
└── docker-compose.yml # Caddy container
|
|
340
|
+
└── Caddyfile, docker-compose.yml # Multi-tenant routing
|
|
328
341
|
```
|
|
329
342
|
|
|
343
|
+
---
|
|
344
|
+
|
|
330
345
|
## Documentation
|
|
331
346
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
- [
|
|
335
|
-
- [
|
|
336
|
-
- [
|
|
337
|
-
- [
|
|
347
|
+
The full documentation site is published at **[docs.pagenary.com](https://docs.pagenary.com)** — built by this publisher from the source below. Read it online, or browse the source:
|
|
348
|
+
|
|
349
|
+
- [Getting Started](docs/GETTING-STARTED.md) — **start here**: zero to a published site with the npm package
|
|
350
|
+
- [Quick Start Guide](docs/QUICKSTART.md) — step-by-step tenant creation
|
|
351
|
+
- [Tenant Configuration](docs/TENANT-CONFIG.md) — all config options (branding, theme, SEO)
|
|
352
|
+
- [Architecture](docs/ARCHITECTURE.md) — system design
|
|
353
|
+
- [API Reference](docs/API.md) — module documentation
|
|
354
|
+
- [Deployment](docs/DEPLOYMENT.md) — hosting patterns
|
|
355
|
+
- [Extending](docs/EXTENDING.md) — customization guide
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## License
|
|
360
|
+
|
|
361
|
+
**GNU Affero General Public License v3.0** — strong copyleft. You may use, modify, and distribute Pagenary, but if you run a modified version to provide a network service, you must make the modified source available to its users. See [LICENSE](../../LICENSE).
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
<div align="center">
|
|
366
|
+
|
|
367
|
+
**[Back to Top](#pagenary-publisher)**
|
|
368
|
+
|
|
369
|
+
Made with care by [Joseph Magly](https://github.com/jmagly)
|
|
370
|
+
|
|
371
|
+
</div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pagenary/publisher",
|
|
3
|
-
"version": "2026.5.
|
|
3
|
+
"version": "2026.5.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Multi-tenant static publishing component for Pagenary platform.",
|
|
6
6
|
"license": "AGPL-3.0-or-later",
|
|
@@ -59,8 +59,10 @@
|
|
|
59
59
|
"engines": {
|
|
60
60
|
"node": ">=16"
|
|
61
61
|
},
|
|
62
|
-
"
|
|
63
|
-
"jest": "^29.7.0",
|
|
62
|
+
"optionalDependencies": {
|
|
64
63
|
"terser": "^5.44.0"
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"jest": "^29.7.0"
|
|
65
67
|
}
|
|
66
68
|
}
|
package/scripts/build-tenants.js
CHANGED
|
@@ -6,7 +6,8 @@ import path from 'path';
|
|
|
6
6
|
import { spawn, execSync } from 'child_process';
|
|
7
7
|
import { createHash } from 'crypto';
|
|
8
8
|
import os from 'os';
|
|
9
|
-
import { generateSeoArtifacts } from './lib/seo-generator.js';
|
|
9
|
+
import { generateSeoArtifacts, resolveBaseUrl, resolveOgImage } from './lib/seo-generator.js';
|
|
10
|
+
import { generateCollections } from './lib/collections-generator.js';
|
|
10
11
|
import { fileURLToPath } from 'node:url';
|
|
11
12
|
|
|
12
13
|
const root = process.cwd();
|
|
@@ -2646,9 +2647,11 @@ async function processNestedContent(sourceDir, distDir, tenantId, contentRoot, o
|
|
|
2646
2647
|
const siteConfig = {
|
|
2647
2648
|
bottomNav: rootManifest?.bottomNav || 'mobile',
|
|
2648
2649
|
bottomNavSections: rootManifest?.bottomNavSections || [],
|
|
2649
|
-
// Pass SEO-relevant config to SPA for dynamic meta tag updates
|
|
2650
|
+
// Pass SEO-relevant config to SPA for dynamic meta tag updates.
|
|
2651
|
+
// siteUrl falls back to `domain` (#15); ogImage drives social cards (#16).
|
|
2650
2652
|
siteTitle: config.title || '',
|
|
2651
|
-
siteUrl: config
|
|
2653
|
+
siteUrl: resolveBaseUrl(config),
|
|
2654
|
+
ogImage: resolveOgImage(config, resolveBaseUrl(config))
|
|
2652
2655
|
};
|
|
2653
2656
|
|
|
2654
2657
|
// Build export branding configuration
|
|
@@ -3212,6 +3215,14 @@ async function buildTenant(tenant, targetOverride, cacheDir, buildOptions) {
|
|
|
3212
3215
|
// Generate SEO artifacts (sitemap.xml, robots.txt, static pages)
|
|
3213
3216
|
await generateSeoArtifacts(distDir, config);
|
|
3214
3217
|
|
|
3218
|
+
// Generate collection manifests/feeds (#18) — opt-in via config.collections
|
|
3219
|
+
if (Array.isArray(config.collections) && config.collections.length > 0) {
|
|
3220
|
+
const collectionRoot = await findContentRoot(sourceDir);
|
|
3221
|
+
if (collectionRoot.basePath) {
|
|
3222
|
+
await generateCollections(distDir, config, collectionRoot.basePath);
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
|
|
3215
3226
|
// Copy to final target if different from dist
|
|
3216
3227
|
if (targetDir !== distDir) {
|
|
3217
3228
|
// Ensure target parent exists
|