@aravindc26/velu 0.13.7 → 0.13.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aravindc26/velu",
3
- "version": "0.13.7",
3
+ "version": "0.13.9",
4
4
  "description": "A modern documentation site generator powered by Markdown and JSON configuration",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,6 +1,7 @@
1
+ import type { Metadata } from 'next';
1
2
  import type { ReactNode } from 'react';
2
3
  import { loadSessionConfigSource, getSessionThemeCss } from '@/lib/preview-config';
3
- import { getBannerConfig, getFontsConfig, getSiteFavicon } from '@/lib/velu';
4
+ import { getBannerConfig, getFontsConfig, getSiteDescription, getSiteFavicon, getSiteName } from '@/lib/velu';
4
5
  import { VeluBanner } from '@/components/banner';
5
6
 
6
7
  interface LayoutProps {
@@ -8,6 +9,18 @@ interface LayoutProps {
8
9
  params: Promise<{ sessionId: string }>;
9
10
  }
10
11
 
12
+ export async function generateMetadata({ params }: { params: Promise<{ sessionId: string }> }): Promise<Metadata> {
13
+ const { sessionId } = await params;
14
+ const configSource = loadSessionConfigSource(sessionId);
15
+ if (!configSource) return {};
16
+ const title = getSiteName(configSource);
17
+ const description = getSiteDescription(configSource);
18
+ return {
19
+ ...(title ? { title: { default: title, template: `%s - ${title}` } } : {}),
20
+ ...(description ? { description } : {}),
21
+ };
22
+ }
23
+
11
24
  /**
12
25
  * Session layout: injects per-session theme CSS, Google Fonts, and banner.
13
26
  * Uses React 19 resource hoisting (<style precedence> / <link precedence>)
@@ -6,6 +6,7 @@ import { existsSync, readFileSync } from 'node:fs';
6
6
  import { join } from 'node:path';
7
7
  import { normalizeConfigNavigation } from './navigation-normalize';
8
8
  import type { VeluConfigSource, VeluConfig } from './velu';
9
+ import { getFontsConfig } from './velu';
9
10
 
10
11
  const WORKSPACE_DIR = process.env.WORKSPACE_DIR || '/mnt/nfs_share/editor_sessions';
11
12
  const PRIMARY_CONFIG_NAME = 'docs.json';
@@ -91,38 +92,80 @@ function textColorFor(hex: string): string {
91
92
  }
92
93
 
93
94
  /**
94
- * Generate CSS custom properties for a session's primary color theme.
95
+ * Generate CSS custom properties for a session's primary color theme and font overrides.
95
96
  */
96
97
  export function getSessionThemeCss(sessionId: string): string | null {
97
98
  const configSource = loadSessionConfigSource(sessionId);
98
- const colors = configSource?.config.colors;
99
- if (!colors?.primary) return null;
99
+ if (!configSource) return null;
100
100
 
101
- const { primary, light, dark } = colors;
102
- const lightAccent = light || primary;
103
- const darkAccent = dark || primary;
104
101
  const lines: string[] = [];
105
102
 
106
- if (lightAccent) {
107
- const accentLow = mixColors(lightAccent, '#ffffff', 0.15);
108
- lines.push(':root {');
109
- lines.push(` --color-fd-primary: ${lightAccent};`);
110
- lines.push(` --color-fd-primary-foreground: ${textColorFor(lightAccent)};`);
111
- lines.push(` --color-fd-accent: ${accentLow};`);
112
- lines.push(` --color-fd-accent-foreground: ${textColorFor(accentLow)};`);
113
- lines.push(` --color-fd-ring: ${lightAccent};`);
114
- lines.push('}');
103
+ // ── Color overrides ──
104
+ const colors = configSource.config.colors;
105
+ if (colors?.primary) {
106
+ const { primary, light, dark } = colors;
107
+ const lightAccent = light || primary;
108
+ const darkAccent = dark || primary;
109
+
110
+ if (lightAccent) {
111
+ const accentLow = mixColors(lightAccent, '#ffffff', 0.15);
112
+ lines.push(':root {');
113
+ lines.push(` --color-fd-primary: ${lightAccent};`);
114
+ lines.push(` --color-fd-primary-foreground: ${textColorFor(lightAccent)};`);
115
+ lines.push(` --color-fd-accent: ${accentLow};`);
116
+ lines.push(` --color-fd-accent-foreground: ${textColorFor(accentLow)};`);
117
+ lines.push(` --color-fd-ring: ${lightAccent};`);
118
+ lines.push('}');
119
+ }
120
+
121
+ if (darkAccent) {
122
+ const accentLow = mixColors(darkAccent, '#000000', 0.3);
123
+ lines.push('.dark {');
124
+ lines.push(` --color-fd-primary: ${darkAccent};`);
125
+ lines.push(` --color-fd-primary-foreground: ${textColorFor(darkAccent)};`);
126
+ lines.push(` --color-fd-accent: ${accentLow};`);
127
+ lines.push(` --color-fd-accent-foreground: ${textColorFor(accentLow)};`);
128
+ lines.push(` --color-fd-ring: ${darkAccent};`);
129
+ lines.push('}');
130
+ }
115
131
  }
116
132
 
117
- if (darkAccent) {
118
- const accentLow = mixColors(darkAccent, '#000000', 0.3);
119
- lines.push('.dark {');
120
- lines.push(` --color-fd-primary: ${darkAccent};`);
121
- lines.push(` --color-fd-primary-foreground: ${textColorFor(darkAccent)};`);
122
- lines.push(` --color-fd-accent: ${accentLow};`);
123
- lines.push(` --color-fd-accent-foreground: ${textColorFor(accentLow)};`);
124
- lines.push(` --color-fd-ring: ${darkAccent};`);
125
- lines.push('}');
133
+ // ── Font overrides ──
134
+ const fontsConfig = getFontsConfig(configSource);
135
+ if (fontsConfig) {
136
+ const { heading, body } = fontsConfig;
137
+ // @font-face for custom sources
138
+ for (const def of [heading, body]) {
139
+ if (def?.source) {
140
+ const fmt = def.format || (def.source.endsWith('.woff2') ? 'woff2' : 'woff');
141
+ lines.push(`@font-face {`);
142
+ lines.push(` font-family: '${def.family}';`);
143
+ lines.push(` src: url('${def.source}') format('${fmt}');`);
144
+ if (def.weight) lines.push(` font-weight: ${def.weight};`);
145
+ lines.push(` font-display: swap;`);
146
+ lines.push(`}`);
147
+ }
148
+ }
149
+ // CSS variable overrides
150
+ const vars: string[] = [];
151
+ if (body) {
152
+ vars.push(` --font-fd-sans: '${body.family}', ui-sans-serif, system-ui, sans-serif;`);
153
+ }
154
+ if (heading) {
155
+ vars.push(` --velu-font-heading: '${heading.family}', ui-sans-serif, system-ui, sans-serif;`);
156
+ }
157
+ if (vars.length) {
158
+ lines.push(':root {');
159
+ lines.push(...vars);
160
+ lines.push('}');
161
+ }
162
+ // Heading font-family rule
163
+ if (heading) {
164
+ lines.push('h1, h2, h3, h4, h5, h6 {');
165
+ lines.push(` font-family: var(--velu-font-heading);`);
166
+ if (heading.weight) lines.push(` font-weight: ${heading.weight};`);
167
+ lines.push('}');
168
+ }
126
169
  }
127
170
 
128
171
  return lines.length > 0 ? lines.join('\n') : null;
@@ -1,9 +1,10 @@
1
- import { NextRequest, NextResponse } from 'next/server';
1
+ import type { NextRequest } from 'next/server';
2
+ import { NextResponse } from 'next/server';
2
3
  import { getPreviewSecret, verifyPreviewToken } from './lib/preview-auth';
3
4
 
4
5
  /**
5
- * Next.js middleware that enforces HMAC-signed token authentication for
6
- * preview page routes.
6
+ * Next.js proxy (middleware) that enforces HMAC-signed token authentication
7
+ * for preview page routes.
7
8
  *
8
9
  * Flow:
9
10
  * 1. Requests with `x-preview-secret` header pass through (server-to-server API calls).
@@ -13,7 +14,7 @@ import { getPreviewSecret, verifyPreviewToken } from './lib/preview-auth';
13
14
  * 4. If a valid `__velu_preview_{sessionId}` cookie exists: allow.
14
15
  * 5. Otherwise: 403.
15
16
  */
16
- export function middleware(request: NextRequest) {
17
+ export function proxy(request: NextRequest) {
17
18
  const secret = getPreviewSecret();
18
19
 
19
20
  // No secret configured — allow everything (dev / local)
@@ -76,16 +77,8 @@ export function middleware(request: NextRequest) {
76
77
  return new NextResponse('Forbidden', { status: 403 });
77
78
  }
78
79
 
79
- // Use Node.js runtime so we can access the crypto module for HMAC verification
80
- export const runtime = 'nodejs';
81
-
82
80
  export const config = {
83
81
  matcher: [
84
- /*
85
- * Match all request paths except:
86
- * - _next (Next.js internals)
87
- * - favicon.ico, robots.txt, sitemap.xml (static meta)
88
- */
89
82
  '/((?!_next|favicon\\.ico|robots\\.txt|sitemap\\.xml).*)',
90
83
  ],
91
84
  };