#!/usr/bin/env node /** * sync-versions.js — Sync version constants across all source files * * Reads the canonical values from src/data/versions.js and updates * every hardcoded occurrence in JS, JSON, MD, and CJS files. * * Usage: * node scripts/sync-versions.js # dry-run (preview changes) * node scripts/sync-versions.js --apply # apply changes * * Safe to run multiple times — idempotent. */ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const ROOT = path.resolve(__dirname, '..'); // Import canonical versions const { VL_VERSION, THEME_VERSION, THEME_VERSION_FULL, STYLESPACE_VERSION, THEME_TAG, THEME_PROFILE, WORKFLOW_VERSION, PARSEVL_URL, } = await import('../src/data/versions.js'); const dryRun = !process.argv.includes('--apply'); // ── Replacement rules ────────────────────────────────────────────── // Each rule: [pattern (regex), replacement (string)] // Patterns are designed to match the OLD format regardless of old version number. const rules = [ // VL_VERSION header in code/templates [/VL_VERSION:[\d.]+/g, `VL_VERSION:${VL_VERSION}`], // VL_SYNTAX_VERSION in md [/VL_SYNTAX_VERSION:[\d.]+/g, `VL_SYNTAX_VERSION:${VL_VERSION}`], // vlVersion: 'x.x' (JS object literal) [/vlVersion:\s*'[\d.]+'/g, `vlVersion: '${VL_VERSION}'`], // "vlVersion": "x.x" (JSON) [/"vlVersion":\s*"[\d.]+"/g, `"vlVersion": "${VL_VERSION}"`], // "version": "x.x" in vl-syntax-meta.json only (too generic for global) // handled via file-specific rule below // VL x.x syntax references in prompts/comments [/VL [\d.]+ syntax/g, `VL ${VL_VERSION} syntax`], [/VL [\d.]+ Syntax/g, `VL ${VL_VERSION} Syntax`], [/VL \(Visual Logic\) [\d.]+/g, `VL (Visual Logic) ${VL_VERSION}`], [/VL \(Visual Language\) v[\d.]+/g, `VL (Visual Language) v${VL_VERSION}`], // VL Language Complete Reference (vX.X) [/VL Language Complete Reference \(v[\d.]+\)/g, `VL Language Complete Reference (v${VL_VERSION})`], // "Follow VL x.x syntax strictly" [/Follow VL [\d.]+ syntax/g, `Follow VL ${VL_VERSION} syntax`], [/following VL [\d.]+ syntax/g, `following VL ${VL_VERSION} syntax`], // "VL x.x" standalone in comments (e.g. "VL 3.5 injects", "VL 3.5 PREFERRED") // Only match "VL X.X" followed by common suffixes to avoid false positives [/VL [\d.]+( injects| PREFERRED| primary| data-vl-id| live| IDs| runtime)/g, `VL ${VL_VERSION}$1`], // Theme-Enterprise-X.X tag [/Theme-Enterprise-[\d.]+/g, `Theme-Enterprise-${THEME_VERSION}`], // Theme version in theme content: version:"X.X.X" [/version:"[\d.]+"\n/g, `version:"${THEME_VERSION_FULL}"\n`], // styleSpaceVersion:"X.X" [/styleSpaceVersion:"[\d.]+"/g, `styleSpaceVersion:"${STYLESPACE_VERSION}"`], // Enterprise Theme X.X in comments [/Enterprise Theme [\d.]+/g, `Enterprise Theme ${THEME_VERSION}`], // Enterprise theme vX.X in doc tables [/Enterprise theme v[\d.]+/g, `Enterprise theme v${THEME_VERSION}`], // THEME_X.X.vth in doc tables [/THEME_[\d.]+\.vth/g, `THEME_${THEME_VERSION}.vth`], // StyleSpace X.X references [/StyleSpace [\d.]+/g, `StyleSpace ${STYLESPACE_VERSION}`], // VL x.x Syntax Rules (in workflow JSON docs) [/"VL [\d.]+ Syntax Rules"/g, `"VL ${VL_VERSION} Syntax Rules"`], // "for VL x.x syntax compliance" [/for VL [\d.]+ syntax compliance/g, `for VL ${VL_VERSION} syntax compliance`], // version: 'x.x' in meta.json context (type: 'vl-project') [/version: '[\d.]+',\n(\s+type: 'vl-project')/g, `version: '${VL_VERSION}',\n$1`], // ── VL Workflow spec version (3.15 → 3.16, etc.) ── // Matches workflow-builder.js, autotest-pipeline.js: version: '3.xx' [/version:\s*'3\.\d{2}'/g, `version: '${WORKFLOW_VERSION}'`], // Matches orchestrator.js template strings: version "3.xx" [/version "3\.\d{2}"/g, `version "${WORKFLOW_VERSION}"`], // Matches workflow-run.js: spec: 'v3.xx' [/spec:\s*'v3\.\d{2}'/g, `spec: 'v${WORKFLOW_VERSION}'`], // Matches AGENTS.md / docs: VL Workflow 3.xx [/VL Workflow [\d.]+/g, `VL Workflow ${WORKFLOW_VERSION}`], // Matches short-form vX.xx spec references in comments/strings [/\(VL Workflow spec v[\d.]+\)/g, `(VL Workflow spec v${WORKFLOW_VERSION})`], // ── VL version in short form (app banners, package.json description) ── [/\bVL v[\d.]+/g, `VL v${VL_VERSION}`], ]; // ── File-specific rules ──────────────────────────────────────────── const fileRules = { 'src/data/vl-syntax-meta.json': [ [/"version":\s*"[\d.]+"/g, `"version": "${VL_VERSION}"`], ], }; // ── Scan targets ─────────────────────────────────────────────────── const SCAN_DIRS = ['src', 'public', '.vl-code/workflows']; const ROOT_FILES = ['AGENTS.md', 'MODULE-SPEC.md', 'package.json', 'CLAUDE.md']; const EXTENSIONS = new Set(['.js', '.cjs', '.mjs', '.json', '.md', '.html']); const SKIP = new Set(['node_modules', 'dist', '.git', 'versions.js']); function walk(dir) { const results = []; for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { if (SKIP.has(entry.name)) continue; const full = path.join(dir, entry.name); if (entry.isDirectory()) { results.push(...walk(full)); } else if (EXTENSIONS.has(path.extname(entry.name))) { results.push(full); } } return results; } // ── Main ─────────────────────────────────────────────────────────── console.log(`\n📐 VL-Code Version Sync`); console.log(` VL_VERSION: ${VL_VERSION}`); console.log(` THEME_VERSION: ${THEME_VERSION} (${THEME_VERSION_FULL})`); console.log(` STYLESPACE: ${STYLESPACE_VERSION}`); console.log(` THEME_TAG: ${THEME_TAG}`); console.log(` Mode: ${dryRun ? 'DRY RUN (use --apply to write)' : 'APPLYING'}\n`); let totalFiles = 0; let changedFiles = 0; let totalReplacements = 0; const files = [ ...SCAN_DIRS.flatMap(d => walk(path.join(ROOT, d))), ...ROOT_FILES.map(f => path.join(ROOT, f)).filter(f => fs.existsSync(f)), ]; for (const filePath of files) { const relPath = path.relative(ROOT, filePath); const original = fs.readFileSync(filePath, 'utf-8'); let content = original; let fileChanges = 0; // Apply global rules for (const [pattern, replacement] of rules) { const before = content; content = content.replace(pattern, replacement); if (content !== before) { const count = (before.match(pattern) || []).length; fileChanges += count; } } // Apply file-specific rules const extras = fileRules[relPath]; if (extras) { for (const [pattern, replacement] of extras) { const before = content; content = content.replace(pattern, replacement); if (content !== before) { const count = (before.match(pattern) || []).length; fileChanges += count; } } } totalFiles++; if (content !== original) { changedFiles++; totalReplacements += fileChanges; console.log(` ✏️ ${relPath} (${fileChanges} replacements)`); if (!dryRun) { fs.writeFileSync(filePath, content, 'utf-8'); } } } console.log(`\n${dryRun ? '🔍 Preview' : '✅ Done'}: ${changedFiles}/${totalFiles} files, ${totalReplacements} replacements\n`); if (dryRun && changedFiles > 0) { console.log('Run with --apply to write changes:\n node scripts/sync-versions.js --apply\n'); }