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
JavaScript
/**
* 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;
}