zyros
Version:
A developer-friendly static site generator built with Next.js and Tailwind CSS. Transform a simple JSON file into a beautiful, fast static website.
222 lines (194 loc) • 7.64 kB
JavaScript
const { execSync } = require('child_process');
const path = require('path');
const fs = require('fs');
const childProcess = require('child_process');
// Helper function to safely remove directory with retry logic
function safeRemoveDir(dirPath, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
if (fs.existsSync(dirPath)) {
// Try to remove read-only attributes on Windows
if (process.platform === 'win32') {
try {
execSync(`attrib -R "${dirPath}\\*" /S /D`, { stdio: 'ignore' });
} catch (e) {
// Ignore attrib errors
}
}
fs.rmSync(dirPath, { recursive: true, force: true });
}
return true;
} catch (error) {
if (i === maxRetries - 1) {
console.warn(`⚠️ Could not fully clean ${dirPath}: ${error.message}`);
return false;
}
// Wait a bit before retrying
if (process.platform === 'win32') {
childProcess.execSync('ping 127.0.0.1 -n 2 > nul', { stdio: 'ignore' });
} else {
childProcess.execSync('sleep 1', { stdio: 'ignore' });
}
}
}
return false;
}
// Generate RSS feed manually since TypeScript compilation is complex
async function generateRSS() {
try {
// Load the site data
const siteDataPath = path.join(process.cwd(), 'public', 'site.json');
if (!fs.existsSync(siteDataPath)) {
console.log('⚠️ No site.json found, skipping RSS generation');
return;
}
const siteData = JSON.parse(fs.readFileSync(siteDataPath, 'utf8'));
// Generate RSS content manually
const baseUrl = process.env.SITE_URL || 'https://your-site.com';
const { site, pages } = siteData;
if (!pages || pages.length === 0) {
console.log('⚠️ No pages found, skipping RSS generation');
return;
}
const rssItems = pages
.sort((a, b) => {
const dateA = new Date(a.publishedAt || '2024-01-01');
const dateB = new Date(b.publishedAt || '2024-01-01');
return dateB.getTime() - dateA.getTime();
})
.map(page => {
const pubDate = page.publishedAt
? new Date(page.publishedAt).toUTCString()
: new Date().toUTCString();
const description = page.description ||
(page.content ? page.content.replace(/[#*`]/g, '').substring(0, 200) + '...' : '');
return `
<item>
<title><![CDATA[${page.title}]]></title>
<description><![CDATA[${description}]]></description>
<link>${baseUrl}/${page.slug}</link>
<guid isPermaLink="true">${baseUrl}/${page.slug}</guid>
<pubDate>${pubDate}</pubDate>
${page.category ? `<category><![CDATA[${page.category}]]></category>` : ''}
${page.tags ? page.tags.map(tag => `<category><![CDATA[${tag}]]></category>`).join('\n ') : ''}
</item>`;
}).join('\n');
const rssContent = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title><![CDATA[${site.title}]]></title>
<description><![CDATA[${site.description || 'A static site built with zyros'}]]></description>
<link>${baseUrl}</link>
<atom:link href="${baseUrl}/rss.xml" rel="self" type="application/rss+xml"/>
<language>en-us</language>
<lastBuildDate>${new Date().toUTCString()}</lastBuildDate>
<generator>zyros</generator>${rssItems}
</channel>
</rss>`;
// Write RSS file to dist directory
const distDir = path.join(process.cwd(), 'out');
if (!fs.existsSync(distDir)) {
fs.mkdirSync(distDir, { recursive: true });
}
const rssPath = path.join(distDir, 'rss.xml');
fs.writeFileSync(rssPath, rssContent, 'utf8');
console.log('✓ RSS feed generated successfully');
} catch (error) {
console.error('Error generating RSS feed:', error);
}
}
async function main() {
try {
console.log('🚀 Building zyros...');
// Run Next.js build with error handling for Windows
console.log('📦 Building Next.js app...');
try {
// Clean output directory first on Windows
if (process.platform === 'win32') {
const outDir = path.join(process.cwd(), 'out');
safeRemoveDir(outDir);
}
execSync('next build', { stdio: 'inherit' });
} catch (error) {
// Check if it's just a Windows permission issue but build succeeded
const outDir = path.join(process.cwd(), 'out');
const buildSucceeded = fs.existsSync(outDir) && fs.readdirSync(outDir).length > 0;
if (buildSucceeded && error.status === 1 && (
error.message.includes('EPERM') ||
error.message.includes('ENOTEMPTY') ||
error.message.includes('uncaughtException') ||
(error.stderr && (error.stderr.includes('EPERM') || error.stderr.includes('ENOTEMPTY'))) ||
(error.stdout && (error.stdout.includes('EPERM') || error.stdout.includes('ENOTEMPTY')))
)) {
console.log('⚠️ Build completed successfully (Windows file permission issue ignored)');
} else if (buildSucceeded) {
console.log('⚠️ Build completed successfully (ignoring cleanup error)');
} else {
throw error;
}
}
// Generate RSS feed
console.log('📡 Generating RSS feed...');
await generateRSS();
// Generate sitemap (optional)
console.log('🗺️ Generating sitemap...');
await generateSitemap();
console.log('✅ Build completed successfully!');
process.exit(0);
} catch (error) {
console.error('❌ Build failed:', error);
process.exit(1);
}
}
async function generateSitemap() {
try {
const siteDataPath = path.join(process.cwd(), 'public', 'site.json');
if (!fs.existsSync(siteDataPath)) {
console.log('⚠️ No site.json found, skipping sitemap generation');
return;
}
const siteData = JSON.parse(fs.readFileSync(siteDataPath, 'utf8'));
const baseUrl = process.env.SITE_URL || 'https://your-site.com';
const urls = [
{
loc: baseUrl,
lastmod: new Date().toISOString(),
changefreq: 'weekly',
priority: '1.0'
}
];
if (siteData.pages && siteData.pages.length > 0) {
urls.push(...siteData.pages.map(page => ({
loc: `${baseUrl}/${page.slug}`,
lastmod: page.publishedAt ? new Date(page.publishedAt).toISOString() : new Date().toISOString(),
changefreq: 'monthly',
priority: '0.8'
})));
}
const sitemapContent = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urls.map(url => ` <url>
<loc>${url.loc}</loc>
<lastmod>${url.lastmod}</lastmod>
<changefreq>${url.changefreq}</changefreq>
<priority>${url.priority}</priority>
</url>`).join('\n')}
</urlset>`;
const distDir = path.join(process.cwd(), 'out');
const sitemapPath = path.join(distDir, 'sitemap.xml');
fs.writeFileSync(sitemapPath, sitemapContent, 'utf8');
console.log('✓ Sitemap generated successfully');
} catch (error) {
console.error('Error generating sitemap:', error);
}
}
// Export functions for use in other modules
module.exports = {
generateSite: main,
generateRSS,
generateSitemap,
safeRemoveDir
};
if (require.main === module) {
main();
}