From 0513000a9eb97afcb82e9e18680d6f9c812a38d7 Mon Sep 17 00:00:00 2001 From: southseact-3d Date: Wed, 11 Feb 2026 19:23:23 +0000 Subject: [PATCH] Fix builder page layout: make new chat and history buttons sticky at top on desktop - Moved sticky positioning from mobile-only to global CSS for .top-left-actions - Buttons now stay fixed at top above content on all screen sizes - Includes various other app updates (version management, server improvements) --- VERSION_MANAGEMENT.md | 143 ++++++++++++ chat/public/apps.html | 7 + chat/public/builder.html | 20 +- chat/public/builder.js | 2 - chat/public/faq.html | 6 - chat/public/home.html | 10 +- chat/server.js | 158 ++++++++++++- chat/src/utils/versionManager.js | 309 ++++++++++++++++++++++++++ chat/src/utils/versionManager.test.js | 115 ++++++++++ 9 files changed, 742 insertions(+), 28 deletions(-) create mode 100644 VERSION_MANAGEMENT.md create mode 100644 chat/src/utils/versionManager.js create mode 100644 chat/src/utils/versionManager.test.js diff --git a/VERSION_MANAGEMENT.md b/VERSION_MANAGEMENT.md new file mode 100644 index 0000000..5673cba --- /dev/null +++ b/VERSION_MANAGEMENT.md @@ -0,0 +1,143 @@ +# WordPress Plugin Version Management - Implementation Summary + +## Overview +Automatic version number management for WordPress plugins has been implemented. The system now detects versions from plugin files, tracks version history, and automatically bumps versions when exporting ZIP files. + +## Files Created/Modified + +### New Files +1. **`chat/src/utils/versionManager.js`** - Core version management utilities +2. **`chat/src/utils/versionManager.test.js`** - Unit tests for version manager + +### Modified Files +1. **`chat/server.js`** + - Added version tracking to session object (lines ~7641-7658) + - Added version manager import (line ~20) + - Modified `handleExportZip()` to detect and bump versions (lines ~16944-17138) + - Modified `handleUploadApp()` to detect version from imported plugins (lines ~16837-16889) + - Updated `serializeSession()` to include version fields (lines ~7463-7481) + +## Features Implemented + +### 1. Automatic Version Detection +- Detects version from WordPress plugin headers (`Version: X.X.X`) +- Detects version from PHP constants (`define('CONSTANT_NAME', 'X.X.X')`) +- Works with imported plugins of any version number + +### 2. Version Bumping +- Automatically bumps patch version on export (1.0.0 → 1.0.1) +- Supports major, minor, patch, and keep bump types +- Tracks bump history in session + +### 3. Imported Plugin Support +- Detects existing version from uploaded ZIP files +- Preserves original version number on import +- Maintains version continuity for imported plugins + +### 4. Version History Tracking +```javascript +session.pluginVersionHistory = [ + { + version: '1.0.1', + previousVersion: '1.0.0', + bumpType: 'patch', + timestamp: '2026-02-11T10:30:00.000Z', + fileUpdated: 'my-plugin/my-plugin.php' + } +] +``` + +### 5. Session Data +```javascript +session.pluginVersion = '1.2.3'; // Current version +session.lastVersionBumpType = 'patch'; // Last bump type +session.pluginVersionHistory = []; // Full history +``` + +## How It Works + +### Export Flow +1. User clicks "Export ZIP" +2. System finds main plugin PHP file +3. Extracts current version (from session or file) +4. Bumps version number (patch by default) +5. Updates version in plugin file content +6. Adds updated file to ZIP archive +7. Tracks version change in session history + +### Import Flow +1. User uploads ZIP file +2. System extracts files to workspace +3. Searches for main plugin file +4. Extracts version from plugin header/constants +5. Stores detected version in session +6. Records import event in version history + +## Version Formats Supported + +### Plugin Headers +```php +/** + * Plugin Name: My Plugin + * Version: 1.2.3 + */ +``` + +### PHP Constants +```php +define('MY_PLUGIN_VERSION', '1.2.3'); +define( "PLUGIN_VERSION", "2.0.0" ); +``` + +### PHP Const Assignments +```php +const PLUGIN_VERSION = '1.2.3'; +``` + +## API Response Changes + +Sessions now include version fields: +```json +{ + "session": { + "id": "...", + "pluginVersion": "1.2.4", + "pluginVersionHistory": [...], + "lastVersionBumpType": "patch" + } +} +``` + +## Future Enhancements (Optional) + +1. **UI Controls**: Add version bump selector in export UI (major/minor/patch/custom) +2. **Changelog Generation**: Auto-generate changelog entries with AI +3. **Version Strategy**: Let AI analyze changes and suggest appropriate bump type +4. **Multi-file Updates**: Update version in all plugin files, not just main file +5. **readme.txt Support**: Update version in WordPress.org readme.txt format + +## Testing + +Run unit tests: +```bash +cd chat/src/utils +node versionManager.test.js +``` + +All tests pass: +- ✓ parseVersion +- ✓ formatVersion +- ✓ bumpVersion +- ✓ extractHeaderVersion +- ✓ extractDefineVersion +- ✓ extractAllVersions +- ✓ updateVersionInContent +- ✓ isWordPressPluginFile +- ✓ getPluginSlugFromPath + +## Backwards Compatibility + +- Plugins without version detection start at 1.0.0 +- Existing sessions without version data will detect from file on first export +- Import works with any plugin structure or version format +- Graceful fallback if version detection fails diff --git a/chat/public/apps.html b/chat/public/apps.html index 91e1d8d..5dae2ce 100644 --- a/chat/public/apps.html +++ b/chat/public/apps.html @@ -2428,6 +2428,13 @@ } }); + window.addEventListener('scroll', () => { + if (modal.style.display !== 'none') { + const config = tourSteps[currentStep - 1]; + positionTourBox(config.target); + } + }, true); + if (typeof document !== 'undefined') { document.addEventListener('keydown', function(e) { const onboardingModal = document.getElementById('onboarding-modal'); diff --git a/chat/public/builder.html b/chat/public/builder.html index 82a0e94..9ebfff5 100644 --- a/chat/public/builder.html +++ b/chat/public/builder.html @@ -81,6 +81,16 @@ align-items: center; gap: 8px; margin-bottom: 12px; + position: sticky; + top: 0; + z-index: 1000; + background: rgba(247, 249, 251, 0.8); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + padding: 10px 16px; + width: 100%; + box-sizing: border-box; + border-bottom: 1px solid rgba(0, 0, 0, 0.05); } .header-left { @@ -876,17 +886,7 @@ @media (max-width: 640px) { .top-left-actions { - position: sticky; - top: 0; - z-index: 1000; - background: rgba(247, 249, 251, 0.8); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); - padding: 10px 16px; margin-bottom: 0; - width: 100%; - box-sizing: border-box; - border-bottom: 1px solid rgba(0, 0, 0, 0.05); } .app-shell.builder-single { diff --git a/chat/public/builder.js b/chat/public/builder.js index 92971f2..92540cc 100644 --- a/chat/public/builder.js +++ b/chat/public/builder.js @@ -1854,8 +1854,6 @@ function showLoadingIndicator(type) { if (type === 'planning') { detailText.textContent = 'Analyzing your request and creating a development plan...'; - } else { - detailText.textContent = 'Building your plugin with AI-generated code...'; } // Add animation keyframes if not already added diff --git a/chat/public/faq.html b/chat/public/faq.html index 878c619..6b1f92f 100644 --- a/chat/public/faq.html +++ b/chat/public/faq.html @@ -233,12 +233,6 @@ Frequently Asked Questions -
- - Start Building Free - -
diff --git a/chat/public/home.html b/chat/public/home.html index 4c38f04..2bda7db 100644 --- a/chat/public/home.html +++ b/chat/public/home.html @@ -264,8 +264,8 @@
-
-
+
+
-
Building features...
+
Building features...
@@ -341,8 +341,8 @@
-
-
+
+
Builder animation preview
diff --git a/chat/server.js b/chat/server.js index 87c92c8..96b7044 100644 --- a/chat/server.js +++ b/chat/server.js @@ -17,6 +17,7 @@ const PDFDocument = require('pdfkit'); const security = require('./security'); const { createExternalWpTester, getExternalTestingConfig } = require('./external-wp-testing'); const blogSystem = require('./blog-system'); +const versionManager = require('./src/utils/versionManager'); let sharp = null; try { @@ -7476,6 +7477,9 @@ function serializeSession(session) { planSummary: session.planSummary, planUserRequest: session.planUserRequest, planApproved: !!session.planApproved, + pluginVersion: session.pluginVersion, + pluginVersionHistory: session.pluginVersionHistory || [], + lastVersionBumpType: session.lastVersionBumpType, }; } @@ -7654,7 +7658,11 @@ async function createSession(payload = {}, userId, appId) { createdAt: now, updatedAt: now, messages: [], - pending: 0 + pending: 0, + // Version tracking for WordPress plugins + pluginVersion: null, // Will be detected from plugin file on first export + pluginVersionHistory: [], // Track version changes over time + lastVersionBumpType: null // 'major', 'minor', 'patch', or 'keep' }; // WordPress identifies plugins by their folder + main file (plugin basename). @@ -16829,6 +16837,55 @@ async function handleUploadApp(req, res, userId) { const files = await extractZipToWorkspace(zipBuffer, session.workspaceDir); session.planSummary = session.planSummary || 'Imported from ZIP upload'; session.planUserRequest = session.planUserRequest || displayName; + + // Try to detect and preserve version from imported plugin + try { + // Collect PHP files to find main plugin file + const validFiles = []; + await collectValidFiles(session.workspaceDir, session.workspaceDir, validFiles, [ + 'node_modules', + '.git', + '.data', + 'uploads', + '*.log', + '*.zip' + ]); + + // Find main plugin file and extract version + for (const fileInfo of validFiles) { + if (fileInfo.fullPath.endsWith('.php')) { + try { + const content = await fs.readFile(fileInfo.fullPath, 'utf-8'); + if (content.includes('Plugin Name:')) { + const versions = versionManager.extractAllVersions(content); + if (versions.detectedVersion) { + session.pluginVersion = versions.detectedVersion; + session.pluginVersionHistory = [{ + version: versions.detectedVersion, + previousVersion: null, + bumpType: 'import', + timestamp: new Date().toISOString(), + fileUpdated: fileInfo.relativePath, + note: 'Imported from uploaded ZIP' + }]; + log('Detected version from imported plugin', { + sessionId: session.id, + version: versions.detectedVersion, + file: fileInfo.relativePath + }); + break; + } + } + } catch (err) { + // Continue to next file + } + } + } + } catch (versionError) { + log('Version detection failed during import', { error: String(versionError) }); + // Don't fail the import if version detection fails + } + await persistState(); return sendJson(res, 201, { session: serializeSession(session), files }); } catch (error) { @@ -16956,6 +17013,78 @@ async function handleExportZip(_req, res, sessionId, userId) { } } + // Version management for WordPress plugins + let versionUpdatedContent = null; + let oldVersion = null; + let newVersion = null; + + if (mainPluginFile && pluginContent) { + try { + // Extract current version from plugin file + const versions = versionManager.extractAllVersions(pluginContent); + const detectedVersion = versions.detectedVersion; + + // Determine which version to use: + // 1. Session version if available and valid + // 2. Detected version from file + // 3. Default to 1.0.0 if nothing found + let currentVersion = session.pluginVersion || detectedVersion || '1.0.0'; + + // If we detected a version from file but session doesn't have one, use detected + if (detectedVersion && !session.pluginVersion) { + currentVersion = detectedVersion; + log('Detected version from imported plugin file', { + file: mainPluginFile.relativePath, + version: detectedVersion + }); + } + + // Bump the version (default to patch bump) + // Note: In the future, this could be configurable via query parameter + newVersion = versionManager.bumpVersion(currentVersion, 'patch'); + + if (newVersion && newVersion !== currentVersion) { + // Update the plugin content with new version + versionUpdatedContent = versionManager.updateVersionInContent( + pluginContent, + currentVersion, + newVersion + ); + + if (versionUpdatedContent) { + oldVersion = currentVersion; + + // Update session tracking + session.pluginVersion = newVersion; + session.lastVersionBumpType = 'patch'; + session.pluginVersionHistory.push({ + version: newVersion, + previousVersion: currentVersion, + bumpType: 'patch', + timestamp: new Date().toISOString(), + fileUpdated: mainPluginFile.relativePath + }); + + log('Bumped plugin version', { + from: currentVersion, + to: newVersion, + file: mainPluginFile.relativePath, + sessionId: session.id + }); + } + } else { + // Keep current version if bump failed + session.pluginVersion = currentVersion; + } + } catch (versionError) { + log('Version detection/update failed, continuing with original files', { + error: String(versionError), + file: mainPluginFile?.relativePath + }); + // Don't fail the export if version management fails + } + } + // Determine the plugin folder name // WordPress identifies plugins by folder name + main file name let pluginFolderName = session.pluginSlug; @@ -16977,7 +17106,7 @@ async function handleExportZip(_req, res, sessionId, userId) { log('Plugin folder will be normalized', { file: mainPluginFile.relativePath, existingFolder, pluginSlug: pluginFolderName }); } } - + // Determine the optimal root to avoid nested wrappers const optimalRoot = findOptimalExportRoot(validFiles, session.workspaceDir); @@ -17019,6 +17148,9 @@ async function handleExportZip(_req, res, sessionId, userId) { for (const fileInfo of validFiles) { const relativePath = path.relative(optimalRoot, fileInfo.fullPath); + // Check if this is the main plugin file with updated content + const isMainPluginFile = mainPluginFile && fileInfo.fullPath === mainPluginFile.fullPath; + if (wrapInPluginFolder) { let archivePath; @@ -17036,9 +17168,19 @@ async function handleExportZip(_req, res, sessionId, userId) { archivePath = path.join(wrapInPluginFolder, relativePath); } - archive.file(fileInfo.fullPath, { name: archivePath }); + // If this is the main plugin file and we have updated content, use it + if (isMainPluginFile && versionUpdatedContent) { + archive.append(Buffer.from(versionUpdatedContent, 'utf-8'), { name: archivePath }); + } else { + archive.file(fileInfo.fullPath, { name: archivePath }); + } } else { - archive.file(fileInfo.fullPath, { name: relativePath }); + // If this is the main plugin file and we have updated content, use it + if (isMainPluginFile && versionUpdatedContent) { + archive.append(Buffer.from(versionUpdatedContent, 'utf-8'), { name: relativePath }); + } else { + archive.file(fileInfo.fullPath, { name: relativePath }); + } } } @@ -17075,7 +17217,13 @@ async function handleExportZip(_req, res, sessionId, userId) { 'Content-Length': zipContent.length }); res.end(zipContent); - log('Export completed successfully', { filename: zipFilename, size: zipContent.length, fileCount }); + log('Export completed successfully', { + filename: zipFilename, + size: zipContent.length, + fileCount, + version: newVersion || session.pluginVersion || 'unknown', + previousVersion: oldVersion || 'initial' + }); } catch (error) { if (error.message && error.message.includes('size exceeds')) { return sendJson(res, 400, { error: error.message }); diff --git a/chat/src/utils/versionManager.js b/chat/src/utils/versionManager.js new file mode 100644 index 0000000..9dfd783 --- /dev/null +++ b/chat/src/utils/versionManager.js @@ -0,0 +1,309 @@ +/** + * Version Manager Utility + * Handles WordPress plugin version parsing, bumping, and detection + */ + +const path = require('path'); +const fs = require('fs').promises; + +/** + * Valid semantic version regex (X.Y.Z format) + */ +const VERSION_REGEX = /^\d+\.\d+\.\d+$/; + +/** + * WordPress plugin header version regex + * Matches: Version: 1.2.3 or Version:1.2.3 + */ +const WP_HEADER_VERSION_REGEX = /^\s*\*?\s*Version:\s*(\d+\.\d+\.\d+)/im; + +/** + * PHP define version regex + * Matches: define('CONSTANT_NAME', '1.2.3') or define( "CONSTANT_NAME", "1.2.3" ) + */ +const PHP_DEFINE_VERSION_REGEX = /define\s*\(\s*['"][A-Z_]*VERSION['"]\s*,\s*['"](\d+\.\d+\.\d+)['"]\s*\)/gi; + +/** + * PHP constant assignment regex (alternative format) + * Matches: const VERSION = '1.2.3'; + */ +const PHP_CONST_VERSION_REGEX = /const\s+[A-Z_]*VERSION\s*=\s*['"](\d+\.\d+\.\d+)['"]/gi; + +/** + * Parse a version string into components + * @param {string} version - Version string (e.g., "1.2.3") + * @returns {object|null} - { major, minor, patch } or null if invalid + */ +function parseVersion(version) { + if (!version || typeof version !== 'string') return null; + + const cleanVersion = version.trim(); + if (!VERSION_REGEX.test(cleanVersion)) return null; + + const [major, minor, patch] = cleanVersion.split('.').map(Number); + return { major, minor, patch, raw: cleanVersion }; +} + +/** + * Format version components into a string + * @param {number} major + * @param {number} minor + * @param {number} patch + * @returns {string} - Formatted version (e.g., "1.2.3") + */ +function formatVersion(major, minor, patch) { + return `${major}.${minor}.${patch}`; +} + +/** + * Bump a version number + * @param {string} currentVersion - Current version (e.g., "1.2.3") + * @param {string} bumpType - 'major', 'minor', 'patch', or 'keep' + * @returns {string|null} - New version or null if invalid input + */ +function bumpVersion(currentVersion, bumpType = 'patch') { + const parsed = parseVersion(currentVersion); + if (!parsed) return null; + + const { major, minor, patch } = parsed; + + switch (bumpType.toLowerCase()) { + case 'major': + return formatVersion(major + 1, 0, 0); + case 'minor': + return formatVersion(major, minor + 1, 0); + case 'patch': + return formatVersion(major, minor, patch + 1); + case 'keep': + return currentVersion; + default: + // Default to patch bump + return formatVersion(major, minor, patch + 1); + } +} + +/** + * Extract version from WordPress plugin header + * @param {string} content - PHP file content + * @returns {string|null} - Version string or null + */ +function extractHeaderVersion(content) { + if (!content) return null; + const match = content.match(WP_HEADER_VERSION_REGEX); + return match ? match[1] : null; +} + +/** + * Extract version from PHP define() constants + * @param {string} content - PHP file content + * @returns {string|null} - Version string or null + */ +function extractDefineVersion(content) { + if (!content) return null; + // Use non-global version of regex to properly capture groups + const regex = /define\s*\(\s*['"][A-Z_]*VERSION['"]\s*,\s*['"](\d+\.\d+\.\d+)['"]\s*\)/i; + const match = content.match(regex); + return match ? match[1] : null; +} + +/** + * Extract version from PHP const assignments + * @param {string} content - PHP file content + * @returns {string|null} - Version string or null + */ +function extractConstVersion(content) { + if (!content) return null; + const regex = new RegExp(PHP_CONST_VERSION_REGEX); + const match = regex.exec(content); + return match ? match[1] : null; +} + +/** + * Find the main plugin file in a directory + * Searches for PHP files with WordPress plugin headers + * @param {string} dirPath - Directory to search + * @param {Array} validFiles - Optional array of file objects from collectValidFiles + * @returns {Promise} - { fullPath, relativePath, content } or null + */ +async function findMainPluginFile(dirPath, validFiles = null) { + // If validFiles not provided, collect them + if (!validFiles) { + validFiles = []; + await collectPhpFiles(dirPath, dirPath, validFiles); + } + + // Sort by likelihood of being main file (root level first, shorter names preferred) + const phpFiles = validFiles + .filter(f => f.fullPath.endsWith('.php')) + .sort((a, b) => { + const aDepth = a.relativePath.split(path.sep).length; + const bDepth = b.relativePath.split(path.sep).length; + if (aDepth !== bDepth) return aDepth - bDepth; + return a.relativePath.length - b.relativePath.length; + }); + + for (const fileInfo of phpFiles) { + try { + const content = await fs.readFile(fileInfo.fullPath, 'utf-8'); + // Check for WordPress plugin header + if (content.includes('Plugin Name:')) { + return { + fullPath: fileInfo.fullPath, + relativePath: fileInfo.relativePath, + content + }; + } + } catch (err) { + // Continue to next file + } + } + + return null; +} + +/** + * Helper to collect PHP files recursively + */ +async function collectPhpFiles(rootDir, currentDir, files) { + try { + const entries = await fs.readdir(currentDir, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(currentDir, entry.name); + const relativePath = path.relative(rootDir, fullPath); + + if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') { + await collectPhpFiles(rootDir, fullPath, files); + } else if (entry.isFile() && entry.name.endsWith('.php')) { + files.push({ fullPath, relativePath }); + } + } + } catch (err) { + // Ignore errors + } +} + +/** + * Extract all version information from a plugin file + * @param {string} content - PHP file content + * @returns {object} - { headerVersion, defineVersion, constVersion, detectedVersion } + */ +function extractAllVersions(content) { + const headerVersion = extractHeaderVersion(content); + const defineVersion = extractDefineVersion(content); + const constVersion = extractConstVersion(content); + + // Use first non-null version found (priority: header > define > const) + const detectedVersion = headerVersion || defineVersion || constVersion; + + return { + headerVersion, + defineVersion, + constVersion, + detectedVersion + }; +} + +/** + * Update version in plugin file content + * @param {string} content - Original file content + * @param {string} oldVersion - Current version + * @param {string} newVersion - New version to set + * @returns {string|null} - Updated content or null if no changes needed + */ +function updateVersionInContent(content, oldVersion, newVersion) { + if (!content || !oldVersion || !newVersion || oldVersion === newVersion) { + return null; + } + + let updated = content; + let changesMade = false; + + // Update WordPress plugin header version + const headerRegex = new RegExp( + `^(\\s*\\*?\\s*Version:)\\s*${oldVersion.replace(/\./g, '\\.')}`, + 'gim' + ); + if (headerRegex.test(updated)) { + updated = updated.replace(headerRegex, `$1 ${newVersion}`); + changesMade = true; + } + + // Update PHP define() version constants + const defineRegex = new RegExp( + `(define\\s*\\(\\s*['"][A-Z_]*VERSION['"]\\s*,\\s*['"])${oldVersion.replace(/\./g, '\\.')}(['"]\\s*\\))`, + 'gi' + ); + if (defineRegex.test(updated)) { + updated = updated.replace(defineRegex, `$1${newVersion}$2`); + changesMade = true; + } + + // Update PHP const version assignments + const constRegex = new RegExp( + `(const\\s+[A-Z_]*VERSION\\s*=\\s*['"])${oldVersion.replace(/\./g, '\\.')}(['"])`, + 'gi' + ); + if (constRegex.test(updated)) { + updated = updated.replace(constRegex, `$1${newVersion}$2`); + changesMade = true; + } + + return changesMade ? updated : null; +} + +/** + * Detect if a file is a WordPress plugin file + * @param {string} content - File content + * @returns {boolean} + */ +function isWordPressPluginFile(content) { + if (!content) return false; + return content.includes('Plugin Name:') && content.includes('Version:'); +} + +/** + * Get the plugin slug from folder name or file name + * @param {string} filePath - Path to main plugin file + * @returns {string} + */ +function getPluginSlugFromPath(filePath) { + const parsed = path.parse(filePath); + const parentDir = path.basename(parsed.dir); + const fileName = parsed.name; + + // If parent directory name matches file name (typical WordPress plugin structure: my-plugin/my-plugin.php) + // or if there's a meaningful parent directory name (not generic like 'to', 'path', 'src') + // use the directory name, otherwise use the file name + const genericDirNames = ['.', '..', 'src', 'lib', 'includes', 'dist', 'build', 'to', 'path']; + + if (parentDir === fileName) { + // Perfect match: folder/my-plugin/my-plugin.php + return parentDir; + } else if (!genericDirNames.includes(parentDir.toLowerCase())) { + // Parent directory has a meaningful name: folder/my-custom-plugin/plugin.php + return parentDir; + } else { + // Use file name as fallback: folder/plugin-name.php + return fileName; + } +} + +module.exports = { + parseVersion, + formatVersion, + bumpVersion, + extractHeaderVersion, + extractDefineVersion, + extractConstVersion, + extractAllVersions, + findMainPluginFile, + updateVersionInContent, + isWordPressPluginFile, + getPluginSlugFromPath, + // Regex exports for testing + VERSION_REGEX, + WP_HEADER_VERSION_REGEX, + PHP_DEFINE_VERSION_REGEX, + PHP_CONST_VERSION_REGEX +}; diff --git a/chat/src/utils/versionManager.test.js b/chat/src/utils/versionManager.test.js new file mode 100644 index 0000000..fcdc8d5 --- /dev/null +++ b/chat/src/utils/versionManager.test.js @@ -0,0 +1,115 @@ +/** + * Version Manager Tests + * Run with: node versionManager.test.js + */ + +const versionManager = require('./versionManager'); +const assert = require('assert'); + +console.log('Running version manager tests...\n'); + +// Test parseVersion +console.log('Test 1: parseVersion'); +assert.deepStrictEqual(versionManager.parseVersion('1.2.3'), { major: 1, minor: 2, patch: 3, raw: '1.2.3' }); +assert.strictEqual(versionManager.parseVersion('invalid'), null); +assert.strictEqual(versionManager.parseVersion('1.2'), null); +assert.strictEqual(versionManager.parseVersion(''), null); +console.log('✓ parseVersion tests passed\n'); + +// Test formatVersion +console.log('Test 2: formatVersion'); +assert.strictEqual(versionManager.formatVersion(1, 2, 3), '1.2.3'); +assert.strictEqual(versionManager.formatVersion(10, 0, 0), '10.0.0'); +console.log('✓ formatVersion tests passed\n'); + +// Test bumpVersion +console.log('Test 3: bumpVersion'); +assert.strictEqual(versionManager.bumpVersion('1.2.3', 'patch'), '1.2.4'); +assert.strictEqual(versionManager.bumpVersion('1.2.3', 'minor'), '1.3.0'); +assert.strictEqual(versionManager.bumpVersion('1.2.3', 'major'), '2.0.0'); +assert.strictEqual(versionManager.bumpVersion('1.2.3', 'keep'), '1.2.3'); +assert.strictEqual(versionManager.bumpVersion('1.2.3', 'unknown'), '1.2.4'); // defaults to patch +assert.strictEqual(versionManager.bumpVersion('invalid', 'patch'), null); +console.log('✓ bumpVersion tests passed\n'); + +// Test extractHeaderVersion +console.log('Test 4: extractHeaderVersion'); +const headerContent = `