Monorepo for Aesthetic.Computer aesthetic.computer
at main 153 lines 3.4 kB view raw
1#!/usr/bin/env node 2import { existsSync, readFileSync } from "node:fs"; 3import path from "node:path"; 4 5const targetDir = process.argv[2]; 6 7if (!targetDir) { 8 process.exit(0); 9} 10 11const pkgPath = path.join(targetDir, "package.json"); 12if (!existsSync(pkgPath)) { 13 process.exit(0); 14} 15 16const nodeModulesPath = path.join(targetDir, "node_modules"); 17if (!existsSync(nodeModulesPath)) { 18 console.log( 19 JSON.stringify( 20 { 21 dir: targetDir, 22 mismatches: [ 23 { 24 name: "*", 25 expected: "package.json", 26 actual: null, 27 reason: "node_modules-missing", 28 }, 29 ], 30 }, 31 null, 32 2, 33 ), 34 ); 35 process.exit(1); 36} 37 38const pkg = JSON.parse(readFileSync(pkgPath, "utf8")); 39const deps = { 40 ...(pkg.dependencies || {}), 41 ...(pkg.devDependencies || {}), 42}; 43 44if (Object.keys(deps).length === 0) { 45 process.exit(0); 46} 47 48const parseVersion = (version) => { 49 return (version || "0.0.0") 50 .replace(/^v/, "") 51 .split(".") 52 .map((part) => { 53 const numeric = parseInt(part, 10); 54 return Number.isFinite(numeric) ? numeric : 0; 55 }); 56}; 57 58const compareVersions = (a, b) => { 59 for (let i = 0; i < Math.max(a.length, b.length); i++) { 60 const aVal = a[i] || 0; 61 const bVal = b[i] || 0; 62 if (aVal > bVal) return 1; 63 if (aVal < bVal) return -1; 64 } 65 return 0; 66}; 67 68const satisfies = (installed, expected) => { 69 if (!expected || expected === "latest") return true; 70 if (!installed) return false; 71 72 const normalizedExpected = expected.trim(); 73 const normalizedInstalled = installed.trim(); 74 75 if (normalizedExpected === normalizedInstalled) { 76 return true; 77 } 78 79 const cleanExpected = normalizedExpected.replace(/^[~^=]/, ""); 80 81 if (/^[~^]/.test(normalizedExpected)) { 82 const expectedParts = parseVersion(cleanExpected); 83 const installedParts = parseVersion(normalizedInstalled); 84 85 if (normalizedExpected.startsWith("^")) { 86 if (installedParts[0] !== expectedParts[0]) { 87 return false; 88 } 89 return compareVersions(installedParts, expectedParts) >= 0; 90 } 91 92 if (normalizedExpected.startsWith("~")) { 93 if (installedParts[0] !== expectedParts[0]) { 94 return false; 95 } 96 if (installedParts[1] !== expectedParts[1]) { 97 return false; 98 } 99 return compareVersions(installedParts, expectedParts) >= 0; 100 } 101 } 102 103 if (/^[<>*]|\|\|/.test(normalizedExpected)) { 104 // Complex ranges are hard to evaluate without semver; assume satisfied to avoid false alarms 105 return true; 106 } 107 108 return normalizedInstalled === cleanExpected; 109}; 110 111const mismatches = []; 112 113for (const [name, expected] of Object.entries(deps)) { 114 const dependencyPkgPath = path.join(nodeModulesPath, name, "package.json"); 115 116 if (!existsSync(dependencyPkgPath)) { 117 mismatches.push({ 118 name, 119 expected, 120 actual: null, 121 reason: "missing", 122 }); 123 continue; 124 } 125 126 const dependencyPkg = JSON.parse(readFileSync(dependencyPkgPath, "utf8")); 127 const installed = dependencyPkg.version || null; 128 129 if (!satisfies(installed, expected)) { 130 mismatches.push({ 131 name, 132 expected, 133 actual: installed, 134 reason: "version-mismatch", 135 }); 136 } 137} 138 139if (mismatches.length > 0) { 140 console.log( 141 JSON.stringify( 142 { 143 dir: targetDir, 144 mismatches, 145 }, 146 null, 147 2, 148 ), 149 ); 150 process.exit(1); 151} 152 153process.exit(0);