Diffinery

codician/diffinerypublic

Open-source, agent-first GitHub and GitHub Actions alternative that runs on your own Cloudflare account: git hosting, pull requests, code review and drop-in GitHub Actions YAML on Workers, Durable Objects, R2 and Containers.

99 lines · 4.6 KBRaw History
1import path from "node:path";
2import { errMsg, failure } from "../job.js";
3import { maxSatisfying, satisfies, truthyInput } from "../util.js";
4import type { Builtin } from "./index.js";
5import { fetchJson, installTarball, isCached, packageCache, probe, readVersionFile, rootLockHash, toolArch, toolDir } from "./tools.js";
6
7interface GoRelease {
8 version: string; // "go1.22.3"
9 stable: boolean;
10 files: Array<{ filename: string; os: string; arch: string; kind: string; sha256?: string }>;
11}
12
13let index: { url: string; at: number; releases: Promise<GoRelease[]> } | null = null;
14
15/** Go's releases index, fetched at most every 10 minutes: the job's head start (prefetch.ts) and the step share it. */
16function goReleases(): Promise<GoRelease[]> {
17 const url = process.env.DIFFINERY_GO_INDEX ?? "https://go.dev/dl/?mode=json&include=all";
18 if (!index || index.url !== url || Date.now() - index.at > 10 * 60_000) {
19 const releases = fetchJson<GoRelease[]>(url);
20 index = { url, at: Date.now(), releases };
21 releases.catch(() => {
22 if (index?.releases === releases) index = null;
23 });
24 }
25 return index.releases;
26}
27
28/** The stable Go release `spec` asks for ("1.22", "1.22.3", "stable", "oldstable", a range), as setup-go picks it. */
29export async function resolveGo(spec: string): Promise<{ version: string; url: string; sha256?: string } | null> {
30 const releases = await goReleases();
31 const arch = toolArch("go");
32 const stable = releases.filter((r) => r.stable && r.files.some((f) => f.os === "linux" && f.arch === arch && f.kind === "archive"));
33 let v: string | null;
34 if (spec === "stable") v = maxSatisfying(stable.map((r) => r.version.replace(/^go/, "")), "*");
35 else if (spec === "oldstable") {
36 const sorted = stable.map((r) => r.version.replace(/^go/, ""));
37 const latest = maxSatisfying(sorted, "*");
38 const latestMinor = latest ? latest.split(".").slice(0, 2).join(".") : "";
39 v = maxSatisfying(sorted.filter((x) => !x.startsWith(latestMinor + ".") && x !== latestMinor), "*");
40 } else v = maxSatisfying(stable.map((r) => r.version.replace(/^go/, "")), spec);
41 if (!v) return null;
42 const rel = stable.find((r) => r.version === `go${v}`)!;
43 const f = rel.files.find((x) => x.os === "linux" && x.arch === arch && x.kind === "archive")!;
44 return { version: v, url: `${process.env.DIFFINERY_GO_DL ?? "https://go.dev/dl/"}${f.filename}`, sha256: f.sha256 };
45}
46
47export const setupGo: Builtin = async (c) => {
48 let spec = c.input("go-version");
49 const file = c.input("go-version-file");
50 if (!spec && file) spec = await readVersionFile(c, file, "go");
51 spec = spec.trim().replace(/^go/, "");
52 const installedRaw = await probe(c, "go", ["version"]);
53 const installed = /go(\d+\.\d+(?:\.\d+)?)/.exec(installedRaw ?? "")?.[1] ?? null;
54 let version = installed;
55
56 if (!spec && !installed) throw failure("No go-version specified and Go is not installed on this runner.");
57 const useInstalled = installed && (!spec || (!/^(stable|oldstable)$/.test(spec) && satisfies(installed, spec))) && !truthyInput(c.input("check-latest"), false);
58 if (useInstalled) {
59 c.log(`Using pre-installed Go ${installed}`);
60 } else {
61 let rel: Awaited<ReturnType<typeof resolveGo>>;
62 try {
63 rel = await resolveGo(spec);
64 } catch (e) {
65 throw failure(`Unable to fetch the Go versions index: ${errMsg(e)}`);
66 }
67 const arch = toolArch("go");
68 if (!rel) throw failure(`Unable to find Go version '${spec}' for platform linux and architecture ${arch}.`);
69 const v = rel.version;
70 const dir = toolDir(c, "go", v, arch);
71 if (!isCached(dir)) await installTarball(c, rel.url, dir, 1, rel.sha256);
72 else c.log(`Found in cache @ ${dir}`);
73 c.addPath(path.join(dir, "bin"));
74 version = v;
75 }
76 const gopath = await probe(c, "go", ["env", "GOPATH"], c.toolEnv());
77 if (gopath) c.addPath(path.join(gopath, "bin"));
78 if (version) c.setOutput("go-version", version);
79
80 if (truthyInput(c.input("cache"), true)) {
81 let hash: string;
82 try {
83 hash = rootLockHash(c, c.input("cache-dependency-path"), ["go.sum"]);
84 if (!hash) throw failure("Some specified paths were not resolved, unable to cache dependencies.");
85 } catch (e) {
86 c.warn(`Restore cache failed: ${errMsg(e)}`);
87 return;
88 }
89 const env = c.toolEnv();
90 const modcache = await probe(c, "go", ["env", "GOMODCACHE"], env);
91 const buildcache = await probe(c, "go", ["env", "GOCACHE"], env);
92 const imageOs = env.ImageOS ?? "ubuntu24";
93 await packageCache(c, {
94 key: `setup-go-Linux-${process.arch}-${imageOs}-go-${version}-${hash}`,
95 paths: [modcache ?? "", buildcache ?? ""].filter(Boolean),
96 what: "go",
97 });
98 }
99};