tpmjs/packages/tools/createBlogPost/src/index.ts
Ajax Davis b58df449da feat(tools): create @tpmjs/createblogpost test tool package
Add test tool package to verify sync system can discover and process tools:

Package Features:
- Creates structured blog posts with frontmatter and metadata
- Supports both Markdown and MDX output formats
- Automatic slug generation from title
- Word count and reading time calculation
- SEO-friendly metadata generation

TPMJS Integration:
- Rich-tier tpmjs field with all optional metadata
- Tagged with 'tpmjs-tool' keyword for NPM discovery
- Complete parameter and return type documentation
- AI agent guidance for optimal LLM usage
- Pricing, authentication, and framework metadata

Implementation:
- Full TypeScript implementation with exported types
- Proper tsconfig and tsup build configuration
- Comprehensive README with usage examples
- Changeset for publishing workflow
- Added packages/tools/* to pnpm workspace

This package serves as an end-to-end test of the sync workers to verify:
1. NPM keyword search discovers the package
2. Changes feed picks up updates
3. tpmjs field validation works correctly
4. Rich-tier metadata is properly extracted
5. Quality score calculation functions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-28 03:10:31 +10:00

147 lines
3.1 KiB
TypeScript

/**
* Blog Post Creation Tool for TPMJS
* Creates structured blog posts with frontmatter and metadata
*/
export interface BlogPostOptions {
title: string;
author: string;
content: string;
tags?: string[];
format?: 'markdown' | 'mdx';
excerpt?: string;
publishDate?: Date;
}
export interface BlogPost {
frontmatter: {
title: string;
author: string;
date: string;
tags: string[];
excerpt?: string;
slug: string;
wordCount: number;
readingTime: number;
};
content: string;
formattedOutput: string;
}
/**
* Creates a slug from a title
*/
function createSlug(title: string): string {
return title
.toLowerCase()
.trim()
.replace(/[^\w\s-]/g, '')
.replace(/[\s_-]+/g, '-')
.replace(/^-+|-+$/g, '');
}
/**
* Calculates reading time based on word count
* Assumes average reading speed of 200 words per minute
*/
function calculateReadingTime(wordCount: number): number {
return Math.ceil(wordCount / 200);
}
/**
* Counts words in content
*/
function countWords(content: string): number {
return content.trim().split(/\s+/).length;
}
/**
* Formats frontmatter as YAML
*/
function formatFrontmatter(
frontmatter: BlogPost['frontmatter'],
format: 'markdown' | 'mdx'
): string {
const delimiter = format === 'mdx' ? '---' : '---';
const lines = [
delimiter,
`title: "${frontmatter.title}"`,
`author: ${frontmatter.author}`,
`date: ${frontmatter.date}`,
`slug: ${frontmatter.slug}`,
`tags: [${frontmatter.tags.map((tag) => `"${tag}"`).join(', ')}]`,
];
if (frontmatter.excerpt) {
lines.push(`excerpt: "${frontmatter.excerpt}"`);
}
lines.push(`wordCount: ${frontmatter.wordCount}`);
lines.push(`readingTime: ${frontmatter.readingTime}`);
lines.push(delimiter);
return lines.join('\n');
}
/**
* Creates a structured blog post with frontmatter and metadata
*/
export async function createBlogPost(options: BlogPostOptions): Promise<BlogPost> {
const {
title,
author,
content,
tags = [],
format = 'markdown',
excerpt,
publishDate = new Date(),
} = options;
// Validate required fields
if (!title || title.trim().length === 0) {
throw new Error('Title is required');
}
if (!author || author.trim().length === 0) {
throw new Error('Author is required');
}
if (!content || content.trim().length === 0) {
throw new Error('Content is required');
}
// Calculate metadata
const slug = createSlug(title);
const wordCount = countWords(content);
const readingTime = calculateReadingTime(wordCount);
// Build frontmatter
const frontmatter: BlogPost['frontmatter'] = {
title,
author,
date: publishDate.toISOString().split('T')[0] || '',
tags,
slug,
wordCount,
readingTime,
};
if (excerpt) {
frontmatter.excerpt = excerpt;
}
// Format the complete blog post
const formattedFrontmatter = formatFrontmatter(frontmatter, format);
const formattedOutput = `${formattedFrontmatter}\n\n${content}`;
return {
frontmatter,
content,
formattedOutput,
};
}
/**
* Export default for convenience
*/
export default createBlogPost;