Skip to content

Commit 500f64c

Browse files
Quickbeardclaude
andcommitted
feat(desktop): mirror each release to a stable bucket prefix
A download link had to name /codev/<version>/<file>, so every link written down anywhere — docs, an email, a chat message — died at the next release. stage-release now also copies each installer to /codev/latest/<file>. Both sides are already in the bucket, so mc does a server-side copy: the mirror costs storage, not a second upload of ~1.8GB. Blockmaps are skipped, since they only mean anything beside the artifact a manifest names, which is the versioned copy. Nothing reads the prefix. The manifests still name <version>/ and that is all an installed build resolves, so this cannot affect auto-update. Also stops uploading every artifact once per channel file. The loop uploaded a whole version folder for each manifest, and every manifest of a release carries the same version — so a four-channel release uploaded each artifact four times. Deduping the versions first makes it once. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 03b4085 commit 500f64c

1 file changed

Lines changed: 34 additions & 3 deletions

File tree

‎packages/desktop/scripts/stage-release.ts‎

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,22 @@
1111
// setFeedURL). electron-updater resolves the prefixed name with
1212
// `new URL(name, base)`, so no client change is needed.
1313
//
14+
// Each release is also mirrored to a stable prefix, so a download link can be
15+
// written down once and keep working. That is for humans only: the manifests
16+
// name <version>/ and an installed build never resolves anything else.
17+
//
1418
// Produces, for base https://…/codev:
1519
// <out>/latest-mac.yml -> …/codev/latest-mac.yml
1620
// <out>/<version>/<artifacts> -> …/codev/<version>/…
21+
// -> …/codev/latest/… (copied in-bucket)
1722
import { $ } from "bun"
1823
import { copyFile, mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises"
1924
import { join } from "node:path"
2025

26+
// A folder named "latest" beside the latest*.yml files. Confusable on a
27+
// listing, but it is the name a download link is expected to have.
28+
const STABLE_PREFIX = "latest"
29+
2130
const distDir = argValue("--dist") ?? join(import.meta.dir, "..", "dist")
2231
const outDir = argValue("--out") ?? join(distDir, "release")
2332

@@ -84,14 +93,36 @@ const upload = (from: string, to: string) =>
8493
? $`mc cp ${from} ${target}/${to}`
8594
: Promise.resolve(console.log(` mc cp ${from} <alias>/<bucket>/codev/${to}`))
8695

96+
// Both sides are already in the bucket, so mc issues a server-side copy and no
97+
// bytes travel through here — the mirror costs storage, not another upload.
98+
const mirror = (from: string, to: string) =>
99+
target
100+
? $`mc cp ${target}/${from} ${target}/${to}`
101+
: Promise.resolve(console.log(` mc cp <alias>/<bucket>/codev/${from} <alias>/<bucket>/codev/${to}`))
102+
103+
// Every manifest of a release carries the same version, and the loop below
104+
// uploads a version folder whole — so without deduping, four channel files mean
105+
// uploading every artifact four times.
106+
const versions = new Set<string>()
107+
for (const manifest of manifests)
108+
versions.add(/^version:\s*(\S+)\s*$/m.exec(await readFile(join(outDir, manifest), "utf8"))![1]!)
109+
87110
if (!target) console.log("\nCODEV_UPDATE_TARGET is unset; run these where the storage credentials live:")
88111
// Artifacts first, manifests last: a client that reads the manifest must find
89112
// every file it names already in place.
90-
for (const manifest of manifests) {
91-
const version = /^version:\s*(\S+)\s*$/m.exec(await readFile(join(outDir, manifest), "utf8"))![1]!
113+
for (const version of versions)
92114
for (const file of await readdir(join(outDir, version)))
93115
await upload(join(outDir, version, file), `${version}/${file}`)
94-
}
116+
117+
// Then the stable prefix. Nothing reads it — the manifests name <version>/ and
118+
// that is all an installed build ever resolves — it exists so a download link
119+
// can be written down once, in the docs or an email, and keep working.
120+
// Blockmaps are left out: they only mean anything beside the artifact a
121+
// manifest names, which is the versioned copy.
122+
for (const version of versions)
123+
for (const file of await readdir(join(outDir, version)))
124+
if (!file.endsWith(".blockmap")) await mirror(`${version}/${file}`, `${STABLE_PREFIX}/${file}`)
125+
95126
for (const manifest of manifests) await upload(join(outDir, manifest), manifest)
96127

97128
function argValue(flag: string) {

0 commit comments

Comments
 (0)