|
| 1 | +#!/usr/bin/env node |
| 2 | + |
| 3 | +// Fetches the latest published version of each Temporal SDK from its package |
| 4 | +// registry and writes src/data/sdk-versions.json. Powers the version chips on |
| 5 | +// the /develop overview page (src/components/elements/Sdk/SdkOverviewCards). |
| 6 | +// |
| 7 | +// Each SDK's registry is queried independently: one registry being down, |
| 8 | +// renamed, or rate-limiting doesn't block updating the other seven. A failed |
| 9 | +// fetch keeps the previously recorded version rather than clearing it. The |
| 10 | +// file is only rewritten when a version actually changed, so a scheduled run |
| 11 | +// that finds nothing new produces no diff (and no PR). |
| 12 | +// |
| 13 | +// node bin/update-sdk-versions.js # report to stdout |
| 14 | +// node bin/update-sdk-versions.js --write # write src/data/sdk-versions.json |
| 15 | + |
| 16 | +const https = require("https"); |
| 17 | +const fs = require("fs"); |
| 18 | +const path = require("path"); |
| 19 | + |
| 20 | +const OUT_PATH = path.join(__dirname, "..", "src", "data", "sdk-versions.json"); |
| 21 | +const USER_AGENT = "temporal-docs-sdk-version-bot (+https://github.com/temporalio/documentation)"; |
| 22 | + |
| 23 | +function fetchText(url) { |
| 24 | + return new Promise((resolve, reject) => { |
| 25 | + https |
| 26 | + .get(url, { headers: { "User-Agent": USER_AGENT, Accept: "application/json" } }, (res) => { |
| 27 | + if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { |
| 28 | + res.resume(); |
| 29 | + fetchText(res.headers.location).then(resolve, reject); |
| 30 | + return; |
| 31 | + } |
| 32 | + let data = ""; |
| 33 | + res.on("data", (chunk) => (data += chunk)); |
| 34 | + res.on("end", () => { |
| 35 | + if (res.statusCode !== 200) { |
| 36 | + reject(new Error(`HTTP ${res.statusCode} for ${url}`)); |
| 37 | + return; |
| 38 | + } |
| 39 | + resolve(data); |
| 40 | + }); |
| 41 | + }) |
| 42 | + .on("error", reject); |
| 43 | + }); |
| 44 | +} |
| 45 | + |
| 46 | +async function fetchJSON(url) { |
| 47 | + return JSON.parse(await fetchText(url)); |
| 48 | +} |
| 49 | + |
| 50 | +// Strips a leading "v" so chips read e.g. "1.31.0" consistently — Go and PHP |
| 51 | +// tag releases as "v1.2.3"; the other registries don't. |
| 52 | +function stripV(version) { |
| 53 | + return version.replace(/^v/, ""); |
| 54 | +} |
| 55 | + |
| 56 | +const STABLE_SEMVER = /^v?\d+\.\d+\.\d+$/; |
| 57 | + |
| 58 | +// One fetcher per SDK in src/constants/sdks.js. Keep the id keys in sync with |
| 59 | +// that file's SDKS[].id. |
| 60 | +const FETCHERS = { |
| 61 | + async go() { |
| 62 | + const data = await fetchJSON("https://proxy.golang.org/github.com/temporalio/sdk-go/@latest"); |
| 63 | + return stripV(data.Version); |
| 64 | + }, |
| 65 | + async java() { |
| 66 | + const xml = await fetchText( |
| 67 | + "https://repo1.maven.org/maven2/io/temporal/temporal-sdk/maven-metadata.xml" |
| 68 | + ); |
| 69 | + const match = xml.match(/<release>([^<]+)<\/release>/); |
| 70 | + if (!match) throw new Error("no <release> in maven-metadata.xml"); |
| 71 | + return match[1]; |
| 72 | + }, |
| 73 | + async dotnet() { |
| 74 | + const data = await fetchJSON("https://api.nuget.org/v3-flatcontainer/temporalio/index.json"); |
| 75 | + const stable = data.versions.filter((v) => STABLE_SEMVER.test(v)); |
| 76 | + if (stable.length === 0) throw new Error("no stable NuGet version found"); |
| 77 | + return stable[stable.length - 1]; |
| 78 | + }, |
| 79 | + async php() { |
| 80 | + const data = await fetchJSON("https://repo.packagist.org/p2/temporal/sdk.json"); |
| 81 | + const versions = data.packages["temporal/sdk"]; |
| 82 | + const stable = versions.find((v) => STABLE_SEMVER.test(v.version)); |
| 83 | + if (!stable) throw new Error("no stable Packagist version found"); |
| 84 | + return stripV(stable.version); |
| 85 | + }, |
| 86 | + async python() { |
| 87 | + const data = await fetchJSON("https://pypi.org/pypi/temporalio/json"); |
| 88 | + return data.info.version; |
| 89 | + }, |
| 90 | + async ruby() { |
| 91 | + const data = await fetchJSON("https://rubygems.org/api/v1/gems/temporalio.json"); |
| 92 | + return data.version; |
| 93 | + }, |
| 94 | + async rust() { |
| 95 | + const data = await fetchJSON("https://crates.io/api/v1/crates/temporalio-sdk"); |
| 96 | + const version = data.crate.max_stable_version || data.crate.newest_version; |
| 97 | + if (!version) throw new Error("no version in crates.io response"); |
| 98 | + return version; |
| 99 | + }, |
| 100 | + async typescript() { |
| 101 | + const data = await fetchJSON("https://registry.npmjs.org/@temporalio/client/latest"); |
| 102 | + return data.version; |
| 103 | + }, |
| 104 | +}; |
| 105 | + |
| 106 | +async function main() { |
| 107 | + const existing = fs.existsSync(OUT_PATH) |
| 108 | + ? JSON.parse(fs.readFileSync(OUT_PATH, "utf-8")) |
| 109 | + : { updatedAt: null, versions: {} }; |
| 110 | + const previousVersions = existing.versions || {}; |
| 111 | + |
| 112 | + const versions = { ...previousVersions }; |
| 113 | + const failures = []; |
| 114 | + |
| 115 | + for (const [id, fetchVersion] of Object.entries(FETCHERS)) { |
| 116 | + try { |
| 117 | + versions[id] = await fetchVersion(); |
| 118 | + } catch (err) { |
| 119 | + failures.push(`${id}: ${err.message}`); |
| 120 | + } |
| 121 | + } |
| 122 | + |
| 123 | + if (failures.length > 0) { |
| 124 | + console.error( |
| 125 | + `Kept previous version for ${failures.length} SDK(s) after a fetch error:\n ${failures.join("\n ")}` |
| 126 | + ); |
| 127 | + } |
| 128 | + if (failures.length === Object.keys(FETCHERS).length) { |
| 129 | + console.error("Every registry fetch failed — leaving sdk-versions.json unchanged."); |
| 130 | + process.exit(1); |
| 131 | + } |
| 132 | + |
| 133 | + // Only bump the timestamp when a version actually changed, so a no-op run |
| 134 | + // (the common case) produces a byte-identical file and no git diff. |
| 135 | + const changed = JSON.stringify(versions) !== JSON.stringify(previousVersions); |
| 136 | + const output = { |
| 137 | + updatedAt: changed ? new Date().toISOString() : existing.updatedAt, |
| 138 | + versions, |
| 139 | + }; |
| 140 | + |
| 141 | + if (process.argv.includes("--write")) { |
| 142 | + fs.writeFileSync(OUT_PATH, JSON.stringify(output, null, 2) + "\n"); |
| 143 | + console.error(`Wrote ${OUT_PATH}`); |
| 144 | + } else { |
| 145 | + console.log(JSON.stringify(output, null, 2)); |
| 146 | + } |
| 147 | +} |
| 148 | + |
| 149 | +main().catch((err) => { |
| 150 | + console.error(err); |
| 151 | + process.exit(1); |
| 152 | +}); |
0 commit comments