|
11 | 11 | // setFeedURL). electron-updater resolves the prefixed name with |
12 | 12 | // `new URL(name, base)`, so no client change is needed. |
13 | 13 | // |
| 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 | +// |
14 | 18 | // Produces, for base https://…/codev: |
15 | 19 | // <out>/latest-mac.yml -> …/codev/latest-mac.yml |
16 | 20 | // <out>/<version>/<artifacts> -> …/codev/<version>/… |
| 21 | +// -> …/codev/latest/… (copied in-bucket) |
17 | 22 | import { $ } from "bun" |
18 | 23 | import { copyFile, mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises" |
19 | 24 | import { join } from "node:path" |
20 | 25 |
|
| 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 | + |
21 | 30 | const distDir = argValue("--dist") ?? join(import.meta.dir, "..", "dist") |
22 | 31 | const outDir = argValue("--out") ?? join(distDir, "release") |
23 | 32 |
|
@@ -84,14 +93,36 @@ const upload = (from: string, to: string) => |
84 | 93 | ? $`mc cp ${from} ${target}/${to}` |
85 | 94 | : Promise.resolve(console.log(` mc cp ${from} <alias>/<bucket>/codev/${to}`)) |
86 | 95 |
|
| 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 | + |
87 | 110 | if (!target) console.log("\nCODEV_UPDATE_TARGET is unset; run these where the storage credentials live:") |
88 | 111 | // Artifacts first, manifests last: a client that reads the manifest must find |
89 | 112 | // 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) |
92 | 114 | for (const file of await readdir(join(outDir, version))) |
93 | 115 | 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 | + |
95 | 126 | for (const manifest of manifests) await upload(join(outDir, manifest), manifest) |
96 | 127 |
|
97 | 128 | function argValue(flag: string) { |
|
0 commit comments