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.
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 1 | #!/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. | |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 7 | // |
| 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 | |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 12 | // 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. | |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 27 | |
| 28 | import { 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. | 29 | import { createHash, createPrivateKey } from "node:crypto"; |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 30 | import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; |
| Merge branch 'worktree-agent-a3b9b1f730f55b5bf' | 31 | import { homedir } from "node:os"; |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 32 | import { dirname, join } from "node:path"; |
| 33 | import { fileURLToPath } from "node:url"; | |
| 34 | ||
| 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. | 35 | import { signBytes, verifyBytes } from "./runner-release.mjs"; |
| 36 | ||
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 37 | const ROOT = join(dirname(fileURLToPath(import.meta.url)), ".."); |
| 38 | const BUCKET = "g1t-downloads"; | |
| Merge branch 'worktree-agent-a3b9b1f730f55b5bf' | 39 | // Pinned by digest, so a release builds with the toolchain (Rust 1.93.0, |
| 40 | // zig 0.16.0) its cache was built with, and `latest` moving never changes a | |
| 41 | // release under it. To move on: `docker pull ghcr.io/rust-cross/cargo-zigbuild:latest` | |
| 42 | // and take the digest from `docker image inspect --format '{{json .RepoDigests}}'`. | |
| 43 | // The Caches workflow keys the build's cache by it (ci-cache.mjs keys: | |
| 44 | // zigbuild), so a new digest starts a new entry. | |
| 45 | export const BUILDER = "ghcr.io/rust-cross/cargo-zigbuild@sha256:d8313491ec5798de0633fdc1c5753761bff79967bea69076020dc78121b2cca8"; | |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 46 | export const TARGETS = { |
| 47 | "linux-x64": { triple: "x86_64-unknown-linux-musl", file: "g1t-linux-x64" }, | |
| 48 | "linux-arm64": { triple: "aarch64-unknown-linux-musl", file: "g1t-linux-arm64" }, | |
| 49 | "macos-x64": { triple: "x86_64-apple-darwin", file: "g1t-macos-x64" }, | |
| 50 | "macos-arm64": { triple: "aarch64-apple-darwin", file: "g1t-macos-arm64" }, | |
| 51 | "windows-x64": { triple: "x86_64-pc-windows-gnu", file: "g1t-windows-x64.exe", exe: true }, | |
| 52 | }; | |
| 53 | ||
| 54 | export function version() { | |
| 55 | const found = /^version\s*=\s*"([^"]+)"/m.exec(readFileSync(join(ROOT, "crates/g1t/Cargo.toml"), "utf8")); | |
| 56 | if (!found) throw new Error("crates/g1t/Cargo.toml has no version"); | |
| 57 | return found[1]; | |
| 58 | } | |
| 59 | ||
| 60 | const 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. | 61 | export const sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex"); |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 62 | |
| 63 | function run(command, args, options = {}) { | |
| 64 | const done = spawnSync(command, args, { stdio: "inherit", cwd: ROOT, ...options }); | |
| 65 | if (done.status !== 0) throw new Error(`${command} ${args.join(" ")} failed`); | |
| 66 | } | |
| 67 | ||
| 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. | 68 | /** |
| 69 | * The release's manifest, from the binaries in `dir`: each platform's file | |
| 70 | * with its SHA-256. Writes SHA256SUMS, manifest.json and latest.json there. | |
| 71 | */ | |
| 72 | export function manifest(dir, v, now = new Date()) { | |
| 73 | const files = {}; | |
| 74 | for (const [platform, target] of Object.entries(TARGETS)) { | |
| 75 | const path = join(dir, target.file); | |
| 76 | if (!existsSync(path)) continue; | |
| 77 | files[platform] = { name: target.file, sha256: sha256(readFileSync(path)) }; | |
| 78 | } | |
| 79 | if (Object.keys(files).length === 0) throw new Error(`no binaries in ${dir}: node scripts/cli-release.mjs build`); | |
| 80 | const body = { version: v, published_at: now.toISOString(), files }; | |
| 81 | const text = `${JSON.stringify(body, null, 2)}\n`; | |
| 82 | writeFileSync(join(dir, "manifest.json"), text); | |
| 83 | writeFileSync(join(dir, "latest.json"), text); | |
| 84 | writeFileSync(join(dir, "SHA256SUMS"), Object.values(files).map((f) => `${f.sha256} ${f.name}`).join("\n") + "\n"); | |
| 85 | return body; | |
| 86 | } | |
| 87 | ||
| Merge branch 'worktree-agent-a3b9b1f730f55b5bf' | 88 | /** |
| 89 | * The builder container's flags. Its target is target/zig in the | |
| 90 | * checkout, which the workflows cache (cli-release-…). In a workflow job, | |
| 91 | * Cargo's registry is the job's own ~/.cargo/registry, which the crates | |
| 92 | * entry (cargo-crates-…) restores; elsewhere a Docker volume keeps it. | |
| 93 | */ | |
| 94 | export function builderArgs({ root = ROOT, env = process.env, home = homedir() } = {}) { | |
| 95 | const registry = env.GITHUB_ACTIONS === "true" ? `${join(env.CARGO_HOME || join(home, ".cargo"), "registry").replaceAll("\\", "/")}` : "g1t-cargo-registry"; | |
| 96 | return [ | |
| 97 | "-e", "CARGO_TARGET_DIR=/src/target/zig", | |
| 98 | "-v", `${root.replaceAll("\\", "/")}:/src`, | |
| 99 | "-v", `${registry}:/usr/local/cargo/registry`, | |
| 100 | "-w", "/src", BUILDER, | |
| 101 | ]; | |
| 102 | } | |
| 103 | ||
| 104 | /** | |
| 105 | * What the container runs. It runs as root, so on a host with users what | |
| 106 | * it wrote is handed back to the one running this, for the cache to save | |
| 107 | * and prune. | |
| 108 | */ | |
| 109 | export function builderScript(triples, owner = process.getuid ? `${process.getuid()}:${process.getgid()}` : null) { | |
| 110 | const build = `set -e; for t in ${triples}; do rustup target add $t >/dev/null 2>&1; cargo zigbuild --release --locked --package g1t --target $t; done`; | |
| 111 | return owner ? `trap 'chown -R ${owner} /src/target/zig /usr/local/cargo/registry' EXIT; ${build}` : build; | |
| 112 | } | |
| 113 | ||
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 114 | function build() { |
| 115 | const v = version(); | |
| 116 | const dir = outDir(v); | |
| 117 | mkdirSync(dir, { recursive: true }); | |
| 118 | const triples = Object.values(TARGETS).map((t) => t.triple).join(" "); | |
| 119 | // One container builds every platform; its target folder is kept apart | |
| 120 | // from the host's so the two never mix. | |
| Merge branch 'worktree-agent-a3b9b1f730f55b5bf' | 121 | run("docker", ["run", "--rm", ...builderArgs(), "sh", "-c", builderScript(triples)], { 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. | 122 | for (const target of Object.values(TARGETS)) { |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 123 | 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. | 124 | writeFileSync(join(dir, target.file), readFileSync(built)); |
| 125 | } | |
| 126 | const m = manifest(dir, v); | |
| 127 | console.log(`built ${Object.keys(m.files).length} binaries of g1t ${v} into ${dir}`); | |
| 128 | } | |
| 129 | ||
| 130 | function privateKey(text = process.env.RUNNER_RELEASE_KEY) { | |
| 131 | if (!text) throw new Error("RUNNER_RELEASE_KEY is not set"); | |
| 132 | return createPrivateKey({ key: Buffer.from(text, "base64"), format: "der", type: "pkcs8" }); | |
| 133 | } | |
| 134 | ||
| 135 | function signRelease() { | |
| 136 | const dir = outDir(); | |
| 137 | const bytes = readFileSync(join(dir, "latest.json")); | |
| 138 | writeFileSync(join(dir, "latest.json.sig"), signBytes(bytes, privateKey())); | |
| 139 | console.log(`signed g1t ${version()}`); | |
| 140 | } | |
| 141 | ||
| 142 | function verifyRelease() { | |
| 143 | const dir = outDir(); | |
| 144 | const publicRaw = process.env.G1T_RUNNER_RELEASE_KEY; | |
| 145 | if (!publicRaw) throw new Error("G1T_RUNNER_RELEASE_KEY is not set"); | |
| 146 | const ok = verifyBytes(readFileSync(join(dir, "latest.json")), readFileSync(join(dir, "latest.json.sig"), "utf8"), publicRaw); | |
| 147 | if (!ok) throw new Error("latest.json's signature is not the release key's"); | |
| 148 | const m = JSON.parse(readFileSync(join(dir, "latest.json"), "utf8")); | |
| 149 | for (const file of Object.values(m.files)) { | |
| 150 | if (sha256(readFileSync(join(dir, file.name))) !== file.sha256) throw new Error(`${file.name} is not the file the manifest names`); | |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 151 | } |
| 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. | 152 | console.log(`g1t ${m.version}: the signature and every file check out`); |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 153 | } |
| 154 | ||
| 155 | function publish(dryRun) { | |
| 156 | const v = version(); | |
| 157 | const dir = outDir(v); | |
| 158 | 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. | 159 | const signed = existsSync(join(dir, "latest.json.sig")); |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 160 | const put = (key, file, type) => { |
| 161 | if (dryRun) return console.log(`would put ${key}`); | |
| 162 | run("npx", ["wrangler", "r2", "object", "put", `${BUCKET}/${key}`, "--file", file, "--remote", "--content-type", type], { | |
| 163 | shell: process.platform === "win32", | |
| 164 | // Away from the repository's .env, which may hold a token meant for | |
| 165 | // something else (as scripts/deploy does). | |
| 166 | cwd: join(ROOT, "apps/web"), | |
| 167 | }); | |
| 168 | }; | |
| 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. | 169 | const m = JSON.parse(readFileSync(join(dir, "manifest.json"), "utf8")); |
| 170 | for (const file of Object.values(m.files)) put(`cli/${v}/${file.name}`, join(dir, file.name), "application/octet-stream"); | |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 171 | put(`cli/${v}/manifest.json`, join(dir, "manifest.json"), "application/json"); |
| 172 | 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. | 173 | if (signed) put(`cli/${v}/latest.json.sig`, join(dir, "latest.json.sig"), "text/plain"); |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 174 | // 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. | 175 | if (signed) put("cli/latest.json.sig", join(dir, "latest.json.sig"), "text/plain"); |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 176 | put("cli/latest.json", join(dir, "latest.json"), "application/json"); |
| 177 | } | |
| 178 | ||
| 179 | if (process.argv[1]?.replaceAll("\\", "/").endsWith("scripts/cli-release.mjs")) { | |
| 180 | const [command, ...rest] = process.argv.slice(2); | |
| 181 | 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. | 182 | else if (command === "manifest") manifest(outDir(), version()); |
| 183 | else if (command === "sign") signRelease(); | |
| 184 | else if (command === "verify") verifyRelease(); | |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 185 | else if (command === "publish") publish(rest.includes("--dry-run")); |
| 186 | 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. | 187 | console.error("usage: node scripts/cli-release.mjs build|manifest|sign|verify|publish [--dry-run]"); |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 188 | process.exit(2); |
| 189 | } | |
| 190 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.