Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions packages/desktop/scripts/stage-release.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,22 @@
// setFeedURL). electron-updater resolves the prefixed name with
// `new URL(name, base)`, so no client change is needed.
//
// Each release is also mirrored to a stable prefix, so a download link can be
// written down once and keep working. That is for humans only: the manifests
// name <version>/ and an installed build never resolves anything else.
//
// Produces, for base https://…/codev:
// <out>/latest-mac.yml -> …/codev/latest-mac.yml
// <out>/<version>/<artifacts> -> …/codev/<version>/…
// -> …/codev/latest/… (copied in-bucket)
import { $ } from "bun"
import { copyFile, mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises"
import { join } from "node:path"

// A folder named "latest" beside the latest*.yml files. Confusable on a
// listing, but it is the name a download link is expected to have.
const STABLE_PREFIX = "latest"

const distDir = argValue("--dist") ?? join(import.meta.dir, "..", "dist")
const outDir = argValue("--out") ?? join(distDir, "release")

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

// Both sides are already in the bucket, so mc issues a server-side copy and no
// bytes travel through here — the mirror costs storage, not another upload.
const mirror = (from: string, to: string) =>
target
? $`mc cp ${target}/${from} ${target}/${to}`
: Promise.resolve(console.log(` mc cp <alias>/<bucket>/codev/${from} <alias>/<bucket>/codev/${to}`))

// Every manifest of a release carries the same version, and the loop below
// uploads a version folder whole — so without deduping, four channel files mean
// uploading every artifact four times.
const versions = new Set<string>()
for (const manifest of manifests)
versions.add(/^version:\s*(\S+)\s*$/m.exec(await readFile(join(outDir, manifest), "utf8"))![1]!)

if (!target) console.log("\nCODEV_UPDATE_TARGET is unset; run these where the storage credentials live:")
// Artifacts first, manifests last: a client that reads the manifest must find
// every file it names already in place.
for (const manifest of manifests) {
const version = /^version:\s*(\S+)\s*$/m.exec(await readFile(join(outDir, manifest), "utf8"))![1]!
for (const version of versions)
for (const file of await readdir(join(outDir, version)))
await upload(join(outDir, version, file), `${version}/${file}`)
}

// Then the stable prefix. Nothing reads it — the manifests name <version>/ and
// that is all an installed build ever resolves — it exists so a download link
// can be written down once, in the docs or an email, and keep working.
// Blockmaps are left out: they only mean anything beside the artifact a
// manifest names, which is the versioned copy.
for (const version of versions)
for (const file of await readdir(join(outDir, version)))
if (!file.endsWith(".blockmap")) await mirror(`${version}/${file}`, `${STABLE_PREFIX}/${file}`)

for (const manifest of manifests) await upload(join(outDir, manifest), manifest)

function argValue(flag: string) {
Expand Down
Loading