@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 CHANGED
@@ -1,85 +1,104 @@
1
+ <div align="center">
2
+
1
3
  # Pagenary Publisher
2
4
 
3
- Static publishing component for Pagenary — "Where documentation takes shape."
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
+ [![npm version](https://img.shields.io/npm/v/@pagenary/publisher?label=npm&color=CB3837&logo=npm&style=flat-square)](https://www.npmjs.com/package/@pagenary/publisher)
16
+ [![npm downloads](https://img.shields.io/npm/dm/@pagenary/publisher?color=CB3837&logo=npm&style=flat-square)](https://www.npmjs.com/package/@pagenary/publisher)
17
+ [![Docs](https://img.shields.io/badge/docs-docs.pagenary.com-22d3ee?style=flat-square&logo=readthedocs&logoColor=white)](https://docs.pagenary.com)
18
+ [![License: AGPL v3](https://img.shields.io/badge/License-AGPL_v3-blue.svg?style=flat-square)](../../LICENSE)
19
+ [![Node Version](https://img.shields.io/badge/node-%E2%89%A516.0.0-brightgreen?style=flat-square&logo=node.js)](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
- Transform shared documentation templates into tenant-specific bundles with custom branding, themes, and content. Zero runtime dependencies, hash-based routing, and full-text search make it ideal for white-label documentation portals.
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
- # Or separately:
14
- npm run build # Build default bundle to dist/
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
- ## Features
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
- ### Content Authoring
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
- ### Rich Content
27
- - **Mermaid Diagrams** - Flowcharts, sequence diagrams, state machines, and more
28
- - **Syntax Highlighting** - Prism.js with 10+ language support
29
- - **Markdown Tables** - Full table syntax with alignment support
30
- - **HTML Components** - Spec tables, layer stacks, box diagrams, cards
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
- ### External Links
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
- **External navigation example** (manifest.json):
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
- **External links in Markdown** (auto-handled):
48
- ```markdown
49
- Visit our [support portal](https://support.example.com) for help.
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
- **Prominent CTA in HTML**:
53
- ```html
54
- <a href="https://example.com" target="_blank" rel="noopener noreferrer" class="external-cta">
55
- Get Started
56
- </a>
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
- **Security & UX:**
60
- - All external links use `target="_blank"` and `rel="noopener noreferrer"` by default
61
- - Navigation external links show indicator
62
- - Content external links styled with subtle ↗ after link text
63
- - No configuration needed - works automatically for `http://` and `https://` URLs
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** - `Ctrl/Cmd+K` or `/` opens global finder
67
- - **Full-Text Search** - Searches all content, not just titles
68
- - **Manifest-Driven Nav** - Declarative navigation structure
69
- - **Keyboard Navigation** - Arrow keys, Enter to select
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** - `accentColor` and `surfaceColor` per tenant
73
- - **Brand Identity** - Logo text, tagline, copyright
74
- - **Typography** - IBM Plex Sans/Mono defaults, customizable
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** - Choose between Current Page or Entire Site export
78
- - **Branded Exports** - Tenant logo, brand name, and tagline in export header
79
- - **Document Export** - One-click HTML export with TOC
80
- - **Print Styles** - Optimized for PDF generation
81
- - **Syntax Highlighting** - Preserved in exports
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 theme settings
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 - auto-generated from content/ if omitted):
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 `id`):
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
- "export": {
211
- "logo": "embed",
212
- "logoPath": "favicon.png",
213
- "showTagline": true,
214
- "showDate": true
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 | "Docs Toolkit" |
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 | "Modular Documentation Toolkit" |
233
+ | `copyright` | Footer copyright text | - |
227
234
  | `accentColor` | Links, buttons, highlights | `#111111` |
228
235
  | `surfaceColor` | Background color (hex) | `#ffffff` |
229
- | `export.logo` | Logo mode: `"embed"`, `"reference"`, or `null` | `"embed"` |
230
- | `export.logoPath` | Path to logo in `.public/` directory | Auto-detect |
231
- | `export.showTagline` | Show tagline in export header | `true` |
232
- | `export.showDate` | Show generation date in export | `true` |
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
- # Full builds
238
- npm run build # Build default bundle
239
- npm run build:tenants # Build all registered tenants
240
- npm run build:tenants my-tenant # Build specific tenant
241
-
242
- # Incremental builds (git-aware)
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
- "my-docs": {
264
- "source": "/absolute/path/to/my-docs",
265
- "domain": "my-docs.local"
266
- },
267
- "client-portal": {
268
- "source": "git:https://github.com/org/client-docs.git#main",
269
- "domain": "docs.client.com"
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**: `/absolute/path/to/content`
276
- - **Git repository**: `git:https://github.com/org/repo.git#branch`
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
- # Build tenants and start Caddy
287
- npm run build:tenants
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
- # Management commands
293
- npm run caddy:logs # Tail logs
294
- npm run caddy:reload # Reload config without restart
295
- npm run caddy:restart # Full restart
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 # Meta tag management
329
+ │ ├── seo.js # Runtime meta tag management
311
330
  │ ├── mermaid-init.js # Diagram rendering
312
331
  │ ├── syntax-highlight.js # Code highlighting
313
- ├── lib/
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
- │ └── sync-docs.js # Template sync
337
+ │ └── lib/seo-generator.js # Sitemap, robots, snapshots, JSON-LD
323
338
  ├── tenants/ # Built-in example tenants
324
339
  ├── docs/ # Documentation
325
- ├── dist/ # Build output
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
- - [Quick Start Guide](docs/QUICKSTART.md) - Step-by-step tenant creation
333
- - [Tenant Configuration](docs/TENANT-CONFIG.md) - All config options
334
- - [Architecture](docs/ARCHITECTURE.md) - System design
335
- - [API Reference](docs/API.md) - Module documentation
336
- - [Deployment](docs/DEPLOYMENT.md) - Hosting patterns
337
- - [Extending](docs/EXTENDING.md) - Customization guide
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.1",
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
- "devDependencies": {
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
  }
@@ -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.seo?.siteUrl || ''
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