UNPKG

myaidev-method

Version:

Comprehensive development framework with SPARC methodology for AI-assisted software development, multi-platform publishing (WordPress, PayloadCMS, Astro, Docusaurus, Mintlify), and Coolify deployment

378 lines (314 loc) 10.3 kB
/** * Static Site Publishing Utilities * Unified utilities for publishing to static site generators * Supports: Docusaurus, Mintlify, Astro * Optimized for Claude Code 2.0 agent integration */ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs'; import { join, dirname, basename } from 'path'; import { parse as parseEnv } from 'dotenv'; import grayMatter from 'gray-matter'; import simpleGit from 'simple-git'; // Platform configurations const platformConfigs = { docusaurus: { contentDirs: { docs: 'docs', blog: 'blog', pages: 'src/pages' }, frontmatterFields: ['id', 'title', 'sidebar_label', 'sidebar_position', 'description', 'tags'], configFile: 'docusaurus.config.js', sidebarFile: 'sidebars.js', fileExtensions: ['.md', '.mdx'], requiresNavUpdate: false }, mintlify: { contentDirs: { default: '.' }, frontmatterFields: ['title', 'description', 'icon', 'iconType'], configFile: 'mint.json', requiresNavUpdate: true, fileExtensions: ['.mdx'], defaultExtension: '.mdx' }, astro: { contentDirs: { content: 'src/content', pages: 'src/pages' }, frontmatterFields: ['title', 'description', 'pubDate', 'author', 'image', 'tags'], configFile: 'astro.config.mjs', collectionSchema: true, fileExtensions: ['.md', '.mdx'], requiresNavUpdate: false } }; export class StaticSiteUtils { constructor(config = {}) { this.platform = config.platform; // Load platform-specific configuration if (!this.platform || !platformConfigs[this.platform]) { throw new Error(`Invalid platform. Supported: ${Object.keys(platformConfigs).join(', ')}`); } this.platformConfig = platformConfigs[this.platform]; // Load project path from config or .env this.projectPath = config.projectPath || this.loadProjectPath(); // Load git configuration const envConfig = this.loadEnvConfig(); this.gitUserName = config.gitUserName || envConfig.GIT_USER_NAME; this.gitUserEmail = config.gitUserEmail || envConfig.GIT_USER_EMAIL; this.gitRemote = config.gitRemote || envConfig.GIT_REMOTE_URL || 'origin'; // Initialize git this.git = simpleGit(this.projectPath); } loadEnvConfig() { try { const envPath = process.env.ENV_PATH || '.env'; if (existsSync(envPath)) { const envContent = readFileSync(envPath, 'utf8'); return parseEnv(envContent); } return {}; } catch (error) { return {}; } } loadProjectPath() { const envConfig = this.loadEnvConfig(); const envKey = `${this.platform.toUpperCase()}_PROJECT_PATH`; return envConfig[envKey] || process.cwd(); } /** * Validate project structure */ validateProject() { const configPath = join(this.projectPath, this.platformConfig.configFile); if (!existsSync(configPath)) { throw new Error( `${this.platform} project not found. Missing ${this.platformConfig.configFile} in ${this.projectPath}` ); } // Check if git repository const gitPath = join(this.projectPath, '.git'); if (!existsSync(gitPath)) { throw new Error(`Not a git repository: ${this.projectPath}`); } return true; } /** * Transform frontmatter for platform-specific requirements */ transformFrontmatter(frontmatter) { const allowedFields = this.platformConfig.frontmatterFields; const transformed = {}; // Only include allowed fields for (const field of allowedFields) { if (frontmatter[field] !== undefined) { transformed[field] = frontmatter[field]; } } // Platform-specific transformations switch (this.platform) { case 'docusaurus': // Generate id from title if not provided if (!transformed.id && transformed.title) { transformed.id = transformed.title .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-|-$/g, ''); } break; case 'mintlify': // Mintlify requires title if (!transformed.title) { throw new Error('title is required in frontmatter for Mintlify'); } break; case 'astro': // Add pubDate if not present if (!transformed.pubDate) { transformed.pubDate = new Date().toISOString(); } break; } return transformed; } /** * Determine target directory based on content type */ getTargetDirectory(options = {}) { const contentType = options.type || 'docs'; const dirs = this.platformConfig.contentDirs; const baseDir = dirs[contentType] || dirs.default || dirs.docs; if (!baseDir) { throw new Error(`Unknown content type: ${contentType}`); } const targetDir = join(this.projectPath, baseDir); // Create directory if it doesn't exist if (!existsSync(targetDir)) { mkdirSync(targetDir, { recursive: true }); } return targetDir; } /** * Generate filename with appropriate extension */ generateFilename(title, originalFile) { const ext = this.platformConfig.defaultExtension || this.platformConfig.fileExtensions[0] || '.md'; // If original file has allowed extension, use it if (originalFile) { const originalExt = originalFile.substring(originalFile.lastIndexOf('.')); if (this.platformConfig.fileExtensions.includes(originalExt)) { return basename(originalFile); } } // Generate from title const slug = title .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-|-$/g, ''); return `${slug}${ext}`; } /** * Publish markdown content to static site */ async publishContent(markdownFile, options = {}) { // Validate project this.validateProject(); // Read and parse markdown const fileContent = readFileSync(markdownFile, 'utf8'); const { data: frontmatter, content } = grayMatter(fileContent); // Transform frontmatter const transformedFrontmatter = this.transformFrontmatter(frontmatter); // Get target directory const targetDir = this.getTargetDirectory(options); // Generate filename const filename = options.filename || this.generateFilename(transformedFrontmatter.title, markdownFile); const targetPath = join(targetDir, filename); // Combine frontmatter and content const output = grayMatter.stringify(content, transformedFrontmatter); // Write file writeFileSync(targetPath, output, 'utf8'); // Update navigation if required if (this.platformConfig.requiresNavUpdate) { await this.updateNavigation(filename, transformedFrontmatter, options); } // Git operations const gitResult = await this.gitCommitAndPush(targetPath, options); return { success: true, file: targetPath, platform: this.platform, git: gitResult }; } /** * Update navigation configuration (Mintlify) */ async updateNavigation(filename, frontmatter, options = {}) { if (this.platform !== 'mintlify') { return; } const mintJsonPath = join(this.projectPath, 'mint.json'); if (!existsSync(mintJsonPath)) { console.warn('mint.json not found, skipping navigation update'); return; } try { const mintConfig = JSON.parse(readFileSync(mintJsonPath, 'utf8')); // Add to navigation if specified if (options.navSection) { const section = mintConfig.navigation?.find(nav => nav.group === options.navSection); if (section) { const pagePath = filename.replace(/\.mdx?$/, ''); if (!section.pages.includes(pagePath)) { section.pages.push(pagePath); writeFileSync(mintJsonPath, JSON.stringify(mintConfig, null, 2), 'utf8'); } } } } catch (error) { console.warn(`Failed to update mint.json: ${error.message}`); } } /** * Git commit and push changes */ async gitCommitAndPush(filePath, options = {}) { try { // Configure git if credentials provided if (this.gitUserName && this.gitUserEmail) { await this.git.addConfig('user.name', this.gitUserName, false); await this.git.addConfig('user.email', this.gitUserEmail, false); } // Stage file await this.git.add(filePath); // Commit const commitMessage = options.commitMessage || `Add/Update ${basename(filePath)} via myaidev-method`; await this.git.commit(commitMessage); // Push if not in dry-run mode if (!options.dryRun && !options.noPush) { const branch = options.branch || 'main'; await this.git.push(this.gitRemote, branch); return { committed: true, pushed: true, branch, message: commitMessage }; } return { committed: true, pushed: false, message: commitMessage }; } catch (error) { throw new Error(`Git operation failed: ${error.message}`); } } /** * Get git status */ async getGitStatus() { try { return await this.git.status(); } catch (error) { throw new Error(`Failed to get git status: ${error.message}`); } } /** * List content files in project */ listContent(contentType = 'docs') { const targetDir = this.getTargetDirectory({ type: contentType }); const fs = await import('fs'); const files = fs.readdirSync(targetDir) .filter(file => { const ext = file.substring(file.lastIndexOf('.')); return this.platformConfig.fileExtensions.includes(ext); }); return files.map(file => join(targetDir, file)); } } export default StaticSiteUtils; /** * Helper function to detect platform from project structure */ export function detectPlatform(projectPath = process.cwd()) { const checks = { docusaurus: join(projectPath, 'docusaurus.config.js'), mintlify: join(projectPath, 'mint.json'), astro: join(projectPath, 'astro.config.mjs') }; for (const [platform, configPath] of Object.entries(checks)) { if (existsSync(configPath)) { return platform; } } return null; }