Monorepo for Aesthetic.Computer
aesthetic.computer
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);