@knowcode/doc-builder 1.5.4 → 1.5.5

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/CHANGELOG.md CHANGED
@@ -5,6 +5,39 @@ All notable changes to @knowcode/doc-builder will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.5.5] - 2025-07-22
9
+
10
+ ### Added
11
+ - **Front Matter Support**: Parse YAML front matter for page-specific SEO customization
12
+ - **SEO Check Command**: New `seo-check` command to analyze and report SEO issues
13
+ - **Smart Description Extraction**: Intelligent extraction of intro paragraphs for better descriptions
14
+ - **Enhanced Title Generation**: SEO-optimized titles with customizable templates
15
+ - **Page-specific Keywords**: Support for front matter keywords plus auto-extraction
16
+ - **Comprehensive SEO Guide**: Detailed documentation for SEO optimization
17
+
18
+ ### Changed
19
+ - Improved `generateDescription` with smart mode for better meta descriptions
20
+ - Title generation now respects 50-60 character limit with smart truncation
21
+ - Keywords can be defined per-page via front matter
22
+ - SEO configuration expanded with new options
23
+
24
+ ### Features
25
+ - **Front Matter SEO**: Add custom title, description, and keywords per page
26
+ - **Title Templates**: Configure title format with `{pageTitle} | {siteName}` patterns
27
+ - **Auto Keywords**: Automatically extract relevant keywords from content
28
+ - **SEO Analysis**: Run `doc-builder seo-check` to find and fix SEO issues
29
+ - **Smart Defaults**: Intelligent fallbacks for missing SEO data
30
+
31
+ ### Configuration
32
+ ```javascript
33
+ seo: {
34
+ titleTemplate: '{pageTitle} | {siteName}',
35
+ autoKeywords: true,
36
+ keywordLimit: 7,
37
+ descriptionFallback: 'smart'
38
+ }
39
+ ```
40
+
8
41
  ## [1.5.4] - 2025-07-22
9
42
 
10
43
  ### Added
