1 import { existsSync, readFileSync, readdirSync } from "node:fs";
2 import { resolve } from "node:path";
3
4 const repoRoot = resolve(import.meta.dirname, "..");
5 const expectedVersion = readFileSync(resolve(repoRoot, "crates/cli/Cargo.toml"), "utf8")
6 .match(/^version\s*=\s*"([^"]+)"/m)?.[1];
7
8 if (!expectedVersion) {
9 throw new Error("Unable to determine the release version from crates/cli/Cargo.toml");
10 }
11
12 const mismatches = [];
13
14 function read(path) {
15 return readFileSync(resolve(repoRoot, path), "utf8");
16 }
17
18 function check(path, actualVersion) {
19 if (actualVersion !== expectedVersion) {
20 mismatches.push(`${path}: ${actualVersion ?? "missing"} (expected ${expectedVersion})`);
21 }
22 }
23
24 function cargoPackageVersion(path) {
25 return read(path).match(/^\[package\][\s\S]*?^version\s*=\s*"([^"]+)"/m)?.[1];
26 }
27
28 for (const directory of ["crates", "sdk/gems"]) {
29 const directoryPath = resolve(repoRoot, directory);
30 for (const entry of readdirSync(directoryPath, { withFileTypes: true })) {
31 if (!entry.isDirectory()) {
32 continue;
33 }
34
35 const candidates = [
36 `${directory}/${entry.name}/Cargo.toml`,
37 ...readdirSync(resolve(directoryPath, entry.name), { withFileTypes: true })
38 .filter((nestedEntry) => nestedEntry.isDirectory())
39 .map((nestedEntry) => `${directory}/${entry.name}/${nestedEntry.name}/Cargo.toml`),
40 ];
41
42 for (const candidate of candidates) {
43 if (existsSync(resolve(repoRoot, candidate))) {
44 const version = cargoPackageVersion(candidate);
45 if (version) {
46 check(candidate, version);
47 }
48 }
49 }
50 }
51 }
52
53 for (const match of read("Cargo.toml").matchAll(
54 /^([\w-]+)\s*=\s*\{\s*version\s*=\s*"([^"]+)"\s*,\s*path\s*=\s*"crates\//gm,
55 )) {
56 check(`Cargo.toml workspace dependency ${match[1]}`, match[2]);
57 }
58
59 for (const path of [
60 "sdk/gems/auth/lib/auth/version.rb",
61 "sdk/gems/email/lib/email/version.rb",
62 "sdk/gems/model/lib/model/version.rb",
63 ]) {
64 check(path, read(path).match(/VERSION\s*=\s*['"]([^'"]+)['"]/)?.[1]);
65 }
66
67 // The Ruby gems link the smbcloud-* crates from crates.io. Their requirement
68 // must be the release's major.minor, or a gem can silently wrap an older crate.
69 const [expectedMajor, expectedMinor] = expectedVersion.split(".");
70 const expectedRequirement = `${expectedMajor}.${expectedMinor}`;
71 for (const path of [
72 "sdk/gems/auth/ext/auth/Cargo.toml",
73 "sdk/gems/email/ext/email/Cargo.toml",
74 "sdk/gems/model/ext/model/Cargo.toml",
75 ]) {
76 for (const match of read(path).matchAll(/^(smbcloud-[\w-]+)\s*=\s*\{\s*version\s*=\s*"([^"]+)"/gm)) {
77 if (match[2] !== expectedRequirement) {
78 mismatches.push(`${path} dependency ${match[1]}: ${match[2]} (expected ${expectedRequirement})`);
79 }
80 }
81 }
82
83 const npmPackage = JSON.parse(read("npm/smbcloud-cli/package.json"));
84 check("npm/smbcloud-cli/package.json", npmPackage.version);
85
86 const sdkNpmPackage = JSON.parse(read("sdk/npm/smbcloud-auth/package.json"));
87 check("sdk/npm/smbcloud-auth/package.json", sdkNpmPackage.version);
88
89 const npmLock = JSON.parse(read("npm/smbcloud-cli/package-lock.json"));
90 check("npm/smbcloud-cli/package-lock.json", npmLock.version);
91 check("npm/smbcloud-cli/package-lock.json packages root", npmLock.packages?.[""]?.version);
92
93 const npmXcrsPackage = JSON.parse(read("npm/xcrs/package.json"));
94 check("npm/xcrs/package.json", npmXcrsPackage.version);
95
96 const npmXcrsLock = JSON.parse(read("npm/xcrs/package-lock.json"));
97 check("npm/xcrs/package-lock.json", npmXcrsLock.version);
98 check("npm/xcrs/package-lock.json packages root", npmXcrsLock.packages?.[""]?.version);
99
100 for (const path of ["server.json", "server-xcrs.json"]) {
101 const server = JSON.parse(read(path));
102 check(path, server.version);
103 for (const [index, packageEntry] of (server.packages ?? []).entries()) {
104 check(`${path} package ${index + 1}`, packageEntry.version);
105 }
106 }
107
108 check(
109 "nuget/smbcloud-cli/SmbCloud.Cli.csproj",
110 read("nuget/smbcloud-cli/SmbCloud.Cli.csproj").match(/<PackageVersion>([^<]+)<\/PackageVersion>/)?.[1],
111 );
112 check(
113 "nuget/xcrs/Xcrs.csproj",
114 read("nuget/xcrs/Xcrs.csproj").match(/<PackageVersion>([^<]+)<\/PackageVersion>/)?.[1],
115 );
116 check(
117 "sdk/gems/email/Gemfile.lock",
118 read("sdk/gems/email/Gemfile.lock").match(/smbcloud-email \(([^)]+)\)/)?.[1],
119 );
120
121 if (mismatches.length > 0) {
122 console.error(`Release version mismatch detected for ${expectedVersion}:`);
123 for (const mismatch of mismatches) {
124 console.error(`- ${mismatch}`);
125 }
126 process.exit(1);
127 }
128
129 console.log(`All release artifacts use version ${expectedVersion}.`);