@pagenary/publisher 2026.5.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/LICENSE +661 -0
- package/README.md +337 -0
- package/bin/pagenary.mjs +116 -0
- package/build.config.json +5 -0
- package/package.json +66 -0
- package/scripts/build-site.js +87 -0
- package/scripts/build-tenants.js +3569 -0
- package/scripts/build.js +99 -0
- package/scripts/generate-sections.js +41 -0
- package/scripts/lib/seo-generator.js +558 -0
- package/scripts/lint-content.js +62 -0
- package/scripts/seo-smoke.js +94 -0
- package/scripts/serve.js +142 -0
- package/site/app.js +1 -0
- package/site/index.html +57 -0
- package/site/lib/categories.js +1 -0
- package/site/lib/export.js +1 -0
- package/site/lib/manifest-utils.js +1 -0
- package/site/lib/router.js +1 -0
- package/site/lib/search.js +1 -0
- package/site/llms.txt +22 -0
- package/site/manifest.js +132 -0
- package/site/mermaid-init.js +1 -0
- package/site/pages/api.html +339 -0
- package/site/pages/architecture.html +303 -0
- package/site/pages/deployment.html +282 -0
- package/site/pages/developer-guide.html +157 -0
- package/site/pages/extending.html +135 -0
- package/site/pages/quickstart.html +318 -0
- package/site/pages/seo-strategy.html +121 -0
- package/site/pages/tenant-config.html +519 -0
- package/site/pages/welcome.html +116 -0
- package/site/robots.txt +10 -0
- package/site/sections/api.js +3 -0
- package/site/sections/architecture.js +3 -0
- package/site/sections/deployment.js +3 -0
- package/site/sections/developer-guide.js +3 -0
- package/site/sections/extending.js +3 -0
- package/site/sections/quickstart.js +3 -0
- package/site/sections/section-templates.js +1 -0
- package/site/sections/seo-strategy.js +3 -0
- package/site/sections/tenant-config.js +3 -0
- package/site/sections/welcome.js +3 -0
- package/site/seo.js +1 -0
- package/site/sitemap.xml +63 -0
- package/site/styles.css +1982 -0
- package/site/syntax-highlight.js +1 -0
- package/src/app.js +988 -0
- package/src/index.html +56 -0
- package/src/lib/categories.js +55 -0
- package/src/lib/export.js +195 -0
- package/src/lib/manifest-utils.js +69 -0
- package/src/lib/router.js +44 -0
- package/src/lib/search.js +151 -0
- package/src/manifest.js +246 -0
- package/src/mermaid-init.js +207 -0
- package/src/sections/archive-future-roadmap.js +7 -0
- package/src/sections/archive-initiative-alpha.js +7 -0
- package/src/sections/archive-milestone-records.js +7 -0
- package/src/sections/archive-timeline-overview.js +7 -0
- package/src/sections/core-technology-compliance-frameworks.js +7 -0
- package/src/sections/core-technology-coordination-model.js +7 -0
- package/src/sections/core-technology-data-definitions.js +7 -0
- package/src/sections/core-technology-hardware-integration.js +7 -0
- package/src/sections/core-technology-integrity-controls.js +7 -0
- package/src/sections/core-technology-network-topology.js +7 -0
- package/src/sections/core-technology-operator-requirements.js +7 -0
- package/src/sections/core-technology-overview.js +7 -0
- package/src/sections/core-technology-service-interfaces.js +7 -0
- package/src/sections/core-technology-synchronization-strategy.js +7 -0
- package/src/sections/core-technology-system-foundation.js +7 -0
- package/src/sections/developers-api-credentials.js +7 -0
- package/src/sections/developers-api-operations.js +7 -0
- package/src/sections/developers-api-reference.js +7 -0
- package/src/sections/developers-api-websocket.js +7 -0
- package/src/sections/developers-automation-blueprints.js +7 -0
- package/src/sections/developers-automation-modules.js +7 -0
- package/src/sections/developers-automation-patterns.js +7 -0
- package/src/sections/developers-deployment-playbook.js +7 -0
- package/src/sections/developers-overview.js +7 -0
- package/src/sections/developers-scheduling-patterns.js +7 -0
- package/src/sections/developers-sdk-go.js +7 -0
- package/src/sections/developers-sdk-javascript.js +7 -0
- package/src/sections/developers-sdk-python.js +7 -0
- package/src/sections/developers-sdk-rust.js +7 -0
- package/src/sections/developers-sdks.js +7 -0
- package/src/sections/developers-solution-examples.js +7 -0
- package/src/sections/developers-testing-framework.js +7 -0
- package/src/sections/getting-started-architecture-basics.js +7 -0
- package/src/sections/getting-started-introduction.js +7 -0
- package/src/sections/getting-started-performance-overview.js +7 -0
- package/src/sections/governance-community-initiatives.js +7 -0
- package/src/sections/governance-dao-overview.js +7 -0
- package/src/sections/governance-multi-token.js +7 -0
- package/src/sections/governance-overview.js +7 -0
- package/src/sections/governance-proposal-process.js +7 -0
- package/src/sections/governance-proposals.js +7 -0
- package/src/sections/governance-structure.js +7 -0
- package/src/sections/governance-token-distribution.js +7 -0
- package/src/sections/governance-treasury.js +7 -0
- package/src/sections/operations-environment-prep.js +7 -0
- package/src/sections/operations-getting-started.js +7 -0
- package/src/sections/operations-incentives-guide.js +7 -0
- package/src/sections/operations-incentives-strategies.js +7 -0
- package/src/sections/operations-incentives.js +7 -0
- package/src/sections/operations-infrastructure.js +7 -0
- package/src/sections/operations-monitoring.js +7 -0
- package/src/sections/operations-overview.js +7 -0
- package/src/sections/operations-performance.js +7 -0
- package/src/sections/operations-power-infrastructure.js +7 -0
- package/src/sections/operations-setup-guide.js +7 -0
- package/src/sections/operations-sync-setup.js +7 -0
- package/src/sections/products-flagship-solution.js +7 -0
- package/src/sections/products-solution-library.js +7 -0
- package/src/sections/resources-brand-assets.js +7 -0
- package/src/sections/resources-faq.js +7 -0
- package/src/sections/resources-glossary.js +7 -0
- package/src/sections/resources-research-papers.js +7 -0
- package/src/sections/section-templates.js +873 -0
- package/src/sections/security-audits.js +7 -0
- package/src/sections/security-best-practices.js +7 -0
- package/src/sections/security-bug-bounty.js +7 -0
- package/src/sections/security-incident-response.js +7 -0
- package/src/sections/security-overview.js +7 -0
- package/src/sections/technical-architecture.js +7 -0
- package/src/sections/technical-whitepaper.js +7 -0
- package/src/sections/tutorial-automation-bot.js +7 -0
- package/src/sections/tutorial-build-first-integration.js +7 -0
- package/src/sections/tutorial-deploy-automation.js +7 -0
- package/src/sections/tutorial-event-driven-experience.js +7 -0
- package/src/sections/tutorial-operations-onboarding.js +7 -0
- package/src/sections/tutorial-systems-integration.js +7 -0
- package/src/sections/tutorials-overview.js +7 -0
- package/src/sections/use-case-connected-devices.js +7 -0
- package/src/sections/use-case-digital-auctions.js +7 -0
- package/src/sections/use-case-financial-automation.js +7 -0
- package/src/sections/use-case-interactive-media.js +7 -0
- package/src/sections/use-case-realtime-execution.js +7 -0
- package/src/sections/use-case-research-analytics.js +7 -0
- package/src/sections/use-case-supply-operations.js +7 -0
- package/src/sections/use-cases-overview.js +7 -0
- package/src/sections/welcome-overview.js +7 -0
- package/src/seo.js +90 -0
- package/src/styles.css +1982 -0
- package/src/syntax-highlight.js +90 -0
- package/tenants.json.example +68 -0
- package/tenants.schema.json +231 -0
package/README.md
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
# Pagenary Publisher
|
|
2
|
+
|
|
3
|
+
Static publishing component for Pagenary — "Where documentation takes shape."
|
|
4
|
+
|
|
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.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install
|
|
11
|
+
npm run dev # Build + serve with watch mode
|
|
12
|
+
|
|
13
|
+
# Or separately:
|
|
14
|
+
npm run build # Build default bundle to dist/
|
|
15
|
+
npm run serve # Preview on http://localhost:5173
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
|
|
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`)
|
|
25
|
+
|
|
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
|
|
32
|
+
|
|
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
|
|
38
|
+
|
|
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
|
+
```
|
|
46
|
+
|
|
47
|
+
**External links in Markdown** (auto-handled):
|
|
48
|
+
```markdown
|
|
49
|
+
Visit our [support portal](https://support.example.com) for help.
|
|
50
|
+
```
|
|
51
|
+
|
|
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
|
+
```
|
|
58
|
+
|
|
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
|
|
64
|
+
|
|
65
|
+
### 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
|
|
70
|
+
|
|
71
|
+
### 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
|
|
75
|
+
|
|
76
|
+
### 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
|
|
83
|
+
|
|
84
|
+
## Tenant Content Workflow
|
|
85
|
+
|
|
86
|
+
### Directory Structure
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
my-tenant/
|
|
90
|
+
├── config.json # Branding and theme settings
|
|
91
|
+
├── manifest.json # Navigation structure (optional)
|
|
92
|
+
├── content/ # Content files
|
|
93
|
+
│ ├── welcome.md # Root-level content
|
|
94
|
+
│ ├── guides/ # Nested directory
|
|
95
|
+
│ │ ├── _manifest.json # Section manifest
|
|
96
|
+
│ │ ├── getting-started.md
|
|
97
|
+
│ │ └── advanced.md
|
|
98
|
+
│ └── api/
|
|
99
|
+
│ ├── _manifest.json
|
|
100
|
+
│ └── reference.md
|
|
101
|
+
└── overrides/ # Post-build file replacements (optional)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Content Types
|
|
105
|
+
|
|
106
|
+
**Markdown (.md)**
|
|
107
|
+
```markdown
|
|
108
|
+
# Getting Started
|
|
109
|
+
|
|
110
|
+
Welcome to the docs. Here's a code example:
|
|
111
|
+
|
|
112
|
+
\`\`\`javascript
|
|
113
|
+
console.log('Hello, Pagenary!');
|
|
114
|
+
\`\`\`
|
|
115
|
+
|
|
116
|
+
And a Mermaid diagram:
|
|
117
|
+
|
|
118
|
+
\`\`\`mermaid
|
|
119
|
+
graph LR
|
|
120
|
+
A[Start] --> B[Build]
|
|
121
|
+
B --> C[Deploy]
|
|
122
|
+
\`\`\`
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**HTML (.html)**
|
|
126
|
+
```html
|
|
127
|
+
<section class="section doc">
|
|
128
|
+
<h1>Custom HTML Section</h1>
|
|
129
|
+
<table class="spec-table">
|
|
130
|
+
<tr><th>Feature</th><th>Status</th></tr>
|
|
131
|
+
<tr><td>Search</td><td>Ready</td></tr>
|
|
132
|
+
</table>
|
|
133
|
+
</section>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**JavaScript (.js)**
|
|
137
|
+
```javascript
|
|
138
|
+
export async function load() {
|
|
139
|
+
const data = await fetch('/api/metrics.json').then(r => r.json());
|
|
140
|
+
return {
|
|
141
|
+
html: `<section><h1>Metrics: ${data.count}</h1></section>`,
|
|
142
|
+
afterRender(container) {
|
|
143
|
+
// DOM manipulation after render
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Manifest Configuration
|
|
150
|
+
|
|
151
|
+
**Root manifest.json** (optional - auto-generated from content/ if omitted):
|
|
152
|
+
```json
|
|
153
|
+
[
|
|
154
|
+
{
|
|
155
|
+
"id": "welcome",
|
|
156
|
+
"title": "Welcome",
|
|
157
|
+
"file": "welcome.md"
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"id": "guides",
|
|
161
|
+
"title": "Guides",
|
|
162
|
+
"subsections": [
|
|
163
|
+
{ "id": "guides/getting-started", "title": "Getting Started", "file": "guides/getting-started.md" },
|
|
164
|
+
{ "id": "guides/advanced", "title": "Advanced Usage", "file": "guides/advanced.md" }
|
|
165
|
+
]
|
|
166
|
+
}
|
|
167
|
+
]
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Section _manifest.json** (in content subdirectories):
|
|
171
|
+
```json
|
|
172
|
+
{
|
|
173
|
+
"title": "API Reference",
|
|
174
|
+
"sections": [
|
|
175
|
+
{ "id": "overview", "title": "Overview", "file": "overview.md" },
|
|
176
|
+
{ "id": "endpoints", "title": "Endpoints", "file": "endpoints.md" }
|
|
177
|
+
]
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**External links in manifest** (use `url` instead of `id`):
|
|
182
|
+
```json
|
|
183
|
+
[
|
|
184
|
+
{ "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
|
+
}
|
|
194
|
+
]
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Branding Configuration
|
|
198
|
+
|
|
199
|
+
**config.json**:
|
|
200
|
+
```json
|
|
201
|
+
{
|
|
202
|
+
"title": "My Documentation",
|
|
203
|
+
"description": "Comprehensive guide to our platform",
|
|
204
|
+
"brandMark": "ACME",
|
|
205
|
+
"brandSub": "Docs",
|
|
206
|
+
"tagline": "Build better, faster",
|
|
207
|
+
"copyright": "ACME Corp",
|
|
208
|
+
"accentColor": "#6366F1",
|
|
209
|
+
"surfaceColor": "#F7FAFC",
|
|
210
|
+
"export": {
|
|
211
|
+
"logo": "embed",
|
|
212
|
+
"logoPath": "favicon.png",
|
|
213
|
+
"showTagline": true,
|
|
214
|
+
"showDate": true
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
| Property | Description | Default |
|
|
220
|
+
|----------|-------------|---------|
|
|
221
|
+
| `title` | Browser tab title | "Docs Toolkit" |
|
|
222
|
+
| `description` | Meta description for SEO | - |
|
|
223
|
+
| `brandMark` | Primary brand text (bold) | "DOCS" |
|
|
224
|
+
| `brandSub` | Secondary brand text (light) | "TOOLKIT" |
|
|
225
|
+
| `tagline` | Subtitle under brand | - |
|
|
226
|
+
| `copyright` | Footer copyright text | "Modular Documentation Toolkit" |
|
|
227
|
+
| `accentColor` | Links, buttons, highlights | `#111111` |
|
|
228
|
+
| `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` |
|
|
233
|
+
|
|
234
|
+
## Build Commands
|
|
235
|
+
|
|
236
|
+
```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
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Tenant Registry
|
|
258
|
+
|
|
259
|
+
Register tenants in `tenants.json`:
|
|
260
|
+
|
|
261
|
+
```json
|
|
262
|
+
{
|
|
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
|
+
}
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**Source types:**
|
|
275
|
+
- **Local path**: `/absolute/path/to/content`
|
|
276
|
+
- **Git repository**: `git:https://github.com/org/repo.git#branch`
|
|
277
|
+
|
|
278
|
+
## Docker Caddy Workflow
|
|
279
|
+
|
|
280
|
+
For multi-tenant domain testing:
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
# Add to /etc/hosts:
|
|
284
|
+
# 127.0.0.1 my-docs.local client-portal.local
|
|
285
|
+
|
|
286
|
+
# Build tenants and start Caddy
|
|
287
|
+
npm run build:tenants
|
|
288
|
+
npm run caddy:up
|
|
289
|
+
|
|
290
|
+
# Visit http://my-docs.local or http://client-portal.local
|
|
291
|
+
|
|
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
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Use non-privileged port: `DOCS_TOOLKIT_PORT=5173 npm run caddy:up`
|
|
300
|
+
|
|
301
|
+
## Repository Layout
|
|
302
|
+
|
|
303
|
+
```
|
|
304
|
+
apps/publisher/
|
|
305
|
+
├── src/
|
|
306
|
+
│ ├── index.html # SPA shell
|
|
307
|
+
│ ├── app.js # Router and core logic
|
|
308
|
+
│ ├── styles.css # All styling
|
|
309
|
+
│ ├── manifest.js # Default navigation
|
|
310
|
+
│ ├── seo.js # Meta tag management
|
|
311
|
+
│ ├── mermaid-init.js # Diagram rendering
|
|
312
|
+
│ ├── 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
|
|
318
|
+
├── scripts/
|
|
319
|
+
│ ├── build.js # Core build script
|
|
320
|
+
│ ├── build-tenants.js # Multi-tenant builder
|
|
321
|
+
│ ├── serve.js # Dev server
|
|
322
|
+
│ └── sync-docs.js # Template sync
|
|
323
|
+
├── tenants/ # Built-in example tenants
|
|
324
|
+
├── docs/ # Documentation
|
|
325
|
+
├── dist/ # Build output
|
|
326
|
+
├── Caddyfile # Multi-tenant routing
|
|
327
|
+
└── docker-compose.yml # Caddy container
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## Documentation
|
|
331
|
+
|
|
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
|
package/bin/pagenary.mjs
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* pagenary — CLI entry point for @pagenary/publisher.
|
|
4
|
+
*
|
|
5
|
+
* A thin wrapper over the existing scripts/*.js ESM entry points so the
|
|
6
|
+
* package works as `npx @pagenary/publisher <command>` and as an installed
|
|
7
|
+
* devDependency CLI (`pagenary <command>`). Scripts are resolved relative to
|
|
8
|
+
* this file (the installed package), never the caller's CWD, so the right
|
|
9
|
+
* generator code runs regardless of where the command is invoked.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { spawn } from 'node:child_process';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
|
|
16
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const scriptsDir = path.join(__dirname, '..', 'scripts');
|
|
18
|
+
|
|
19
|
+
// command -> { script, baseArgs, summary }
|
|
20
|
+
const COMMANDS = {
|
|
21
|
+
build: {
|
|
22
|
+
script: 'build.js',
|
|
23
|
+
baseArgs: [],
|
|
24
|
+
summary: 'Build the default bundle to dist/.'
|
|
25
|
+
},
|
|
26
|
+
'build:tenants': {
|
|
27
|
+
script: 'build-tenants.js',
|
|
28
|
+
baseArgs: [],
|
|
29
|
+
summary: 'Build tenant bundles to dist/<id>/. Pass a tenant id to build one.'
|
|
30
|
+
},
|
|
31
|
+
'tenants:list': {
|
|
32
|
+
script: 'build-tenants.js',
|
|
33
|
+
baseArgs: ['--list'],
|
|
34
|
+
summary: 'List configured tenants from the registry.'
|
|
35
|
+
},
|
|
36
|
+
serve: {
|
|
37
|
+
script: 'serve.js',
|
|
38
|
+
baseArgs: [],
|
|
39
|
+
summary: 'Serve the built output over HTTP.'
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Convenience aliases.
|
|
44
|
+
const ALIASES = {
|
|
45
|
+
'build:tenant': 'build:tenants'
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
function printHelp() {
|
|
49
|
+
const lines = [
|
|
50
|
+
'pagenary — multi-tenant documentation publisher',
|
|
51
|
+
'',
|
|
52
|
+
'Usage:',
|
|
53
|
+
' pagenary <command> [options]',
|
|
54
|
+
' npx @pagenary/publisher <command> [options]',
|
|
55
|
+
'',
|
|
56
|
+
'Commands:'
|
|
57
|
+
];
|
|
58
|
+
const width = Math.max(...Object.keys(COMMANDS).map((c) => c.length));
|
|
59
|
+
for (const [name, def] of Object.entries(COMMANDS)) {
|
|
60
|
+
lines.push(` ${name.padEnd(width)} ${def.summary}`);
|
|
61
|
+
}
|
|
62
|
+
lines.push(
|
|
63
|
+
'',
|
|
64
|
+
'Examples:',
|
|
65
|
+
' pagenary build',
|
|
66
|
+
' pagenary build:tenants # build all enabled tenants',
|
|
67
|
+
' pagenary build:tenants pagenary # build one tenant',
|
|
68
|
+
' pagenary tenants:list',
|
|
69
|
+
' pagenary serve',
|
|
70
|
+
'',
|
|
71
|
+
'Any extra options are passed through to the underlying script, e.g.',
|
|
72
|
+
' pagenary build:tenants --incremental',
|
|
73
|
+
''
|
|
74
|
+
);
|
|
75
|
+
console.log(lines.join('\n'));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function main() {
|
|
79
|
+
const [, , rawCommand, ...rest] = process.argv;
|
|
80
|
+
|
|
81
|
+
if (!rawCommand || rawCommand === '--help' || rawCommand === '-h' || rawCommand === 'help') {
|
|
82
|
+
printHelp();
|
|
83
|
+
process.exit(0);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const command = ALIASES[rawCommand] || rawCommand;
|
|
87
|
+
const def = COMMANDS[command];
|
|
88
|
+
|
|
89
|
+
if (!def) {
|
|
90
|
+
console.error(`pagenary: unknown command "${rawCommand}"\n`);
|
|
91
|
+
printHelp();
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const scriptPath = path.join(scriptsDir, def.script);
|
|
96
|
+
const args = [scriptPath, ...def.baseArgs, ...rest];
|
|
97
|
+
|
|
98
|
+
const child = spawn(process.execPath, args, {
|
|
99
|
+
stdio: 'inherit',
|
|
100
|
+
cwd: process.cwd()
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
child.on('error', (err) => {
|
|
104
|
+
console.error(`pagenary: failed to run ${command}: ${err.message}`);
|
|
105
|
+
process.exit(1);
|
|
106
|
+
});
|
|
107
|
+
child.on('exit', (code, signal) => {
|
|
108
|
+
if (signal) {
|
|
109
|
+
process.kill(process.pid, signal);
|
|
110
|
+
} else {
|
|
111
|
+
process.exit(code ?? 0);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pagenary/publisher",
|
|
3
|
+
"version": "2026.5.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Multi-tenant static publishing component for Pagenary platform.",
|
|
6
|
+
"license": "AGPL-3.0-or-later",
|
|
7
|
+
"homepage": "https://git.integrolabs.net/roctinam/pagenary",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/jmagly/pagenary.git",
|
|
11
|
+
"directory": "apps/publisher"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"bin": {
|
|
17
|
+
"pagenary": "bin/pagenary.mjs"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"src/",
|
|
21
|
+
"scripts/",
|
|
22
|
+
"site/",
|
|
23
|
+
"build.config.json",
|
|
24
|
+
"tenants.schema.json",
|
|
25
|
+
"tenants.json.example",
|
|
26
|
+
"README.md",
|
|
27
|
+
"LICENSE"
|
|
28
|
+
],
|
|
29
|
+
"scripts": {
|
|
30
|
+
"dev": "npm run build:dev && npm run serve:dev",
|
|
31
|
+
"build": "node scripts/build.js",
|
|
32
|
+
"build:dev": "NODE_ENV=development node scripts/build.js --dev",
|
|
33
|
+
"build:tenants": "node scripts/build-tenants.js",
|
|
34
|
+
"build:tenant": "node scripts/build-tenants.js",
|
|
35
|
+
"build:site": "node scripts/build-tenants.js pagenary && node scripts/build-site.js",
|
|
36
|
+
"prepack": "node scripts/build-tenants.js pagenary >&2 && node scripts/build-site.js >&2",
|
|
37
|
+
"build:incremental": "node scripts/build-tenants.js --incremental",
|
|
38
|
+
"build:diff": "node scripts/build-tenants.js --diff-only",
|
|
39
|
+
"tenants:list": "node scripts/build-tenants.js --list",
|
|
40
|
+
"tenants:diff": "node scripts/build-tenants.js --diff-only",
|
|
41
|
+
"serve": "node scripts/serve.js",
|
|
42
|
+
"serve:dev": "NODE_ENV=development node scripts/serve.js --dev",
|
|
43
|
+
"start": "npm run build && npm run serve",
|
|
44
|
+
"clean": "node -e \"require('fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
45
|
+
"sync:docs": "node scripts/generate-sections.js",
|
|
46
|
+
"lint:content": "node scripts/lint-content.js",
|
|
47
|
+
"check:seo": "node scripts/seo-smoke.js",
|
|
48
|
+
"check": "npm run lint:content && npm run build && npm run check:seo",
|
|
49
|
+
"test": "node --experimental-vm-modules ../../node_modules/jest/bin/jest.js",
|
|
50
|
+
"test:coverage": "node --experimental-vm-modules ../../node_modules/jest/bin/jest.js --coverage",
|
|
51
|
+
"test:watch": "node --experimental-vm-modules ../../node_modules/jest/bin/jest.js --watch",
|
|
52
|
+
"ci": "npm run check && npm test",
|
|
53
|
+
"caddy:up": "docker compose up -d caddy",
|
|
54
|
+
"caddy:down": "docker compose down",
|
|
55
|
+
"caddy:restart": "docker compose restart caddy",
|
|
56
|
+
"caddy:logs": "docker compose logs -f caddy",
|
|
57
|
+
"caddy:reload": "docker compose exec caddy caddy reload --config /srv/app/Caddyfile"
|
|
58
|
+
},
|
|
59
|
+
"engines": {
|
|
60
|
+
"node": ">=16"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"jest": "^29.7.0",
|
|
64
|
+
"terser": "^5.44.0"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Flatten the compiled `pagenary` tenant bundle into a top-level `site/`
|
|
4
|
+
* directory so it ships inside the published @pagenary/publisher tarball
|
|
5
|
+
* (see issue #7). This runs from the `prepack` lifecycle hook, after
|
|
6
|
+
* `build:tenants pagenary` has produced `dist/pagenary/`.
|
|
7
|
+
*
|
|
8
|
+
* `prepack` fires for `npm pack` and `npm publish` but NOT for a consumer's
|
|
9
|
+
* `npm install` (that would be `prepare`), so installing the package never
|
|
10
|
+
* triggers a site build.
|
|
11
|
+
*
|
|
12
|
+
* Output shape: site/index.html (+ assets) at the top level — not
|
|
13
|
+
* site/pagenary/ — matching the tarball-audit expectation.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { promises as fsp } from 'node:fs';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
|
|
20
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
const root = path.join(__dirname, '..');
|
|
22
|
+
const srcDir = path.join(root, 'dist', 'pagenary');
|
|
23
|
+
const destDir = path.join(root, 'site');
|
|
24
|
+
|
|
25
|
+
async function pathExists(target) {
|
|
26
|
+
try {
|
|
27
|
+
await fsp.access(target);
|
|
28
|
+
return true;
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Recursively copy a directory. Follows symlinks (the pagenary tenant uses
|
|
36
|
+
* repo-relative symlinks for source content, but dist/ is already a fully
|
|
37
|
+
* materialized build, so this copy sees regular files).
|
|
38
|
+
* Returns { files, bytes }.
|
|
39
|
+
*/
|
|
40
|
+
async function copyDir(from, to) {
|
|
41
|
+
await fsp.mkdir(to, { recursive: true });
|
|
42
|
+
const entries = await fsp.readdir(from, { withFileTypes: true });
|
|
43
|
+
let files = 0;
|
|
44
|
+
let bytes = 0;
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
const fromPath = path.join(from, entry.name);
|
|
47
|
+
const toPath = path.join(to, entry.name);
|
|
48
|
+
if (entry.isDirectory()) {
|
|
49
|
+
const sub = await copyDir(fromPath, toPath);
|
|
50
|
+
files += sub.files;
|
|
51
|
+
bytes += sub.bytes;
|
|
52
|
+
} else {
|
|
53
|
+
await fsp.copyFile(fromPath, toPath);
|
|
54
|
+
const stat = await fsp.stat(toPath);
|
|
55
|
+
files += 1;
|
|
56
|
+
bytes += stat.size;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return { files, bytes };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function main() {
|
|
63
|
+
if (!(await pathExists(srcDir))) {
|
|
64
|
+
console.error(
|
|
65
|
+
`build-site: ${path.relative(root, srcDir)} not found. ` +
|
|
66
|
+
`Run "npm run build:tenants pagenary" first (prepack does this automatically).`
|
|
67
|
+
);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Deterministic: start from a clean site/.
|
|
72
|
+
await fsp.rm(destDir, { recursive: true, force: true });
|
|
73
|
+
|
|
74
|
+
const { files, bytes } = await copyDir(srcDir, destDir);
|
|
75
|
+
const mb = (bytes / (1024 * 1024)).toFixed(2);
|
|
76
|
+
console.log(`build-site: copied ${files} files (${mb} MB) -> ${path.relative(root, destDir)}/`);
|
|
77
|
+
|
|
78
|
+
if (!(await pathExists(path.join(destDir, 'index.html')))) {
|
|
79
|
+
console.error('build-site: site/index.html missing after copy — build output looks incomplete.');
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
main().catch((err) => {
|
|
85
|
+
console.error(`build-site: ${err.message}`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
});
|