```js #!/usr/bin/env node import fs from 'fs/promises'; import path from 'path'; import https from 'https'; const REMOTE_URL = 'https://raw.githubusercontent.com/get-convex/convex-js/refs/heads/main/CHANGELOG.md'; const LOCAL_FILE_PATH = './.cursor/rules/general/convex.changes.mdc'; const DESCRIPTION = 'Contains the official, up-to-date changelog for the Convex framework, detailing all releases, new features, bug fixes, and breaking changes. Retrieve this file to understand recent updates, check for migration notes, or reference historical changes relevant to Convex development and compatibility - or when the user asks for it.'; const FRONTMATTER = `---\ndescription: ${DESCRIPTION}\nalwaysApply: false\n---`; /** * Download file from URL */ function downloadFile(url) { return new Promise((resolve, reject) => { https .get(url, (response) => { // Handle redirects if ( response.statusCode >= 300 && response.statusCode < 400 && response.headers.location ) { return downloadFile(response.headers.location) .then(resolve) .catch(reject); } if (response.statusCode !== 200) { reject( new Error(`HTTP ${response.statusCode}: ${response.statusMessage}`) ); return; } let data = ''; response.setEncoding('utf8'); response.on('data', (chunk) => { data += chunk; }); response.on('end', () => { resolve(data); }); response.on('error', reject); }) .on('error', reject); }); } /** * Ensure directory exists */ async function ensureDirectoryExists(filePath) { const dir = path.dirname(filePath); try { await fs.access(dir); } catch (error) { await fs.mkdir(dir, { recursive: true }); } } /** * Read local file content, return empty string if file doesn't exist */ async function readLocalFile(filePath) { try { return await fs.readFile(filePath, 'utf8'); } catch (error) { if (error.code === 'ENOENT') { return ''; } throw error; } } /** * Normalize content for comparison (trim whitespace, normalize line endings) */ function normalizeContent(content) { return content.trim().replace(/\r\n/g, '\n'); } /** * Extract YAML frontmatter (between first two --- lines) if present * Returns { frontmatter: string, rest: string } */ function extractFrontmatter(content) { const lines = content.split(/\r?\n/); if (lines[0] === '---') { let endIdx = -1; for (let i = 1; i < lines.length; i++) { if (lines[i] === '---') { endIdx = i; break; } } if (endIdx !== -1) { const frontmatter = lines.slice(0, endIdx + 1).join('\n'); const rest = lines .slice(endIdx + 1) .join('\n') .replace(/^\n+/, ''); return { frontmatter, rest }; } } return { frontmatter: '', rest: content }; } /** * Main sync function */ async function syncChangelog() { console.log('🔄 Syncing Convex changelog...'); try { // Download remote content const remoteContent = await downloadFile(REMOTE_URL); // Compose new file content with fixed frontmatter const newFileContent = `${FRONTMATTER}\n\n${remoteContent.trim()}\n`; // Read local content const localContent = await readLocalFile(LOCAL_FILE_PATH); // Normalize both contents for comparison const normalizedRemote = normalizeContent(newFileContent); const normalizedLocal = normalizeContent(localContent); // Compare contents if (normalizedRemote === normalizedLocal) { console.log('✅ Local changelog is up to date!'); process.exit(0); } // Contents differ, update local file console.log('📝 Updating local changelog file...'); // Ensure directory exists await ensureDirectoryExists(LOCAL_FILE_PATH); // Write new content await fs.writeFile(LOCAL_FILE_PATH, newFileContent, 'utf8'); console.log('✅ Successfully updated local changelog!'); process.exit(0); } catch (error) { console.error('❌ Error syncing changelog:', error.message); process.exit(1); } } // Run the sync syncChangelog(); ```