sync-versions.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. #!/usr/bin/env node
  2. /**
  3. * sync-versions.js — Sync version constants across all source files
  4. *
  5. * Reads the canonical values from src/data/versions.js and updates
  6. * every hardcoded occurrence in JS, JSON, MD, and CJS files.
  7. *
  8. * Usage:
  9. * node scripts/sync-versions.js # dry-run (preview changes)
  10. * node scripts/sync-versions.js --apply # apply changes
  11. *
  12. * Safe to run multiple times — idempotent.
  13. */
  14. import fs from 'fs';
  15. import path from 'path';
  16. import { fileURLToPath } from 'url';
  17. const __dirname = path.dirname(fileURLToPath(import.meta.url));
  18. const ROOT = path.resolve(__dirname, '..');
  19. // Import canonical versions
  20. const {
  21. VL_VERSION,
  22. THEME_VERSION,
  23. THEME_VERSION_FULL,
  24. STYLESPACE_VERSION,
  25. THEME_TAG,
  26. THEME_PROFILE,
  27. WORKFLOW_VERSION,
  28. PARSEVL_URL,
  29. } = await import('../src/data/versions.js');
  30. const dryRun = !process.argv.includes('--apply');
  31. // ── Replacement rules ──────────────────────────────────────────────
  32. // Each rule: [pattern (regex), replacement (string)]
  33. // Patterns are designed to match the OLD format regardless of old version number.
  34. const rules = [
  35. // VL_VERSION header in code/templates
  36. [/VL_VERSION:[\d.]+/g, `VL_VERSION:${VL_VERSION}`],
  37. // VL_SYNTAX_VERSION in md
  38. [/VL_SYNTAX_VERSION:[\d.]+/g, `VL_SYNTAX_VERSION:${VL_VERSION}`],
  39. // vlVersion: 'x.x' (JS object literal)
  40. [/vlVersion:\s*'[\d.]+'/g, `vlVersion: '${VL_VERSION}'`],
  41. // "vlVersion": "x.x" (JSON)
  42. [/"vlVersion":\s*"[\d.]+"/g, `"vlVersion": "${VL_VERSION}"`],
  43. // "version": "x.x" in vl-syntax-meta.json only (too generic for global)
  44. // handled via file-specific rule below
  45. // VL x.x syntax references in prompts/comments
  46. [/VL [\d.]+ syntax/g, `VL ${VL_VERSION} syntax`],
  47. [/VL [\d.]+ Syntax/g, `VL ${VL_VERSION} Syntax`],
  48. [/VL \(Visual Logic\) [\d.]+/g, `VL (Visual Logic) ${VL_VERSION}`],
  49. [/VL \(Visual Language\) v[\d.]+/g, `VL (Visual Language) v${VL_VERSION}`],
  50. // VL Language Complete Reference (vX.X)
  51. [/VL Language Complete Reference \(v[\d.]+\)/g, `VL Language Complete Reference (v${VL_VERSION})`],
  52. // "Follow VL x.x syntax strictly"
  53. [/Follow VL [\d.]+ syntax/g, `Follow VL ${VL_VERSION} syntax`],
  54. [/following VL [\d.]+ syntax/g, `following VL ${VL_VERSION} syntax`],
  55. // "VL x.x" standalone in comments (e.g. "VL 3.5 injects", "VL 3.5 PREFERRED")
  56. // Only match "VL X.X" followed by common suffixes to avoid false positives
  57. [/VL [\d.]+( injects| PREFERRED| primary| data-vl-id| live| IDs| runtime)/g,
  58. `VL ${VL_VERSION}$1`],
  59. // Theme-Enterprise-X.X tag
  60. [/Theme-Enterprise-[\d.]+/g, `Theme-Enterprise-${THEME_VERSION}`],
  61. // Theme version in theme content: version:"X.X.X"
  62. [/version:"[\d.]+"\n/g, `version:"${THEME_VERSION_FULL}"\n`],
  63. // styleSpaceVersion:"X.X"
  64. [/styleSpaceVersion:"[\d.]+"/g, `styleSpaceVersion:"${STYLESPACE_VERSION}"`],
  65. // Enterprise Theme X.X in comments
  66. [/Enterprise Theme [\d.]+/g, `Enterprise Theme ${THEME_VERSION}`],
  67. // Enterprise theme vX.X in doc tables
  68. [/Enterprise theme v[\d.]+/g, `Enterprise theme v${THEME_VERSION}`],
  69. // THEME_X.X.vth in doc tables
  70. [/THEME_[\d.]+\.vth/g, `THEME_${THEME_VERSION}.vth`],
  71. // StyleSpace X.X references
  72. [/StyleSpace [\d.]+/g, `StyleSpace ${STYLESPACE_VERSION}`],
  73. // VL x.x Syntax Rules (in workflow JSON docs)
  74. [/"VL [\d.]+ Syntax Rules"/g, `"VL ${VL_VERSION} Syntax Rules"`],
  75. // "for VL x.x syntax compliance"
  76. [/for VL [\d.]+ syntax compliance/g, `for VL ${VL_VERSION} syntax compliance`],
  77. // version: 'x.x' in meta.json context (type: 'vl-project')
  78. [/version: '[\d.]+',\n(\s+type: 'vl-project')/g, `version: '${VL_VERSION}',\n$1`],
  79. // ── VL Workflow spec version (3.15 → 3.16, etc.) ──
  80. // Matches workflow-builder.js, autotest-pipeline.js: version: '3.xx'
  81. [/version:\s*'3\.\d{2}'/g, `version: '${WORKFLOW_VERSION}'`],
  82. // Matches orchestrator.js template strings: version "3.xx"
  83. [/version "3\.\d{2}"/g, `version "${WORKFLOW_VERSION}"`],
  84. // Matches workflow-run.js: spec: 'v3.xx'
  85. [/spec:\s*'v3\.\d{2}'/g, `spec: 'v${WORKFLOW_VERSION}'`],
  86. // Matches AGENTS.md / docs: VL Workflow 3.xx
  87. [/VL Workflow [\d.]+/g, `VL Workflow ${WORKFLOW_VERSION}`],
  88. // Matches short-form vX.xx spec references in comments/strings
  89. [/\(VL Workflow spec v[\d.]+\)/g, `(VL Workflow spec v${WORKFLOW_VERSION})`],
  90. // ── VL version in short form (app banners, package.json description) ──
  91. [/\bVL v[\d.]+/g, `VL v${VL_VERSION}`],
  92. ];
  93. // ── File-specific rules ────────────────────────────────────────────
  94. const fileRules = {
  95. 'src/data/vl-syntax-meta.json': [
  96. [/"version":\s*"[\d.]+"/g, `"version": "${VL_VERSION}"`],
  97. ],
  98. };
  99. // ── Scan targets ───────────────────────────────────────────────────
  100. const SCAN_DIRS = ['src', 'public', '.vl-code/workflows'];
  101. const ROOT_FILES = ['AGENTS.md', 'MODULE-SPEC.md', 'package.json', 'CLAUDE.md'];
  102. const EXTENSIONS = new Set(['.js', '.cjs', '.mjs', '.json', '.md', '.html']);
  103. const SKIP = new Set(['node_modules', 'dist', '.git', 'versions.js']);
  104. function walk(dir) {
  105. const results = [];
  106. for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
  107. if (SKIP.has(entry.name)) continue;
  108. const full = path.join(dir, entry.name);
  109. if (entry.isDirectory()) {
  110. results.push(...walk(full));
  111. } else if (EXTENSIONS.has(path.extname(entry.name))) {
  112. results.push(full);
  113. }
  114. }
  115. return results;
  116. }
  117. // ── Main ───────────────────────────────────────────────────────────
  118. console.log(`\n📐 VL-Code Version Sync`);
  119. console.log(` VL_VERSION: ${VL_VERSION}`);
  120. console.log(` THEME_VERSION: ${THEME_VERSION} (${THEME_VERSION_FULL})`);
  121. console.log(` STYLESPACE: ${STYLESPACE_VERSION}`);
  122. console.log(` THEME_TAG: ${THEME_TAG}`);
  123. console.log(` Mode: ${dryRun ? 'DRY RUN (use --apply to write)' : 'APPLYING'}\n`);
  124. let totalFiles = 0;
  125. let changedFiles = 0;
  126. let totalReplacements = 0;
  127. const files = [
  128. ...SCAN_DIRS.flatMap(d => walk(path.join(ROOT, d))),
  129. ...ROOT_FILES.map(f => path.join(ROOT, f)).filter(f => fs.existsSync(f)),
  130. ];
  131. for (const filePath of files) {
  132. const relPath = path.relative(ROOT, filePath);
  133. const original = fs.readFileSync(filePath, 'utf-8');
  134. let content = original;
  135. let fileChanges = 0;
  136. // Apply global rules
  137. for (const [pattern, replacement] of rules) {
  138. const before = content;
  139. content = content.replace(pattern, replacement);
  140. if (content !== before) {
  141. const count = (before.match(pattern) || []).length;
  142. fileChanges += count;
  143. }
  144. }
  145. // Apply file-specific rules
  146. const extras = fileRules[relPath];
  147. if (extras) {
  148. for (const [pattern, replacement] of extras) {
  149. const before = content;
  150. content = content.replace(pattern, replacement);
  151. if (content !== before) {
  152. const count = (before.match(pattern) || []).length;
  153. fileChanges += count;
  154. }
  155. }
  156. }
  157. totalFiles++;
  158. if (content !== original) {
  159. changedFiles++;
  160. totalReplacements += fileChanges;
  161. console.log(` ✏️ ${relPath} (${fileChanges} replacements)`);
  162. if (!dryRun) {
  163. fs.writeFileSync(filePath, content, 'utf-8');
  164. }
  165. }
  166. }
  167. console.log(`\n${dryRun ? '🔍 Preview' : '✅ Done'}: ${changedFiles}/${totalFiles} files, ${totalReplacements} replacements\n`);
  168. if (dryRun && changedFiles > 0) {
  169. console.log('Run with --apply to write changes:\n node scripts/sync-versions.js --apply\n');
  170. }