release/v0.7.4
claude/smbcloud-github-app-k9641p
development
feature/apps
feature/build-release
feature/ci-docs
feature/ci-ruby-gems
feature/cicd
feature/completion
feature/dist-nuget
feature/fix-ci-sdk-pypi-rubygem
feature/fix-clippy-on-stable-channel
feature/fix-deployment-script
feature/github-email
feature/gresiq-v1
feature/project-new-structure
feature/rsync-deploy
feature/ruby-runner
feature/sdk-npm
feature/sdk-workflows
feature/smbcloud-cli-release-npm
feature/smbcloud-deploy
feature/wasm
main
release/v0.7.4
| 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 | replaceOrThrow( |
| 233 | content, |
| 234 | /^version\s*=\s*"[^"]+"/m, |
| 235 | `version = "${releaseVersion}"`, |
| 236 | `${modelGemCargoTomlPath} package version`, |
| 237 | ), |
| 238 | ) |
| 239 | ) { |
| 240 | updatedPaths.push(modelGemCargoTomlPath); |
| 241 | } |
| 242 | |
| 243 | if ( |
| 244 | updateFile(emailGemVersionPath, (content) => |
| 245 | replaceOrThrow( |
| 246 | content, |
| 247 | /VERSION\s*=\s*'[^']+'/, |
| 248 | `VERSION = '${releaseVersion}'`, |
| 249 | `${emailGemVersionPath} VERSION constant`, |
| 250 | ), |
| 251 | ) |
| 252 | ) { |
| 253 | updatedPaths.push(emailGemVersionPath); |
| 254 | } |
| 255 | |
| 256 | if ( |
| 257 | updateFile(emailGemCargoTomlPath, (content) => |
| 258 | replaceOrThrow( |
| 259 | content, |
| 260 | /^version\s*=\s*"[^"]+"/m, |
| 261 | `version = "${releaseVersion}"`, |
| 262 | `${emailGemCargoTomlPath} package version`, |
| 263 | ), |
| 264 | ) |
| 265 | ) { |
| 266 | updatedPaths.push(emailGemCargoTomlPath); |
| 267 | } |
| 268 | |
| 269 | if ( |
| 270 | updateFile(emailGemLockPath, (content) => |
| 271 | replaceOrThrow( |
| 272 | content, |
| 273 | /(smbcloud-email \()[^)]+(\))/, |
| 274 | `$1${releaseVersion}$2`, |
| 275 | `${emailGemLockPath} gem version`, |
| 276 | ), |
| 277 | ) |
| 278 | ) { |
| 279 | updatedPaths.push(emailGemLockPath); |
| 280 | } |
| 281 | |
| 282 | if ( |
| 283 | updateFile(mcpServerJsonPath, (content) => { |
| 284 | const parsed = JSON.parse(content); |
| 285 | parsed.version = releaseVersion; |
| 286 | |
| 287 | for (const pkg of parsed.packages ?? []) { |
| 288 | pkg.version = releaseVersion; |
| 289 | } |
| 290 | |
| 291 | return `${JSON.stringify(parsed, null, 2)}\n`; |
| 292 | }) |
| 293 | ) { |
| 294 | updatedPaths.push(mcpServerJsonPath); |
| 295 | } |
| 296 | |
| 297 | if ( |
| 298 | updateFile(xcrsMcpServerJsonPath, (content) => { |
| 299 | const parsed = JSON.parse(content); |
| 300 | parsed.version = releaseVersion; |
| 301 | |
| 302 | for (const pkg of parsed.packages ?? []) { |
| 303 | pkg.version = releaseVersion; |
| 304 | } |
| 305 | |
| 306 | return `${JSON.stringify(parsed, null, 2)}\n`; |
| 307 | }) |
| 308 | ) { |
| 309 | updatedPaths.push(xcrsMcpServerJsonPath); |
| 310 | } |
| 311 | |
| 312 | console.log(`Release version: ${releaseVersion}`); |
| 313 | |
| 314 | if (updatedPaths.length === 0) { |
| 315 | console.log("Release metadata already in sync."); |
| 316 | } else { |
| 317 | console.log("Updated release metadata files:"); |
| 318 | for (const path of updatedPaths) { |
| 319 | console.log(`- ${path}`); |
| 320 | } |
| 321 | } |
| 322 | |
| 323 | console.log(`Ruby SDK dependency requirement: ${rubySdkRequirement}`); |