package/CLAUDE.md CHANGED
@@ -312,6 +312,9 @@ build(config).then(() => {
312
312
 
313
313
  MIT License - See LICENSE file for details
314
314
 
315
+ ### NPM Features Documentation
316
+ - When we add new features - add it to the NPM documented bullet point features - if its a big feature give it a section
317
+
315
318
  ---
316
319
 
317
320
  **Note**: This project was extracted from the cybersolstice monorepo on 2025-07-21 with full git history preservation.
package/README.md CHANGED
@@ -49,6 +49,9 @@ This aligns perfectly with our mission: beautiful documentation should be access
49
49
  - 🔄 **Live Reload** - Development server with hot reloading
50
50
  - ☁️ **Vercel Integration** - One-command deployment to Vercel
51
51
  - 🔍 **SEO Optimized** - Meta tags, Open Graph, Twitter Cards, and structured data
52
+ - ✅ **Google Site Verification** - Easy Google Search Console verification with CLI command
53
+ - 📝 **Front Matter SEO** - Per-page titles, descriptions, and keywords with YAML front matter
54
+ - 🎯 **SEO Analysis** - Built-in `seo-check` command to optimize your content
52
55
  - 📦 **Self-Contained** - No configuration or setup required
53
56
  - 🤖 **Claude Code Ready** - Optimized for AI-generated documentation workflows
54
57
 
@@ -147,6 +150,44 @@ npx @knowcode/doc-builder set-production-url https://my-custom-domain.com
147
150
 
148
151
  This is useful when you have a custom domain or Vercel alias that differs from the auto-detected URL.
149
152
 
153
+ ### google-verify
154
+
155
+ Add Google site verification meta tag for Google Search Console:
156
+
157
+ ```bash
158
+ # Add your verification code
159
+ npx @knowcode/doc-builder google-verify YOUR_VERIFICATION_CODE
160
+
161
+ # Example
162
+ npx @knowcode/doc-builder google-verify FtzcDTf5BQ9K5EfnGazQkgU2U4FiN3ITzM7gHwqUAqQ
163
+ ```
164
+
165
+ This adds the verification meta tag to all generated HTML pages, allowing you to verify ownership in Google Search Console. See the [Google Site Verification Guide](docs/guides/google-site-verification-guide.md) for complete details.
166
+
167
+ ### seo-check
168
+
169
+ Analyze and optimize your documentation's SEO:
170
+
171
+ ```bash
172
+ # Check all pages for SEO issues
173
+ npx @knowcode/doc-builder seo-check
174
+
175
+ # Check a specific page
176
+ npx @knowcode/doc-builder seo-check docs/guide.md
177
+
178
+ # Future: Auto-fix common issues
179
+ npx @knowcode/doc-builder seo-check --fix
180
+ ```
181
+
182
+ This command analyzes:
183
+ - Title length and optimization (50-60 characters)
184
+ - Meta descriptions (140-160 characters)
185
+ - Keywords usage and relevance
186
+ - Front matter SEO fields
187
+ - Content quality signals
188
+
189
+ See the [SEO Optimization Guide](docs/guides/seo-optimization-guide.md) for best practices.
190
+
150
191
  ### setup-seo
151
192
  Interactive SEO configuration wizard:
152
193
  ```bash
package/cli.js CHANGED
@@ -10,6 +10,7 @@ const { build } = require('./lib/builder');
10
10
  const { startDevServer } = require('./lib/dev-server');
11
11
  const { deployToVercel, setupVercelProject, prepareDeployment } = require('./lib/deploy');
12
12
  const { loadConfig, createDefaultConfig } = require('./lib/config');
13
+ const { generateDescription } = require('./lib/seo');
13
14
  const { execSync } = require('child_process');
14
15
 
15
16
  // Package info
@@ -191,6 +192,177 @@ ${chalk.yellow('Get your verification code from Google Search Console.')}
191
192
  }
192
193
  });
193
194
 
195
+ // SEO Check command
196
+ program
197
+ .command('seo-check [path]')
198
+ .description('Analyze SEO for your documentation pages')
199
+ .option('-c, --config <path>', 'path to config file (default: doc-builder.config.js)')
200
+ .option('--fix', 'auto-fix common SEO issues')
201
+ .addHelpText('after', `
202
+ ${chalk.yellow('Examples:')}
203
+ ${chalk.gray('$')} doc-builder seo-check ${chalk.gray('# Check all pages')}
204
+ ${chalk.gray('$')} doc-builder seo-check docs/guide.md ${chalk.gray('# Check specific page')}
205
+ ${chalk.gray('$')} doc-builder seo-check --fix ${chalk.gray('# Auto-fix issues')}
206
+
207
+ ${chalk.yellow('This command analyzes:')}
208
+ • Title length and optimization (50-60 characters)
209
+ • Meta descriptions (140-160 characters)
210
+ • Keywords usage and relevance
211
+ • Front matter SEO fields
212
+ • Duplicate content issues
213
+ `)
214
+ .action(async (filePath, options) => {
215
+ try {
216
+ const config = await loadConfig(options.config);
217
+ const docsDir = path.resolve(config.docsDir || 'docs');
218
+
219
+ console.log(chalk.cyan('🔍 Analyzing SEO...'));
220
+
221
+ // Get files to check
222
+ let files = [];
223
+ if (filePath) {
224
+ const fullPath = path.resolve(filePath);
225
+ if (await fs.pathExists(fullPath)) {
226
+ files = [fullPath];
227
+ } else {
228
+ console.error(chalk.red(`File not found: ${filePath}`));
229
+ process.exit(1);
230
+ }
231
+ } else {
232
+ // Get all markdown files
233
+ const getAllFiles = async (dir) => {
234
+ const results = [];
235
+ const items = await fs.readdir(dir);
236
+ for (const item of items) {
237
+ const fullPath = path.join(dir, item);
238
+ const stat = await fs.stat(fullPath);
239
+ if (stat.isDirectory() && !item.startsWith('.')) {
240
+ results.push(...await getAllFiles(fullPath));
241
+ } else if (item.endsWith('.md')) {
242
+ results.push(fullPath);
243
+ }
244
+ }
245
+ return results;
246
+ };
247
+ files = await getAllFiles(docsDir);
248
+ }
249
+
250
+ // Analyze each file
251
+ const issues = [];
252
+ const suggestions = [];
253
+
254
+ for (const file of files) {
255
+ const content = await fs.readFile(file, 'utf-8');
256
+ const { data: frontMatter, content: mainContent } = matter(content);
257
+ const relativePath = path.relative(docsDir, file);
258
+
259
+ // Extract current metadata
260
+ const h1Match = mainContent.match(/^#\s+(.+)$/m);
261
+ const h1Title = h1Match ? h1Match[1] : null;
262
+ const title = frontMatter.title || h1Title || path.basename(file, '.md');
263
+ const description = frontMatter.description || generateDescription(mainContent, 'smart');
264
+ const keywords = frontMatter.keywords || [];
265
+
266
+ // Check title
267
+ const titleLength = title.length;
268
+ if (titleLength > 60) {
269
+ issues.push({
270
+ file: relativePath,
271
+ type: 'title',
272
+ message: `Title too long (${titleLength} chars, max 60)`,
273
+ current: title,
274
+ suggestion: title.substring(0, 57) + '...'
275
+ });
276
+ } else if (titleLength < 30) {
277
+ suggestions.push({
278
+ file: relativePath,
279
+ type: 'title',
280
+ message: `Title might be too short (${titleLength} chars, ideal 50-60)`
281
+ });
282
+ }
283
+
284
+ // Check description
285
+ const descLength = description.length;
286
+ if (descLength > 160) {
287
+ issues.push({
288
+ file: relativePath,
289
+ type: 'description',
290
+ message: `Description too long (${descLength} chars, max 160)`,
291
+ current: description,
292
+ suggestion: description.substring(0, 157) + '...'
293
+ });
294
+ } else if (descLength < 120) {
295
+ suggestions.push({
296
+ file: relativePath,
297
+ type: 'description',
298
+ message: `Description might be too short (${descLength} chars, ideal 140-160)`
299
+ });
300
+ }
301
+
302
+ // Check keywords
303
+ if (keywords.length === 0 && !frontMatter.description) {
304
+ suggestions.push({
305
+ file: relativePath,
306
+ type: 'keywords',
307
+ message: 'No keywords defined in front matter'
308
+ });
309
+ }
310
+
311
+ // Check for front matter
312
+ if (!frontMatter.title && !frontMatter.description) {
313
+ suggestions.push({
314
+ file: relativePath,
315
+ type: 'frontmatter',
316
+ message: 'No SEO front matter found'
317
+ });
318
+ }
319
+ }
320
+
321
+ // Display results
322
+ console.log(`\n${chalk.cyan('📊 SEO Analysis Complete')}\n`);
323
+ console.log(`Analyzed ${files.length} files\n`);
324
+
325
+ if (issues.length === 0 && suggestions.length === 0) {
326
+ console.log(chalk.green('✅ No SEO issues found!'));
327
+ } else {
328
+ if (issues.length > 0) {
329
+ console.log(chalk.red(`❌ Found ${issues.length} issues:\n`));
330
+ issues.forEach(issue => {
331
+ console.log(chalk.red(` ${issue.file}:`));
332
+ console.log(chalk.yellow(` ${issue.message}`));
333
+ if (issue.suggestion) {
334
+ console.log(chalk.gray(` Suggestion: ${issue.suggestion.substring(0, 50)}...`));
335
+ }
336
+ console.log('');
337
+ });
338
+ }
339
+
340
+ if (suggestions.length > 0) {
341
+ console.log(chalk.yellow(`💡 ${suggestions.length} suggestions:\n`));
342
+ suggestions.forEach(suggestion => {
343
+ console.log(chalk.yellow(` ${suggestion.file}: ${suggestion.message}`));
344
+ });
345
+ }
346
+
347
+ console.log(`\n${chalk.cyan('💡 Tips:')}`);
348
+ console.log(' • Add front matter to customize SEO per page');
349
+ console.log(' • Keep titles between 50-60 characters');
350
+ console.log(' • Write descriptions between 140-160 characters');
351
+ console.log(' • Include relevant keywords in front matter');
352
+ console.log(`\n${chalk.gray('Example front matter:')}`);
353
+ console.log(chalk.gray('---'));
354
+ console.log(chalk.gray('title: "Your SEO Optimized Title Here"'));
355
+ console.log(chalk.gray('description: "A compelling description between 140-160 characters that includes keywords and encourages clicks."'));
356
+ console.log(chalk.gray('keywords: ["keyword1", "keyword2", "keyword3"]'));
357
+ console.log(chalk.gray('---'));
358
+ }
359
+
360
+ } catch (error) {
361
+ console.error(chalk.red('Failed to analyze SEO:'), error.message);
362
+ process.exit(1);
363
+ }
364
+ });
365
+
194
366
  // Set Production URL command
195
367
  program
196
368
  .command('set-production-url <url>')
package/html/README.html CHANGED
@@ -61,8 +61,8 @@
61
61
  "name": "Knowcode Ltd",
62
62
  "url": "https://knowcode.tech"
63
63
  },
64
- "datePublished": "2025-07-22T06:56:56.764Z",
65
- "dateModified": "2025-07-22T06:56:56.764Z",
64
+ "datePublished": "2025-07-22T07:02:21.490Z",
65
+ "dateModified": "2025-07-22T07:02:21.490Z",
66
66
  "mainEntityOfPage": {
67
67
  "@type": "WebPage",
68
68
  "@id": "https://doc-builder-delta.vercel.app/README.html"
@@ -95,7 +95,7 @@
95
95
 
96
96
  <div class="header-actions">
97
97
  <div class="deployment-info">
98
- <span class="deployment-date" title="Built with doc-builder v1.5.3">Last updated: Jul 22, 2025, 06:56 AM UTC</span>
98
+ <span class="deployment-date" title="Built with doc-builder v1.5.4">Last updated: Jul 22, 2025, 07:02 AM UTC</span>
99
99
  </div>
100
100
 
101
101
 
@@ -61,8 +61,8 @@
61
61
  "name": "Knowcode Ltd",
62
62
  "url": "https://knowcode.tech"
63
63
  },
64
- "datePublished": "2025-07-22T06:56:56.775Z",
65
- "dateModified": "2025-07-22T06:56:56.775Z",
64
+ "datePublished": "2025-07-22T07:02:21.500Z",
65
+ "dateModified": "2025-07-22T07:02:21.500Z",
66
66
  "mainEntityOfPage": {
67
67
  "@type": "WebPage",
68
68
  "@id": "https://doc-builder-delta.vercel.app/documentation-index.html"
@@ -95,7 +95,7 @@
95
95
 
96
96
  <div class="header-actions">
97
97
  <div class="deployment-info">
98
- <span class="deployment-date" title="Built with doc-builder v1.5.3">Last updated: Jul 22, 2025, 06:56 AM UTC</span>
98
+ <span class="deployment-date" title="Built with doc-builder v1.5.4">Last updated: Jul 22, 2025, 07:02 AM UTC</span>
99
99
  </div>
100
100
 
101
101
 
@@ -61,8 +61,8 @@
61
61
  "name": "Knowcode Ltd",
62
62
  "url": "https://knowcode.tech"
63
63
  },
64
- "datePublished": "2025-07-22T06:56:56.778Z",
65
- "dateModified": "2025-07-22T06:56:56.778Z",
64
+ "datePublished": "2025-07-22T07:02:21.503Z",
65
+ "dateModified": "2025-07-22T07:02:21.503Z",
66
66
  "mainEntityOfPage": {
67
67
  "@type": "WebPage",
68
68
  "@id": "https://doc-builder-delta.vercel.app/guides/authentication-guide.html"
@@ -101,7 +101,7 @@
101
101
 
102
102
  <div class="header-actions">
103
103
  <div class="deployment-info">
104
- <span class="deployment-date" title="Built with doc-builder v1.5.3">Last updated: Jul 22, 2025, 06:56 AM UTC</span>
104
+ <span class="deployment-date" title="Built with doc-builder v1.5.4">Last updated: Jul 22, 2025, 07:02 AM UTC</span>
105
105
  </div>
106
106
 
107
107
 
@@ -61,8 +61,8 @@
61
61
  "name": "Knowcode Ltd",
62
62
  "url": "https://knowcode.tech"
63
63
  },
64
- "datePublished": "2025-07-22T06:56:56.781Z",
65
- "dateModified": "2025-07-22T06:56:56.781Z",
64
+ "datePublished": "2025-07-22T07:02:21.506Z",
65
+ "dateModified": "2025-07-22T07:02:21.506Z",
66
66
  "mainEntityOfPage": {
67
67
  "@type": "WebPage",
68
68
  "@id": "https://doc-builder-delta.vercel.app/guides/claude-workflow-guide.html"
@@ -101,7 +101,7 @@
101
101
 
102
102
  <div class="header-actions">
103
103
  <div class="deployment-info">
104
- <span class="deployment-date" title="Built with doc-builder v1.5.3">Last updated: Jul 22, 2025, 06:56 AM UTC</span>
104
+ <span class="deployment-date" title="Built with doc-builder v1.5.4">Last updated: Jul 22, 2025, 07:02 AM UTC</span>
105
105
  </div>
106
106
 
107
107
 
@@ -61,8 +61,8 @@
61
61
  "name": "Knowcode Ltd",
62
62
  "url": "https://knowcode.tech"
63
63
  },
64
- "datePublished": "2025-07-22T06:56:56.784Z",
65
- "dateModified": "2025-07-22T06:56:56.784Z",
64
+ "datePublished": "2025-07-22T07:02:21.509Z",
65
+ "dateModified": "2025-07-22T07:02:21.509Z",
66
66
  "mainEntityOfPage": {
67
67
  "@type": "WebPage",
68
68
  "@id": "https://doc-builder-delta.vercel.app/guides/documentation-standards.html"
@@ -101,7 +101,7 @@
101
101
 
102
102
  <div class="header-actions">
103
103
  <div class="deployment-info">
104
- <span class="deployment-date" title="Built with doc-builder v1.5.3">Last updated: Jul 22, 2025, 06:56 AM UTC</span>
104
+ <span class="deployment-date" title="Built with doc-builder v1.5.4">Last updated: Jul 22, 2025, 07:02 AM UTC</span>
105
105
  </div>
106
106
 
107
107
 
@@ -61,8 +61,8 @@
61
61
  "name": "Knowcode Ltd",
62
62
  "url": "https://knowcode.tech"
63
63
  },
64
- "datePublished": "2025-07-22T06:56:56.790Z",
65
- "dateModified": "2025-07-22T06:56:56.790Z",
64
+ "datePublished": "2025-07-22T07:02:21.516Z",
65
+ "dateModified": "2025-07-22T07:02:21.516Z",
66
66
  "mainEntityOfPage": {
67
67
  "@type": "WebPage",
68
68
  "@id": "https://doc-builder-delta.vercel.app/guides/google-site-verification-guide.html"
@@ -101,7 +101,7 @@
101
101
 
102
102
  <div class="header-actions">
103
103
  <div class="deployment-info">
104
- <span class="deployment-date" title="Built with doc-builder v1.5.3">Last updated: Jul 22, 2025, 06:56 AM UTC</span>
104
+ <span class="deployment-date" title="Built with doc-builder v1.5.4">Last updated: Jul 22, 2025, 07:02 AM UTC</span>
105
105
  </div>
106
106
 
107
107
 
@@ -61,8 +61,8 @@
61
61
  "name": "Knowcode Ltd",
62
62
  "url": "https://knowcode.tech"
63
63
  },
64
- "datePublished": "2025-07-22T06:56:56.793Z",
65
- "dateModified": "2025-07-22T06:56:56.793Z",
64
+ "datePublished": "2025-07-22T07:02:21.519Z",
65
+ "dateModified": "2025-07-22T07:02:21.519Z",
66
66
  "mainEntityOfPage": {
67
67
  "@type": "WebPage",
68
68
  "@id": "https://doc-builder-delta.vercel.app/guides/seo-guide.html"
@@ -101,7 +101,7 @@
101
101
 
102
102
  <div class="header-actions">
103
103
  <div class="deployment-info">
104
- <span class="deployment-date" title="Built with doc-builder v1.5.3">Last updated: Jul 22, 2025, 06:56 AM UTC</span>
104
+ <span class="deployment-date" title="Built with doc-builder v1.5.4">Last updated: Jul 22, 2025, 07:02 AM UTC</span>
105
105
  </div>
106
106
 
107
107
 
@@ -61,8 +61,8 @@
61
61
  "name": "Knowcode Ltd",
62
62
  "url": "https://knowcode.tech"
63
63
  },
64
- "datePublished": "2025-07-22T06:56:56.796Z",
65
- "dateModified": "2025-07-22T06:56:56.796Z",
64
+ "datePublished": "2025-07-22T07:02:21.522Z",
65
+ "dateModified": "2025-07-22T07:02:21.522Z",
66
66
  "mainEntityOfPage": {
67
67
  "@type": "WebPage",
68
68
  "@id": "https://doc-builder-delta.vercel.app/guides/troubleshooting-guide.html"
@@ -101,7 +101,7 @@
101
101
 
102
102
  <div class="header-actions">
103
103
  <div class="deployment-info">
104
- <span class="deployment-date" title="Built with doc-builder v1.5.3">Last updated: Jul 22, 2025, 06:56 AM UTC</span>
104
+ <span class="deployment-date" title="Built with doc-builder v1.5.4">Last updated: Jul 22, 2025, 07:02 AM UTC</span>
105
105
  </div>
106
106
 
107
107
 
package/html/index.html CHANGED
@@ -61,8 +61,8 @@
61
61
  "name": "Knowcode Ltd",
62
62
  "url": "https://knowcode.tech"
63
63
  },
64
- "datePublished": "2025-07-22T06:56:56.764Z",
65
- "dateModified": "2025-07-22T06:56:56.764Z",
64
+ "datePublished": "2025-07-22T07:02:21.490Z",
65
+ "dateModified": "2025-07-22T07:02:21.490Z",
66
66
  "mainEntityOfPage": {
67
67
  "@type": "WebPage",
68
68
  "@id": "https://doc-builder-delta.vercel.app/README.html"
@@ -95,7 +95,7 @@
95
95
 
96
96
  <div class="header-actions">
97
97
  <div class="deployment-info">
98
- <span class="deployment-date" title="Built with doc-builder v1.5.3">Last updated: Jul 22, 2025, 06:56 AM UTC</span>
98
+ <span class="deployment-date" title="Built with doc-builder v1.5.4">Last updated: Jul 22, 2025, 07:02 AM UTC</span>
99
99
  </div>
100
100
 
101
101
 
package/html/sitemap.xml CHANGED
@@ -2,85 +2,85 @@
2
2
  <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
3
3
  <url>
4
4
  <loc>https://doc-builder-delta.vercel.app/404.html</loc>
5
- <lastmod>2025-07-22T06:56:56.811Z</lastmod>
5
+ <lastmod>2025-07-22T07:02:21.540Z</lastmod>
6
6
  <changefreq>monthly</changefreq>
7
7
  <priority>0.6</priority>
8
8
  </url>
9
9
  <url>
10
10
  <loc>https://doc-builder-delta.vercel.app/README.html</loc>
11
- <lastmod>2025-07-22T06:56:56.811Z</lastmod>
11
+ <lastmod>2025-07-22T07:02:21.540Z</lastmod>
12
12
  <changefreq>monthly</changefreq>
13
13
  <priority>0.6</priority>
14
14
  </url>
15
15
  <url>
16
16
  <loc>https://doc-builder-delta.vercel.app/claude-workflow-guide.html</loc>
17
- <lastmod>2025-07-22T06:56:56.811Z</lastmod>
17
+ <lastmod>2025-07-22T07:02:21.540Z</lastmod>
18
18
  <changefreq>monthly</changefreq>
19
19
  <priority>0.8</priority>
20
20
  </url>
21
21
  <url>
22
22
  <loc>https://doc-builder-delta.vercel.app/documentation-index.html</loc>
23
- <lastmod>2025-07-22T06:56:56.811Z</lastmod>
23
+ <lastmod>2025-07-22T07:02:21.540Z</lastmod>
24
24
  <changefreq>monthly</changefreq>
25
25
  <priority>0.6</priority>
26
26
  </url>
27
27
  <url>
28
28
  <loc>https://doc-builder-delta.vercel.app/guides/authentication-guide.html</loc>
29
- <lastmod>2025-07-22T06:56:56.811Z</lastmod>
29
+ <lastmod>2025-07-22T07:02:21.540Z</lastmod>
30
30
  <changefreq>monthly</changefreq>
31
31
  <priority>0.8</priority>
32
32
  </url>
33
33
  <url>
34
34
  <loc>https://doc-builder-delta.vercel.app/guides/claude-workflow-guide.html</loc>
35
- <lastmod>2025-07-22T06:56:56.811Z</lastmod>
35
+ <lastmod>2025-07-22T07:02:21.540Z</lastmod>
36
36
  <changefreq>monthly</changefreq>
37
37
  <priority>0.8</priority>
38
38
  </url>
39
39
  <url>
40
40
  <loc>https://doc-builder-delta.vercel.app/guides/document-standards.html</loc>
41
- <lastmod>2025-07-22T06:56:56.811Z</lastmod>
41
+ <lastmod>2025-07-22T07:02:21.540Z</lastmod>
42
42
  <changefreq>monthly</changefreq>
43
43
  <priority>0.8</priority>
44
44
  </url>
45
45
  <url>
46
46
  <loc>https://doc-builder-delta.vercel.app/guides/documentation-standards.html</loc>
47
- <lastmod>2025-07-22T06:56:56.811Z</lastmod>
47
+ <lastmod>2025-07-22T07:02:21.540Z</lastmod>
48
48
  <changefreq>monthly</changefreq>
49
49
  <priority>0.8</priority>
50
50
  </url>
51
51
  <url>
52
52
  <loc>https://doc-builder-delta.vercel.app/guides/google-site-verification-guide.html</loc>
53
- <lastmod>2025-07-22T06:56:56.811Z</lastmod>
53
+ <lastmod>2025-07-22T07:02:21.540Z</lastmod>
54
54
  <changefreq>monthly</changefreq>
55
55
  <priority>0.8</priority>
56
56
  </url>
57
57
  <url>
58
58
  <loc>https://doc-builder-delta.vercel.app/guides/seo-guide.html</loc>
59
- <lastmod>2025-07-22T06:56:56.811Z</lastmod>
59
+ <lastmod>2025-07-22T07:02:21.540Z</lastmod>
60
60
  <changefreq>monthly</changefreq>
61
61
  <priority>0.8</priority>
62
62
  </url>
63
63
  <url>
64
64
  <loc>https://doc-builder-delta.vercel.app/guides/troubleshooting-guide.html</loc>
65
- <lastmod>2025-07-22T06:56:56.811Z</lastmod>
65
+ <lastmod>2025-07-22T07:02:21.540Z</lastmod>
66
66
  <changefreq>monthly</changefreq>
67
67
  <priority>0.8</priority>
68
68
  </url>
69
69
  <url>
70
70
  <loc>https://doc-builder-delta.vercel.app/index.html</loc>
71
- <lastmod>2025-07-22T06:56:56.811Z</lastmod>
71
+ <lastmod>2025-07-22T07:02:21.540Z</lastmod>
72
72
  <changefreq>weekly</changefreq>
73
73
  <priority>1.0</priority>
74
74
  </url>
75
75
  <url>
76
76
  <loc>https://doc-builder-delta.vercel.app/vercel-cli-setup-guide.html</loc>
77
- <lastmod>2025-07-22T06:56:56.811Z</lastmod>
77
+ <lastmod>2025-07-22T07:02:21.540Z</lastmod>
78
78
  <changefreq>monthly</changefreq>
79
79
  <priority>0.8</priority>
80
80
  </url>
81
81
  <url>
82
82
  <loc>https://doc-builder-delta.vercel.app/vercel-first-time-setup-guide.html</loc>
83
- <lastmod>2025-07-22T06:56:56.811Z</lastmod>
83
+ <lastmod>2025-07-22T07:02:21.540Z</lastmod>
84
84
  <changefreq>monthly</changefreq>
85
85
  <priority>0.8</priority>
86
86
  </url>
@@ -61,8 +61,8 @@
61
61
  "name": "Knowcode Ltd",
62
62
  "url": "https://knowcode.tech"
63
63
  },
64
- "datePublished": "2025-07-22T06:56:56.800Z",
65
- "dateModified": "2025-07-22T06:56:56.800Z",
64
+ "datePublished": "2025-07-22T07:02:21.526Z",
65
+ "dateModified": "2025-07-22T07:02:21.526Z",
66
66
  "mainEntityOfPage": {
67
67
  "@type": "WebPage",
68
68
  "@id": "https://doc-builder-delta.vercel.app/vercel-cli-setup-guide.html"
@@ -95,7 +95,7 @@
95
95
 
96
96
  <div class="header-actions">
97
97
  <div class="deployment-info">
98
- <span class="deployment-date" title="Built with doc-builder v1.5.3">Last updated: Jul 22, 2025, 06:56 AM UTC</span>
98
+ <span class="deployment-date" title="Built with doc-builder v1.5.4">Last updated: Jul 22, 2025, 07:02 AM UTC</span>
99
99
  </div>
100
100
 
101
101
 
@@ -61,8 +61,8 @@
61
61
  "name": "Knowcode Ltd",
62
62
  "url": "https://knowcode.tech"
63
63
  },
64
- "datePublished": "2025-07-22T06:56:56.803Z",
65
- "dateModified": "2025-07-22T06:56:56.803Z",
64
+ "datePublished": "2025-07-22T07:02:21.530Z",
65
+ "dateModified": "2025-07-22T07:02:21.530Z",
66
66
  "mainEntityOfPage": {
67
67
  "@type": "WebPage",
68
68
  "@id": "https://doc-builder-delta.vercel.app/vercel-first-time-setup-guide.html"
@@ -95,7 +95,7 @@
95
95
 
96
96
  <div class="header-actions">
97
97
  <div class="deployment-info">
98
- <span class="deployment-date" title="Built with doc-builder v1.5.3">Last updated: Jul 22, 2025, 06:56 AM UTC</span>
98
+ <span class="deployment-date" title="Built with doc-builder v1.5.4">Last updated: Jul 22, 2025, 07:02 AM UTC</span>
99
99
  </div>
100
100
 
101
101
 
package/lib/config.js CHANGED
@@ -55,6 +55,10 @@ const defaultConfig = {
55
55
  twitterHandle: '',
56
56
  language: 'en-US',
57
57
  keywords: [],
58
+ titleTemplate: '{pageTitle} | {siteName}', // Customizable title format
59
+ autoKeywords: true, // Extract keywords from content
60
+ keywordLimit: 7, // Max keywords per page
61
+ descriptionFallback: 'smart', // 'smart' or 'first-paragraph'
58
62
  organization: {
59
63
  name: '',
60
64
  url: '',
@@ -2,6 +2,7 @@ const fs = require('fs-extra');
2
2
  const path = require('path');
3
3
  const marked = require('marked');
4
4
  const chalk = require('chalk');
5
+ const matter = require('gray-matter');
5
6
  const {
6
7
  generateMetaTags,
7
8
  generateJSONLD,
@@ -189,7 +190,7 @@ function processMarkdownContent(content) {
189
190
  }
190
191
 
191
192
  // Generate HTML from template
192
- function generateHTML(title, content, navigation, currentPath = '', config = {}, originalContent = '') {
193
+ function generateHTML(title, content, navigation, currentPath = '', config = {}, originalContent = '', frontMatter = {}) {
193
194
  const depth = currentPath.split('/').filter(p => p).length;
194
195
  const relativePath = depth > 0 ? '../'.repeat(depth) : '';
195
196
 
@@ -203,24 +204,40 @@ function generateHTML(title, content, navigation, currentPath = '', config = {},
203
204
  // SEO preparation
204
205
  let seoTags = '';
205
206
  let jsonLd = '';
207
+ let finalSeoTitle = `${title} - ${siteName}`;
208
+ let pageDescription = frontMatter.description ||
209
+ generateDescription(originalContent || content, config.seo?.descriptionFallback) ||
210
+ siteDescription;
206
211
 
207
212
  if (config.seo?.enabled && config.seo?.siteUrl) {
208
213
  // Generate page URL
209
214
  const pageUrl = `${config.seo.siteUrl}/${currentPath}`;
210
215
 
211
- // Extract or generate description
212
- const pageDescription = generateDescription(originalContent || content) || siteDescription;
213
-
214
- // Extract keywords from content
215
- const contentKeywords = extractKeywords(originalContent || content, 5);
216
- const keywords = [...new Set([...(config.seo.keywords || []), ...contentKeywords])];
216
+ // Extract keywords - priority: front matter > content extraction + global
217
+ const contentKeywords = config.seo?.autoKeywords !== false ?
218
+ extractKeywords(originalContent || content, config.seo?.keywordLimit || 7) :
219
+ [];
220
+ const pageKeywords = frontMatter.keywords || [];
221
+ const keywords = [...new Set([...(config.seo.keywords || []), ...pageKeywords, ...contentKeywords])]
222
+ .slice(0, config.seo?.keywordLimit || 7);
217
223
 
218
224
  // Generate breadcrumbs
219
225
  const breadcrumbs = generateBreadcrumbs(currentPath, config.seo.siteUrl, siteName);
220
226
 
227
+ // Generate SEO-optimized title
228
+ const titleTemplate = config.seo?.titleTemplate || '{pageTitle} | {siteName}';
229
+ const seoTitle = titleTemplate
230
+ .replace('{pageTitle}', title)
231
+ .replace('{siteName}', siteName);
232
+
233
+ // Ensure title is within 50-60 characters
234
+ finalSeoTitle = seoTitle.length > 60 ?
235
+ title.length > 50 ? title.substring(0, 50) + '...' : title
236
+ : seoTitle;
237
+
221
238
  // Generate meta tags
222
239
  seoTags = generateMetaTags({
223
- title: `${title} - ${siteName}`,
240
+ title: finalSeoTitle,
224
241
  description: pageDescription,
225
242
  url: pageUrl,
226
243
  author: config.seo.author,
@@ -254,8 +271,8 @@ function generateHTML(title, content, navigation, currentPath = '', config = {},
254
271
  <head>
255
272
  <meta charset="UTF-8">
256
273
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
257
- <meta name="description" content="${escapeHtml(generateDescription(originalContent || content) || siteDescription)}">
258
- <title>${escapeHtml(title)} - ${escapeHtml(siteName)}</title>
274
+ <meta name="description" content="${escapeHtml(pageDescription || generateDescription(originalContent || content) || siteDescription)}">
275
+ <title>${escapeHtml(finalSeoTitle || `${title} - ${siteName}`)}</title>
259
276
 
260
277
  ${seoTags}
261
278
 
@@ -578,17 +595,21 @@ function buildNavigationStructure(files, currentFile) {
578
595
 
579
596
  // Process single markdown file
580
597
  async function processMarkdownFile(filePath, outputPath, allFiles, config) {
581
- const content = await fs.readFile(filePath, 'utf-8');
598
+ const rawContent = await fs.readFile(filePath, 'utf-8');
582
599
  const fileName = path.basename(filePath, '.md');
583
600
  const relativePath = path.relative(config.docsDir, filePath);
584
601
  const urlPath = relativePath.replace(/\.md$/, '.html').replace(/\\/g, '/');
585
602
 
586
- // Extract title from content
587
- const titleMatch = content.match(/^#\s+(.+)$/m);
588
- const title = titleMatch ? titleMatch[1] : fileName;
603
+ // Parse front matter
604
+ const { data: frontMatter, content } = matter(rawContent);
605
+
606
+ // Extract title - priority: front matter > H1 > filename
607
+ const h1Match = content.match(/^#\s+(.+)$/m);
608
+ const h1Title = h1Match ? h1Match[1] : null;
609
+ const title = frontMatter.title || h1Title || fileName;
589
610
 
590
- // Extract summary for tooltip
591
- const summary = extractSummary(content);
611
+ // Extract summary for tooltip - priority: front matter > auto-extract
612
+ const summary = frontMatter.description || extractSummary(content);
592
613
 
593
614
  // Process content
594
615
  const htmlContent = processMarkdownContent(content);
@@ -596,14 +617,14 @@ async function processMarkdownFile(filePath, outputPath, allFiles, config) {
596
617
  // Build navigation
597
618
  const navigation = buildNavigationStructure(allFiles, urlPath);
598
619
 
599
- // Generate full HTML (pass original content for SEO)
600
- const html = generateHTML(title, htmlContent, navigation, urlPath, config, content);
620
+ // Generate full HTML (pass original content and front matter for SEO)
621
+ const html = generateHTML(title, htmlContent, navigation, urlPath, config, content, frontMatter);
601
622
 
602
623
  // Write file
603
624
  await fs.ensureDir(path.dirname(outputPath));
604
625
  await fs.writeFile(outputPath, html);
605
626
 
606
- return { title, urlPath, summary };
627
+ return { title, urlPath, summary, frontMatter };
607
628
  }
608
629
 
609
630
  // Get all markdown files
package/lib/seo.js CHANGED
@@ -48,23 +48,60 @@ function extractKeywords(content, maxKeywords = 10) {
48
48
  /**
49
49
  * Generate meta description from content
50
50
  */
51
- function generateDescription(content, maxLength = 160) {
52
- // Remove markdown syntax and get first paragraph
51
+ function generateDescription(content, mode = 'smart', maxLength = 160) {
52
+ // Remove markdown syntax
53
53
  const plainText = content
54
54
  .replace(/```[\s\S]*?```/g, '') // Remove code blocks
55
+ .replace(/^---[\s\S]*?---/m, '') // Remove front matter
55
56
  .replace(/#+\s(.+)/g, '$1. ') // Convert headers to sentences
56
57
  .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // Extract link text
57
58
  .replace(/[*_~]/g, '') // Remove emphasis
58
59
  .replace(/\n+/g, ' ') // Replace newlines with spaces
59
60
  .trim();
60
61
 
61
- // Find first sentence or use first maxLength characters
62
- const firstSentence = plainText.match(/^[^.!?]+[.!?]/);
63
- let description = firstSentence ? firstSentence[0] : plainText;
62
+ let description = '';
63
+
64
+ if (mode === 'smart') {
65
+ // Try to find intro paragraph after first header
66
+ const lines = content.split('\n');
67
+ let foundH1 = false;
68
+ let introText = '';
69
+
70
+ for (const line of lines) {
71
+ if (line.match(/^#\s+/)) {
72
+ foundH1 = true;
73
+ continue;
74
+ }
75
+ if (foundH1 && line.trim() && !line.match(/^#/)) {
76
+ introText = line.trim();
77
+ break;
78
+ }
79
+ }
80
+
81
+ if (introText) {
82
+ // Clean markdown from intro text
83
+ description = introText
84
+ .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
85
+ .replace(/[*_~]/g, '');
86
+ }
87
+ }
88
+
89
+ // Fallback to first paragraph
90
+ if (!description) {
91
+ const firstSentence = plainText.match(/^[^.!?]+[.!?]/);
92
+ description = firstSentence ? firstSentence[0] : plainText;
93
+ }
64
94
 
65
- // Truncate if needed
95
+ // Ensure optimal length (140-160 chars)
66
96
  if (description.length > maxLength) {
67
- description = description.substring(0, maxLength - 3) + '...';
97
+ // Try to cut at word boundary
98
+ const cutPoint = description.lastIndexOf(' ', maxLength - 3);
99
+ description = description.substring(0, cutPoint > 100 ? cutPoint : maxLength - 3) + '...';
100
+ } else if (description.length < 140 && description.length > 100) {
101
+ // Maybe add a call to action if too short
102
+ if (!description.match(/[.!?]$/)) {
103
+ description += '.';
104
+ }
68
105
  }
69
106
 
70
107
  return description;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knowcode/doc-builder",
3
- "version": "1.5.4",
3
+ "version": "1.5.5",
4
4
  "description": "Reusable documentation builder for markdown-based sites with Vercel deployment support",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -27,6 +27,7 @@
27
27
  "chalk": "^4.1.2",
28
28
  "commander": "^11.0.0",
29
29
  "fs-extra": "^11.2.0",
30
+ "gray-matter": "^4.0.3",
30
31
  "marked": "^15.0.12",
31
32
  "ora": "5.4.1",
32
33
  "prompts": "^2.4.2"