@knowcode/doc-builder 1.5.6 → 1.5.8
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 +31 -0
- package/cli.js +132 -45
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,37 @@ 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.8] - 2025-07-22
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- **BREAKING**: `seo-check` command now analyzes generated HTML files instead of markdown source files
|
|
12
|
+
- Completely rewrote SEO analysis to inspect actual HTML output that search engines see
|
|
13
|
+
- Added comprehensive checks for all SEO elements in HTML including:
|
|
14
|
+
- Title tags and optimal length
|
|
15
|
+
- Meta descriptions and character limits
|
|
16
|
+
- Keywords meta tags
|
|
17
|
+
- Canonical URLs
|
|
18
|
+
- H1 tags and consistency with titles
|
|
19
|
+
- Open Graph tags for social media
|
|
20
|
+
- Twitter Card tags
|
|
21
|
+
- Structured data (JSON-LD)
|
|
22
|
+
- Updated help text and examples to reflect HTML analysis
|
|
23
|
+
- Improved error messages to guide users to build HTML first
|
|
24
|
+
|
|
25
|
+
### Why This Change?
|
|
26
|
+
- SEO analysis should check what search engines actually see (the generated HTML)
|
|
27
|
+
- Provides more accurate and comprehensive SEO insights
|
|
28
|
+
- Can verify that all meta tags are properly generated
|
|
29
|
+
- Ensures Open Graph and Twitter Cards are working correctly
|
|
30
|
+
|
|
31
|
+
## [1.5.7] - 2025-07-22
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
- Fixed "path argument must be string" error when running seo-check via npx
|
|
35
|
+
- Properly pass options to loadConfig in seo-check command
|
|
36
|
+
- Added validation to check if docs directory exists before running SEO analysis
|
|
37
|
+
- Improved error messages when docs directory is missing
|
|
38
|
+
|
|
8
39
|
## [1.5.6] - 2025-07-22
|
|
9
40
|
|
|
10
41
|
### Fixed
|
package/cli.js
CHANGED
|
@@ -196,28 +196,38 @@ ${chalk.yellow('Get your verification code from Google Search Console.')}
|
|
|
196
196
|
// SEO Check command
|
|
197
197
|
program
|
|
198
198
|
.command('seo-check [path]')
|
|
199
|
-
.description('Analyze SEO
|
|
199
|
+
.description('Analyze SEO metadata in generated HTML files')
|
|
200
200
|
.option('-c, --config <path>', 'path to config file (default: doc-builder.config.js)')
|
|
201
|
-
.option('--fix', 'auto-fix common SEO issues')
|
|
202
201
|
.addHelpText('after', `
|
|
203
202
|
${chalk.yellow('Examples:')}
|
|
204
|
-
${chalk.gray('$')} doc-builder seo-check ${chalk.gray('# Check all pages')}
|
|
205
|
-
${chalk.gray('$')} doc-builder seo-check
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
•
|
|
210
|
-
•
|
|
211
|
-
•
|
|
212
|
-
•
|
|
213
|
-
•
|
|
203
|
+
${chalk.gray('$')} doc-builder seo-check ${chalk.gray('# Check all HTML pages')}
|
|
204
|
+
${chalk.gray('$')} doc-builder seo-check html/guide.html ${chalk.gray('# Check specific HTML file')}
|
|
205
|
+
|
|
206
|
+
${chalk.yellow('This command analyzes generated HTML files for:')}
|
|
207
|
+
• Title tags and length (50-60 characters)
|
|
208
|
+
• Meta descriptions (140-160 characters)
|
|
209
|
+
• Keywords meta tags
|
|
210
|
+
• Canonical URLs
|
|
211
|
+
• H1 tags and consistency
|
|
212
|
+
• Open Graph tags (Facebook)
|
|
213
|
+
• Twitter Card tags
|
|
214
|
+
• Structured data (JSON-LD)
|
|
215
|
+
|
|
216
|
+
${chalk.yellow('Note:')} Run 'doc-builder build' first to generate HTML files.
|
|
214
217
|
`)
|
|
215
218
|
.action(async (filePath, options) => {
|
|
216
219
|
try {
|
|
217
|
-
const config = await loadConfig(options.config);
|
|
218
|
-
const
|
|
220
|
+
const config = await loadConfig(options.config || 'doc-builder.config.js', options);
|
|
221
|
+
const outputDir = path.resolve(config.outputDir || 'html');
|
|
222
|
+
|
|
223
|
+
// Check if outputDir exists
|
|
224
|
+
if (!await fs.pathExists(outputDir)) {
|
|
225
|
+
console.error(chalk.red(`Output directory not found: ${outputDir}`));
|
|
226
|
+
console.log(chalk.yellow('\nPlease build your documentation first with: doc-builder build'));
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
219
229
|
|
|
220
|
-
console.log(chalk.cyan('🔍 Analyzing SEO...'));
|
|
230
|
+
console.log(chalk.cyan('🔍 Analyzing SEO in generated HTML files...'));
|
|
221
231
|
|
|
222
232
|
// Get files to check
|
|
223
233
|
let files = [];
|
|
@@ -230,7 +240,7 @@ ${chalk.yellow('This command analyzes:')}
|
|
|
230
240
|
process.exit(1);
|
|
231
241
|
}
|
|
232
242
|
} else {
|
|
233
|
-
// Get all
|
|
243
|
+
// Get all HTML files
|
|
234
244
|
const getAllFiles = async (dir) => {
|
|
235
245
|
const results = [];
|
|
236
246
|
const items = await fs.readdir(dir);
|
|
@@ -239,13 +249,13 @@ ${chalk.yellow('This command analyzes:')}
|
|
|
239
249
|
const stat = await fs.stat(fullPath);
|
|
240
250
|
if (stat.isDirectory() && !item.startsWith('.')) {
|
|
241
251
|
results.push(...await getAllFiles(fullPath));
|
|
242
|
-
} else if (item.endsWith('.
|
|
252
|
+
} else if (item.endsWith('.html') && item !== '404.html') {
|
|
243
253
|
results.push(fullPath);
|
|
244
254
|
}
|
|
245
255
|
}
|
|
246
256
|
return results;
|
|
247
257
|
};
|
|
248
|
-
files = await getAllFiles(
|
|
258
|
+
files = await getAllFiles(outputDir);
|
|
249
259
|
}
|
|
250
260
|
|
|
251
261
|
// Analyze each file
|
|
@@ -254,19 +264,43 @@ ${chalk.yellow('This command analyzes:')}
|
|
|
254
264
|
|
|
255
265
|
for (const file of files) {
|
|
256
266
|
const content = await fs.readFile(file, 'utf-8');
|
|
257
|
-
const
|
|
258
|
-
|
|
267
|
+
const relativePath = path.relative(outputDir, file);
|
|
268
|
+
|
|
269
|
+
// Extract metadata from HTML
|
|
270
|
+
const titleMatch = content.match(/<title>([^<]+)<\/title>/);
|
|
271
|
+
const descMatch = content.match(/<meta\s+name="description"\s+content="([^"]+)"/);
|
|
272
|
+
const keywordsMatch = content.match(/<meta\s+name="keywords"\s+content="([^"]+)"/);
|
|
273
|
+
const canonicalMatch = content.match(/<link\s+rel="canonical"\s+href="([^"]+)"/);
|
|
274
|
+
const h1Match = content.match(/<h1>([^<]+)<\/h1>/);
|
|
259
275
|
|
|
260
|
-
|
|
261
|
-
const
|
|
262
|
-
const
|
|
263
|
-
const
|
|
264
|
-
const
|
|
265
|
-
|
|
276
|
+
const title = titleMatch ? titleMatch[1] : 'No title found';
|
|
277
|
+
const description = descMatch ? descMatch[1] : '';
|
|
278
|
+
const keywords = keywordsMatch ? keywordsMatch[1].split(',').map(k => k.trim()) : [];
|
|
279
|
+
const canonical = canonicalMatch ? canonicalMatch[1] : '';
|
|
280
|
+
const h1 = h1Match ? h1Match[1] : '';
|
|
281
|
+
|
|
282
|
+
// Check for Open Graph tags
|
|
283
|
+
const ogTitleMatch = content.match(/<meta\s+property="og:title"\s+content="([^"]+)"/);
|
|
284
|
+
const ogDescMatch = content.match(/<meta\s+property="og:description"\s+content="([^"]+)"/);
|
|
285
|
+
const ogImageMatch = content.match(/<meta\s+property="og:image"\s+content="([^"]+)"/);
|
|
286
|
+
|
|
287
|
+
// Check for Twitter Card tags
|
|
288
|
+
const twitterTitleMatch = content.match(/<meta\s+name="twitter:title"\s+content="([^"]+)"/);
|
|
289
|
+
const twitterDescMatch = content.match(/<meta\s+name="twitter:description"\s+content="([^"]+)"/);
|
|
290
|
+
|
|
291
|
+
// Check for structured data
|
|
292
|
+
const structuredDataMatch = content.match(/<script\s+type="application\/ld\+json">/);
|
|
266
293
|
|
|
267
294
|
// Check title
|
|
268
295
|
const titleLength = title.length;
|
|
269
|
-
if (
|
|
296
|
+
if (!titleMatch) {
|
|
297
|
+
issues.push({
|
|
298
|
+
file: relativePath,
|
|
299
|
+
type: 'title',
|
|
300
|
+
message: 'Missing <title> tag',
|
|
301
|
+
severity: 'critical'
|
|
302
|
+
});
|
|
303
|
+
} else if (titleLength > 60) {
|
|
270
304
|
issues.push({
|
|
271
305
|
file: relativePath,
|
|
272
306
|
type: 'title',
|
|
@@ -283,38 +317,90 @@ ${chalk.yellow('This command analyzes:')}
|
|
|
283
317
|
}
|
|
284
318
|
|
|
285
319
|
// Check description
|
|
286
|
-
|
|
287
|
-
if (descLength > 160) {
|
|
320
|
+
if (!descMatch) {
|
|
288
321
|
issues.push({
|
|
289
322
|
file: relativePath,
|
|
290
323
|
type: 'description',
|
|
291
|
-
message:
|
|
324
|
+
message: 'Missing meta description',
|
|
325
|
+
severity: 'critical'
|
|
326
|
+
});
|
|
327
|
+
} else {
|
|
328
|
+
const descLength = description.length;
|
|
329
|
+
if (descLength > 160) {
|
|
330
|
+
issues.push({
|
|
331
|
+
file: relativePath,
|
|
332
|
+
type: 'description',
|
|
333
|
+
message: `Description too long (${descLength} chars, max 160)`,
|
|
292
334
|
current: description,
|
|
293
335
|
suggestion: description.substring(0, 157) + '...'
|
|
294
336
|
});
|
|
295
|
-
|
|
337
|
+
} else if (descLength < 120) {
|
|
338
|
+
suggestions.push({
|
|
339
|
+
file: relativePath,
|
|
340
|
+
type: 'description',
|
|
341
|
+
message: `Description might be too short (${descLength} chars, ideal 140-160)`
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Check keywords
|
|
347
|
+
if (!keywordsMatch || keywords.length === 0) {
|
|
296
348
|
suggestions.push({
|
|
297
349
|
file: relativePath,
|
|
298
|
-
type: '
|
|
299
|
-
message:
|
|
350
|
+
type: 'keywords',
|
|
351
|
+
message: 'No keywords meta tag found'
|
|
300
352
|
});
|
|
301
353
|
}
|
|
302
354
|
|
|
303
|
-
// Check
|
|
304
|
-
if (
|
|
355
|
+
// Check canonical URL
|
|
356
|
+
if (!canonicalMatch) {
|
|
305
357
|
suggestions.push({
|
|
306
358
|
file: relativePath,
|
|
307
|
-
type: '
|
|
308
|
-
message: '
|
|
359
|
+
type: 'canonical',
|
|
360
|
+
message: 'Missing canonical URL'
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Check H1
|
|
365
|
+
if (!h1Match) {
|
|
366
|
+
issues.push({
|
|
367
|
+
file: relativePath,
|
|
368
|
+
type: 'h1',
|
|
369
|
+
message: 'Missing H1 tag',
|
|
370
|
+
severity: 'important'
|
|
371
|
+
});
|
|
372
|
+
} else if (h1 !== title.split(' | ')[0]) {
|
|
373
|
+
suggestions.push({
|
|
374
|
+
file: relativePath,
|
|
375
|
+
type: 'h1',
|
|
376
|
+
message: 'H1 differs from page title'
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Check Open Graph tags
|
|
381
|
+
if (!ogTitleMatch || !ogDescMatch) {
|
|
382
|
+
suggestions.push({
|
|
383
|
+
file: relativePath,
|
|
384
|
+
type: 'opengraph',
|
|
385
|
+
message: 'Missing or incomplete Open Graph tags'
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Check Twitter Card tags
|
|
390
|
+
if (!twitterTitleMatch || !twitterDescMatch) {
|
|
391
|
+
suggestions.push({
|
|
392
|
+
file: relativePath,
|
|
393
|
+
type: 'twitter',
|
|
394
|
+
message: 'Missing or incomplete Twitter Card tags'
|
|
309
395
|
});
|
|
310
396
|
}
|
|
311
397
|
|
|
312
|
-
// Check
|
|
313
|
-
if (!
|
|
398
|
+
// Check structured data
|
|
399
|
+
if (!structuredDataMatch) {
|
|
314
400
|
suggestions.push({
|
|
315
401
|
file: relativePath,
|
|
316
|
-
type: '
|
|
317
|
-
message: '
|
|
402
|
+
type: 'structured-data',
|
|
403
|
+
message: 'Missing structured data (JSON-LD)'
|
|
318
404
|
});
|
|
319
405
|
}
|
|
320
406
|
}
|
|
@@ -345,15 +431,16 @@ ${chalk.yellow('This command analyzes:')}
|
|
|
345
431
|
});
|
|
346
432
|
}
|
|
347
433
|
|
|
348
|
-
console.log(`\n${chalk.cyan('💡 Tips:')}`);
|
|
349
|
-
console.log(' • Add front matter to customize SEO
|
|
434
|
+
console.log(`\n${chalk.cyan('💡 Tips to improve SEO:')}`);
|
|
435
|
+
console.log(' • Add front matter to markdown files to customize SEO');
|
|
350
436
|
console.log(' • Keep titles between 50-60 characters');
|
|
351
437
|
console.log(' • Write descriptions between 140-160 characters');
|
|
352
438
|
console.log(' • Include relevant keywords in front matter');
|
|
353
|
-
console.log(
|
|
439
|
+
console.log(' • Ensure each page has a unique title and description');
|
|
440
|
+
console.log(`\n${chalk.gray('Add to your markdown files:')}`);
|
|
354
441
|
console.log(chalk.gray('---'));
|
|
355
442
|
console.log(chalk.gray('title: "Your SEO Optimized Title Here"'));
|
|
356
|
-
console.log(chalk.gray('description: "A compelling description between 140-160 characters
|
|
443
|
+
console.log(chalk.gray('description: "A compelling description between 140-160 characters."'));
|
|
357
444
|
console.log(chalk.gray('keywords: ["keyword1", "keyword2", "keyword3"]'));
|
|
358
445
|
console.log(chalk.gray('---'));
|
|
359
446
|
}
|