Skip to content
164 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

A last-commits walk runs to the end unless asked for a budget; the page bounds its own wait1#!/usr/bin/env node
2// Releases of the g1t CLI (crates/g1t): built for every platform,
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.3// checksummed, signed, and published to the g1t-downloads R2 bucket that
4// g1t.sh serves at /downloads/cli/ (apps/web/app/routes/downloads-runner.ts).
5// g1t.sh/install and g1t.sh/install.ps1 (apps/web/app/content/install/)
6// install the newest one.
A last-commits walk runs to the end unless asked for a budget; the page bounds its own wait7//
8// node scripts/cli-release.mjs build # every platform, in the cargo-zigbuild image (Docker)
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.9// node scripts/cli-release.mjs manifest # SHA256SUMS, manifest.json and latest.json from what was built
10// node scripts/cli-release.mjs sign # latest.json.sig
11// node scripts/cli-release.mjs verify # checks the signature
A last-commits walk runs to the end unless asked for a budget; the page bounds its own wait12// node scripts/cli-release.mjs publish [--dry-run]
13//
14// At cli/<version>/ in the bucket: g1t-linux-x64, -linux-arm64,
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.15// -macos-x64, -macos-arm64, -windows-x64.exe, SHA256SUMS, manifest.json and
16// latest.json.sig; at cli/: latest.json, the newest release's manifest, and
17// latest.json.sig, its Ed25519 signature in base64. SHA256SUMS is what the
18// install scripts check a download against; the signature is what a CLI
19// that updates itself will check, with the same release key as the
20// runner's (scripts/runner-release.mjs keygen):
21//
22// RUNNER_RELEASE_KEY the private key: a g1t Actions secret
23// G1T_RUNNER_RELEASE_KEY the public key, for `verify`
24//
25// The macOS builds link Apple's Security framework (the keychain), whose
26// SDK the rust-cross/cargo-zigbuild image carries, so `build` runs there.
A last-commits walk runs to the end unless asked for a budget; the page bounds its own wait27
28import { spawnSync } from "node:child_process";
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.29import { createHash, createPrivateKey } from "node:crypto";
A last-commits walk runs to the end unless asked for a budget; the page bounds its own wait30import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
31import { dirname, join } from "node:path";
32import { fileURLToPath } from "node:url";
33
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.34import { signBytes, verifyBytes } from "./runner-release.mjs";
35
A last-commits walk runs to the end unless asked for a budget; the page bounds its own wait36const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
37const BUCKET = "g1t-downloads";
38const BUILDER = "ghcr.io/rust-cross/cargo-zigbuild:latest";
39export const TARGETS = {
40 "linux-x64": { triple: "x86_64-unknown-linux-musl", file: "g1t-linux-x64" },
41 "linux-arm64": { triple: "aarch64-unknown-linux-musl", file: "g1t-linux-arm64" },
42 "macos-x64": { triple: "x86_64-apple-darwin", file: "g1t-macos-x64" },
43 "macos-arm64": { triple: "aarch64-apple-darwin", file: "g1t-macos-arm64" },
44 "windows-x64": { triple: "x86_64-pc-windows-gnu", file: "g1t-windows-x64.exe", exe: true },
45};
46
47export function version() {
48 const found = /^version\s*=\s*"([^"]+)"/m.exec(readFileSync(join(ROOT, "crates/g1t/Cargo.toml"), "utf8"));
49 if (!found) throw new Error("crates/g1t/Cargo.toml has no version");
50 return found[1];
51}
52
53const outDir = (v = version()) => join(ROOT, "target", "cli-release", v);
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.54export const sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex");
A last-commits walk runs to the end unless asked for a budget; the page bounds its own wait55
56function run(command, args, options = {}) {
57 const done = spawnSync(command, args, { stdio: "inherit", cwd: ROOT, ...options });
58 if (done.status !== 0) throw new Error(`${command} ${args.join(" ")} failed`);
59}
60
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.61/**
62 * The release's manifest, from the binaries in `dir`: each platform's file
63 * with its SHA-256. Writes SHA256SUMS, manifest.json and latest.json there.
64 */
65export function manifest(dir, v, now = new Date()) {
66 const files = {};
67 for (const [platform, target] of Object.entries(TARGETS)) {
68 const path = join(dir, target.file);
69 if (!existsSync(path)) continue;
70 files[platform] = { name: target.file, sha256: sha256(readFileSync(path)) };
71 }
72 if (Object.keys(files).length === 0) throw new Error(`no binaries in ${dir}: node scripts/cli-release.mjs build`);
73 const body = { version: v, published_at: now.toISOString(), files };
74 const text = `${JSON.stringify(body, null, 2)}\n`;
75 writeFileSync(join(dir, "manifest.json"), text);
76 writeFileSync(join(dir, "latest.json"), text);
77 writeFileSync(join(dir, "SHA256SUMS"), Object.values(files).map((f) => `${f.sha256} ${f.name}`).join("\n") + "\n");
78 return body;
79}
80
A last-commits walk runs to the end unless asked for a budget; the page bounds its own wait81function build() {
82 const v = version();
83 const dir = outDir(v);
84 mkdirSync(dir, { recursive: true });
85 const triples = Object.values(TARGETS).map((t) => t.triple).join(" ");
86 // One container builds every platform; its target folder is kept apart
87 // from the host's so the two never mix.
88 run("docker", [
89 "run", "--rm",
90 "-e", "CARGO_TARGET_DIR=/src/target/zig",
91 "-v", `${ROOT.replaceAll("\\", "/")}:/src`,
92 "-v", "g1t-cargo-registry:/usr/local/cargo/registry",
93 "-w", "/src", BUILDER, "sh", "-c",
94 `set -e; for t in ${triples}; do rustup target add $t >/dev/null 2>&1; cargo zigbuild --release --locked --package g1t --target $t; done`,
95 ], { env: { ...process.env, MSYS_NO_PATHCONV: "1" } });
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.96 for (const target of Object.values(TARGETS)) {
A last-commits walk runs to the end unless asked for a budget; the page bounds its own wait97 const built = join(ROOT, "target", "zig", target.triple, "release", target.exe ? "g1t.exe" : "g1t");
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.98 writeFileSync(join(dir, target.file), readFileSync(built));
99 }
100 const m = manifest(dir, v);
101 console.log(`built ${Object.keys(m.files).length} binaries of g1t ${v} into ${dir}`);
102}
103
104function privateKey(text = process.env.RUNNER_RELEASE_KEY) {
105 if (!text) throw new Error("RUNNER_RELEASE_KEY is not set");
106 return createPrivateKey({ key: Buffer.from(text, "base64"), format: "der", type: "pkcs8" });
107}
108
109function signRelease() {
110 const dir = outDir();
111 const bytes = readFileSync(join(dir, "latest.json"));
112 writeFileSync(join(dir, "latest.json.sig"), signBytes(bytes, privateKey()));
113 console.log(`signed g1t ${version()}`);
114}
115
116function verifyRelease() {
117 const dir = outDir();
118 const publicRaw = process.env.G1T_RUNNER_RELEASE_KEY;
119 if (!publicRaw) throw new Error("G1T_RUNNER_RELEASE_KEY is not set");
120 const ok = verifyBytes(readFileSync(join(dir, "latest.json")), readFileSync(join(dir, "latest.json.sig"), "utf8"), publicRaw);
121 if (!ok) throw new Error("latest.json's signature is not the release key's");
122 const m = JSON.parse(readFileSync(join(dir, "latest.json"), "utf8"));
123 for (const file of Object.values(m.files)) {
124 if (sha256(readFileSync(join(dir, file.name))) !== file.sha256) throw new Error(`${file.name} is not the file the manifest names`);
A last-commits walk runs to the end unless asked for a budget; the page bounds its own wait125 }
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.126 console.log(`g1t ${m.version}: the signature and every file check out`);
A last-commits walk runs to the end unless asked for a budget; the page bounds its own wait127}
128
129function publish(dryRun) {
130 const v = version();
131 const dir = outDir(v);
132 if (!existsSync(join(dir, "manifest.json"))) throw new Error(`nothing built for ${v}: node scripts/cli-release.mjs build`);
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.133 const signed = existsSync(join(dir, "latest.json.sig"));
A last-commits walk runs to the end unless asked for a budget; the page bounds its own wait134 const put = (key, file, type) => {
135 if (dryRun) return console.log(`would put ${key}`);
136 run("npx", ["wrangler", "r2", "object", "put", `${BUCKET}/${key}`, "--file", file, "--remote", "--content-type", type], {
137 shell: process.platform === "win32",
138 // Away from the repository's .env, which may hold a token meant for
139 // something else (as scripts/deploy does).
140 cwd: join(ROOT, "apps/web"),
141 });
142 };
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.143 const m = JSON.parse(readFileSync(join(dir, "manifest.json"), "utf8"));
144 for (const file of Object.values(m.files)) put(`cli/${v}/${file.name}`, join(dir, file.name), "application/octet-stream");
A last-commits walk runs to the end unless asked for a budget; the page bounds its own wait145 put(`cli/${v}/manifest.json`, join(dir, "manifest.json"), "application/json");
146 put(`cli/${v}/SHA256SUMS`, join(dir, "SHA256SUMS"), "text/plain");
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.147 if (signed) put(`cli/${v}/latest.json.sig`, join(dir, "latest.json.sig"), "text/plain");
A last-commits walk runs to the end unless asked for a budget; the page bounds its own wait148 // Last, so `latest` never names a version whose files are not up yet.
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.149 if (signed) put("cli/latest.json.sig", join(dir, "latest.json.sig"), "text/plain");
A last-commits walk runs to the end unless asked for a budget; the page bounds its own wait150 put("cli/latest.json", join(dir, "latest.json"), "application/json");
151}
152
153if (process.argv[1]?.replaceAll("\\", "/").endsWith("scripts/cli-release.mjs")) {
154 const [command, ...rest] = process.argv.slice(2);
155 if (command === "build") build();
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.156 else if (command === "manifest") manifest(outDir(), version());
157 else if (command === "sign") signRelease();
158 else if (command === "verify") verifyRelease();
A last-commits walk runs to the end unless asked for a budget; the page bounds its own wait159 else if (command === "publish") publish(rest.includes("--dry-run"));
160 else {
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.161 console.error("usage: node scripts/cli-release.mjs build|manifest|sign|verify|publish [--dry-run]");
A last-commits walk runs to the end unless asked for a budget; the page bounds its own wait162 process.exit(2);
163 }
164}

This file's history is long; its oldest lines are credited to the oldest commit read.