1 import { readFileSync, writeFileSync } from "node:fs";
2 import { resolve } from "node:path";
3
4 const repoRoot = resolve(import.meta.dirname, "..");
5 const cliCargoTomlPath = resolve(repoRoot, "crates/cli/Cargo.toml");
6 const workspaceCargoTomlPath = resolve(repoRoot, "Cargo.toml");
7 const npmCliPackageJsonPath = resolve(repoRoot, "npm/smbcloud-cli/package.json");
8 const npmCliPackageLockPath = resolve(repoRoot, "npm/smbcloud-cli/package-lock.json");
9 const nugetCliProjectPath = resolve(repoRoot, "nuget/smbcloud-cli/SmbCloud.Cli.csproj");
10 const npmXcrsPackageJsonPath = resolve(repoRoot, "npm/xcrs/package.json");
11 const npmXcrsPackageLockPath = resolve(repoRoot, "npm/xcrs/package-lock.json");
12 const nugetXcrsProjectPath = resolve(repoRoot, "nuget/xcrs/Xcrs.csproj");
13 const sdkNpmPackageJsonPath = resolve(repoRoot, "sdk/npm/smbcloud-auth/package.json");
14 const authGemVersionPath = resolve(repoRoot, "sdk/gems/auth/lib/auth/version.rb");
15 const authGemCargoTomlPath = resolve(repoRoot, "sdk/gems/auth/ext/auth/Cargo.toml");
16 const modelGemVersionPath = resolve(repoRoot, "sdk/gems/model/lib/model/version.rb");
17 const modelGemCargoTomlPath = resolve(repoRoot, "sdk/gems/model/ext/model/Cargo.toml");
18 const emailGemVersionPath = resolve(repoRoot, "sdk/gems/email/lib/email/version.rb");
19 const emailGemCargoTomlPath = resolve(repoRoot, "sdk/gems/email/ext/email/Cargo.toml");
20 const emailGemLockPath = resolve(repoRoot, "sdk/gems/email/Gemfile.lock");
21 const mcpServerJsonPath = resolve(repoRoot, "server.json");
22 const xcrsMcpServerJsonPath = resolve(repoRoot, "server-xcrs.json");
23
24 function readText(path) {
25 return readFileSync(path, "utf8");
26 }
27
28 function writeText(path, content) {
29 writeFileSync(path, content, "utf8");
30 }
31
32 function readCargoPackageVersion(path) {
33 const match = readText(path).match(/^version\s*=\s*"([^"]+)"/m);
34
35 if (!match) {
36 throw new Error(`Unable to find version in ${path}`);
37 }
38
39 return match[1];
40 }
41
42 function replaceOrThrow(content, pattern, replacement, description) {
43 if (!pattern.test(content)) {
44 throw new Error(`Unable to update ${description}`);
45 }
46
47 return content.replace(pattern, replacement);
48 }
49
50 function updateFile(path, updater) {
51 const current = readText(path);
52 const next = updater(current);
53
54 if (next !== current) {
55 writeText(path, next);
56 return true;
57 }
58
59 return false;
60 }
61
62 const releaseVersion = readCargoPackageVersion(cliCargoTomlPath);
63 const [major = "0", minor = "0"] = releaseVersion.split(".");
64 const rubySdkRequirement = `${major}.${minor}`;
65 const updatedPaths = [];
66
67 if (
68 updateFile(workspaceCargoTomlPath, (content) =>
69 content.replace(
70 /^([\w-]+\s*=\s*\{\s*version\s*=\s*")[^"]+("\s*,\s*path\s*=\s*"crates\/[^"]+"\s*\})/gm,
71 `$1${releaseVersion}$2`,
72 ),
73 )
74 ) {
75 updatedPaths.push(workspaceCargoTomlPath);
76 }
77
78 if (
79 updateFile(npmCliPackageJsonPath, (content) => {
80 const parsed = JSON.parse(content);
81 parsed.version = releaseVersion;
82
83 for (const packageName of Object.keys(parsed.optionalDependencies ?? {})) {
84 parsed.optionalDependencies[packageName] = releaseVersion;
85 }
86
87 return `${JSON.stringify(parsed, null, 2)}\n`;
88 })
89 ) {
90 updatedPaths.push(npmCliPackageJsonPath);
91 }
92
93 if (
94 updateFile(npmCliPackageLockPath, (content) => {
95 const parsed = JSON.parse(content);
96 parsed.version = releaseVersion;
97 parsed.packages[""].version = releaseVersion;
98
99 for (const packageName of Object.keys(parsed.packages[""].optionalDependencies ?? {})) {
100 parsed.packages[""].optionalDependencies[packageName] = releaseVersion;
101 }
102
103 return `${JSON.stringify(parsed, null, 2)}\n`;
104 })
105 ) {
106 updatedPaths.push(npmCliPackageLockPath);
107 }
108
109 if (
110 updateFile(nugetCliProjectPath, (content) =>
111 replaceOrThrow(
112 content,
113 /<PackageVersion>[^<]+<\/PackageVersion>/,
114 `<PackageVersion>${releaseVersion}</PackageVersion>`,
115 `${nugetCliProjectPath} package version`,
116 ),
117 )
118 ) {
119 updatedPaths.push(nugetCliProjectPath);
120 }
121
122 if (
123 updateFile(npmXcrsPackageJsonPath, (content) => {
124 const parsed = JSON.parse(content);
125 parsed.version = releaseVersion;
126
127 for (const packageName of Object.keys(parsed.optionalDependencies ?? {})) {
128 parsed.optionalDependencies[packageName] = releaseVersion;
129 }
130
131 return `${JSON.stringify(parsed, null, 2)}\n`;
132 })
133 ) {
134 updatedPaths.push(npmXcrsPackageJsonPath);
135 }
136
137 if (
138 updateFile(npmXcrsPackageLockPath, (content) => {
139 const parsed = JSON.parse(content);
140 parsed.version = releaseVersion;
141 parsed.packages[""].version = releaseVersion;
142
143 for (const packageName of Object.keys(parsed.packages[""].optionalDependencies ?? {})) {
144 parsed.packages[""].optionalDependencies[packageName] = releaseVersion;
145 }
146
147 return `${JSON.stringify(parsed, null, 2)}\n`;
148 })
149 ) {
150 updatedPaths.push(npmXcrsPackageLockPath);
151 }
152
153 if (
154 updateFile(nugetXcrsProjectPath, (content) =>
155 replaceOrThrow(
156 content,
157 /<PackageVersion>[^<]+<\/PackageVersion>/,
158 `<PackageVersion>${releaseVersion}</PackageVersion>`,
159 `${nugetXcrsProjectPath} package version`,
160 ),
161 )
162 ) {
163 updatedPaths.push(nugetXcrsProjectPath);
164 }
165
166 if (
167 updateFile(sdkNpmPackageJsonPath, (content) => {
168 const parsed = JSON.parse(content);
169 parsed.version = releaseVersion;
170 return `${JSON.stringify(parsed, null, 2)}\n`;
171 })
172 ) {
173 updatedPaths.push(sdkNpmPackageJsonPath);
174 }
175
176 if (
177 updateFile(authGemVersionPath, (content) =>
178 replaceOrThrow(
179 content,
180 /VERSION\s*=\s*'[^']+'/,
181 `VERSION = '${releaseVersion}'`,
182 `${authGemVersionPath} VERSION constant`,
183 ),
184 )
185 ) {
186 updatedPaths.push(authGemVersionPath);
187 }
188
189 if (
190 updateFile(authGemCargoTomlPath, (content) => {
191 let next = replaceOrThrow(
192 content,
193 /^version\s*=\s*"[^"]+"/m,
194 `version = "${releaseVersion}"`,
195 `${authGemCargoTomlPath} package version`,
196 );
197
198 for (const crateName of [
199 "smbcloud-auth-sdk",
200 "smbcloud-model",
201 "smbcloud-network",
202 ]) {
203 next = replaceOrThrow(
204 next,
205 new RegExp(`(${crateName}\\s*=\\s*\\{\\s*version\\s*=\\s*")([^"]+)("\\s*\\})`),
206 `$1${rubySdkRequirement}$3`,
207 `${authGemCargoTomlPath} dependency ${crateName}`,
208 );
209 }
210
211 return next;
212 })
213 ) {
214 updatedPaths.push(authGemCargoTomlPath);
215 }
216
217 if (
218 updateFile(modelGemVersionPath, (content) =>
219 replaceOrThrow(
220 content,
221 /VERSION\s*=\s*'[^']+'/,
222 `VERSION = '${releaseVersion}'`,
223 `${modelGemVersionPath} VERSION constant`,
224 ),
225 )
226 ) {
227 updatedPaths.push(modelGemVersionPath);
228 }
229
230 if (
231 updateFile(modelGemCargoTomlPath, (content) => {
232 let next = replaceOrThrow(
233 content,
234 /^version\s*=\s*"[^"]+"/m,
235 `version = "${releaseVersion}"`,
236 `${modelGemCargoTomlPath} package version`,
237 );
238
239 next = replaceOrThrow(
240 next,
241 /(smbcloud-model\s*=\s*\{\s*version\s*=\s*")([^"]+)("\s*\})/,
242 `$1${rubySdkRequirement}$3`,
243 `${modelGemCargoTomlPath} dependency smbcloud-model`,
244 );
245
246 return next;
247 })
248 ) {
249 updatedPaths.push(modelGemCargoTomlPath);
250 }
251
252 if (
253 updateFile(emailGemVersionPath, (content) =>
254 replaceOrThrow(
255 content,
256 /VERSION\s*=\s*'[^']+'/,
257 `VERSION = '${releaseVersion}'`,
258 `${emailGemVersionPath} VERSION constant`,
259 ),
260 )
261 ) {
262 updatedPaths.push(emailGemVersionPath);
263 }
264
265 if (
266 updateFile(emailGemCargoTomlPath, (content) =>
267 replaceOrThrow(
268 content,
269 /^version\s*=\s*"[^"]+"/m,
270 `version = "${releaseVersion}"`,
271 `${emailGemCargoTomlPath} package version`,
272 ).replace(
273 /(smbcloud-email-sdk\s*=\s*\{\s*version\s*=\s*")([^"]+)("\s*\})/,
274 `$1${rubySdkRequirement}$3`,
275 ),
276 )
277 ) {
278 updatedPaths.push(emailGemCargoTomlPath);
279 }
280
281 if (
282 updateFile(emailGemLockPath, (content) =>
283 replaceOrThrow(
284 content,
285 /(smbcloud-email \()[^)]+(\))/,
286 `$1${releaseVersion}$2`,
287 `${emailGemLockPath} gem version`,
288 ),
289 )
290 ) {
291 updatedPaths.push(emailGemLockPath);
292 }
293
294 if (
295 updateFile(mcpServerJsonPath, (content) => {
296 const parsed = JSON.parse(content);
297 parsed.version = releaseVersion;
298
299 for (const pkg of parsed.packages ?? []) {
300 pkg.version = releaseVersion;
301 }
302
303 return `${JSON.stringify(parsed, null, 2)}\n`;
304 })
305 ) {
306 updatedPaths.push(mcpServerJsonPath);
307 }
308
309 if (
310 updateFile(xcrsMcpServerJsonPath, (content) => {
311 const parsed = JSON.parse(content);
312 parsed.version = releaseVersion;
313
314 for (const pkg of parsed.packages ?? []) {
315 pkg.version = releaseVersion;
316 }
317
318 return `${JSON.stringify(parsed, null, 2)}\n`;
319 })
320 ) {
321 updatedPaths.push(xcrsMcpServerJsonPath);
322 }
323
324 console.log(`Release version: ${releaseVersion}`);
325
326 if (updatedPaths.length === 0) {
327 console.log("Release metadata already in sync.");
328 } else {
329 console.log("Updated release metadata files:");
330 for (const path of updatedPaths) {
331 console.log(`- ${path}`);
332 }
333 }
334
335 console.log(`Ruby SDK dependency requirement: ${rubySdkRequirement}`);