ci(release): check latest.json with one jq filter that the tests also run
Review follow-ups of PR #416. The check that release.yml runs before deleting the published manifest moves from an inline jq program to scripts/check-latest-json.jq, which release.yml applies with `jq -e --arg version ... -f`. scripts/build-latest-json.test.mjs now runs that same filter on every pull request: on the script's output, on a wrong version, on nine broken manifests and on the shape published up to v0.17.1. It also compares the key lists of the script, of the filter and of verify-release.sh, so a list left behind shows before the tag. Two new cases pin the version argument: 0.18.0 must come out as 0.18.0 (the other fixtures pass 0.17.1, the version the bundle names carry, so a script that ignored its argument passed them all), and an empty version must fail. Both mutations survived the review's pass. No change to what the manifest holds: still four keys. Refs #320 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
e8306aa260
commit
ba3db7c35d
6 changed files with 145 additions and 16 deletions
|
|
@ -44,7 +44,7 @@ updated: 2026-10-10
|
||||||
bash .claude/skills/release/scripts/verify-release.sh vX.Y.Z
|
bash .claude/skills/release/scripts/verify-release.sh vX.Y.Z
|
||||||
```
|
```
|
||||||
Le script (token `~/.forgejo-token`) lit la release par tag, compte les 7 artefacts **par suffixe** (`.exe` NSIS, `.deb`, `.rpm`, leurs 3 signatures `.sig`, `latest.json` — les noms exacts varient selon la version de Tauri), puis télécharge `latest.json` (il pilote l'auto-update des installations existantes) et vérifie `version` (il affiche `pub_date` et la longueur des notes), puis exige **exactement quatre clés** (#320) : `linux-x86_64`, `linux-x86_64-deb`, `linux-x86_64-rpm` et `windows-x86_64`. Pour chacune : l'extension de l'URL (`.deb`, `.deb`, `.rpm`, `.exe`), une signature non vide dont le `file:` (commentaire de confiance minisign, signature décodée du base64) est le nom de fichier de l'URL, décodé, et la version dans l'URL. **Il sort en 1 sur tout écart** (`MANQUANT`, `VIDE`, `MAUVAISE VERSION`, `MAUVAISE EXTENSION`, `DIFFÉRENT`, `INATTENDUE`) et finit par `VERDICT : OK` ou `VERDICT : ÉCHEC` : pas d'annonce de la release ni d'étape 10 avant un verdict OK. Un second argument (`verify-release.sh vX.Y.Z fichier.json`) contrôle un `latest.json` local au lieu de l'asset, pour éprouver les contrôles sur un fichier fabriqué.
|
Le script (token `~/.forgejo-token`) lit la release par tag, compte les 7 artefacts **par suffixe** (`.exe` NSIS, `.deb`, `.rpm`, leurs 3 signatures `.sig`, `latest.json` — les noms exacts varient selon la version de Tauri), puis télécharge `latest.json` (il pilote l'auto-update des installations existantes) et vérifie `version` (il affiche `pub_date` et la longueur des notes), puis exige **exactement quatre clés** (#320) : `linux-x86_64`, `linux-x86_64-deb`, `linux-x86_64-rpm` et `windows-x86_64`. Pour chacune : l'extension de l'URL (`.deb`, `.deb`, `.rpm`, `.exe`), une signature non vide dont le `file:` (commentaire de confiance minisign, signature décodée du base64) est le nom de fichier de l'URL, décodé, et la version dans l'URL. **Il sort en 1 sur tout écart** (`MANQUANT`, `VIDE`, `MAUVAISE VERSION`, `MAUVAISE EXTENSION`, `DIFFÉRENT`, `INATTENDUE`) et finit par `VERDICT : OK` ou `VERDICT : ÉCHEC` : pas d'annonce de la release ni d'étape 10 avant un verdict OK. Un second argument (`verify-release.sh vX.Y.Z fichier.json`) contrôle un `latest.json` local au lieu de l'asset, pour éprouver les contrôles sur un fichier fabriqué.
|
||||||
**Lire aussi l'étape « Generate latest.json » du journal du run** (même route web que ci-dessous) : `scripts/build-latest-json.sh` y nomme le paquet retenu pour chaque type (`build-latest-json: .rpm <- …`), puis le `latest.json` généré est affiché. Ce script, le délimiteur aléatoire des notes et l'assertion placée avant le `DELETE` du registre ne s'exercent qu'au tag : une assertion en échec arrête le job sur `ERROR: release-assets/latest.json failed its checks`, registre intact, et la release Forgejo reste créée sans mise à jour servie.
|
**Lire aussi l'étape « Generate latest.json » du journal du run** (même route web que ci-dessous) : `scripts/build-latest-json.sh` y nomme le paquet retenu pour chaque type (`build-latest-json: .rpm <- …`), puis le `latest.json` généré est affiché. Le script et le filtre `scripts/check-latest-json.jq` passent par les tests de `check-frontend` à chaque PR ; leur appel par `release.yml` et le délimiteur aléatoire des notes ne s'exercent qu'au tag. Un manifeste refusé par le filtre arrête le job sur `ERROR: release-assets/latest.json failed scripts/check-latest-json.jq`, avant le `DELETE` : registre intact, et la release Forgejo reste créée sans mise à jour servie.
|
||||||
Durée observée du run : 22 à 30 min sur les 12 dernières releases — poser le `Monitor` puis passer à autre chose.
|
Durée observée du run : 22 à 30 min sur les 12 dernières releases — poser le `Monitor` puis passer à autre chose.
|
||||||
**Lire aussi le Node.js du run.** `release.yml` n'est exercé qu'au tag : une montée de Node (Node 20 → 24 par #376) ne se prouve que dans ce journal. Il passe par la route web (l'API v1 des journaux répond 404). Le `run_number` vient de `/actions/tasks`, qui rend toute la liste, la plus récente en tête (le paramètre `limit` y est ignoré) :
|
**Lire aussi le Node.js du run.** `release.yml` n'est exercé qu'au tag : une montée de Node (Node 20 → 24 par #376) ne se prouve que dans ce journal. Il passe par la route web (l'API v1 des journaux répond 404). Le `run_number` vient de `/actions/tasks`, qui rend toute la liste, la plus récente en tête (le paramètre `limit` y est ignoré) :
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,9 @@ ok &= v == version
|
||||||
print(f"notes: {len(notes)} caractères — début: {notes[:120]!r}")
|
print(f"notes: {len(notes)} caractères — début: {notes[:120]!r}")
|
||||||
|
|
||||||
# The four keys (#320): tauri-plugin-updater looks up "{os}-{arch}-{installer}",
|
# The four keys (#320): tauri-plugin-updater looks up "{os}-{arch}-{installer}",
|
||||||
# then "{os}-{arch}". Each with the extension its URL must end with.
|
# then "{os}-{arch}". Each with the extension its URL must end with. Same set
|
||||||
|
# as scripts/build-latest-json.sh writes and scripts/check-latest-json.jq
|
||||||
|
# checks: scripts/build-latest-json.test.mjs reads this line and compares.
|
||||||
EXPECTED = {"linux-x86_64": ".deb", "linux-x86_64-deb": ".deb",
|
EXPECTED = {"linux-x86_64": ".deb", "linux-x86_64-deb": ".deb",
|
||||||
"linux-x86_64-rpm": ".rpm", "windows-x86_64": ".exe"}
|
"linux-x86_64-rpm": ".rpm", "windows-x86_64": ".exe"}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -194,17 +194,12 @@ jobs:
|
||||||
# Check the manifest before the DELETE below (#320): once the old
|
# Check the manifest before the DELETE below (#320): once the old
|
||||||
# version is deleted, installed apps find no update until a valid
|
# version is deleted, installed apps find no update until a valid
|
||||||
# one is uploaded. A failed check stops the job, registry untouched.
|
# one is uploaded. A failed check stops the job, registry untouched.
|
||||||
# Same four keys as scripts/build-latest-json.sh writes.
|
# The filter lives in scripts/check-latest-json.jq, which
|
||||||
if ! jq -e --arg version "${GITHUB_REF_NAME#v}" '
|
# scripts/build-latest-json.test.mjs also runs on every pull
|
||||||
.version == $version
|
# request, so its key list cannot drift from the script's unseen.
|
||||||
and (.platforms | keys) == ["linux-x86_64", "linux-x86_64-deb", "linux-x86_64-rpm", "windows-x86_64"]
|
if ! jq -e --arg version "${GITHUB_REF_NAME#v}" \
|
||||||
and all(.platforms[]; (.signature | type) == "string" and (.signature | length) > 0)
|
-f scripts/check-latest-json.jq release-assets/latest.json > /dev/null; then
|
||||||
and (.platforms["linux-x86_64"].url | endswith(".deb"))
|
echo "ERROR: release-assets/latest.json failed scripts/check-latest-json.jq, registry left as is:"
|
||||||
and (.platforms["linux-x86_64-deb"].url | endswith(".deb"))
|
|
||||||
and (.platforms["linux-x86_64-rpm"].url | endswith(".rpm"))
|
|
||||||
and (.platforms["windows-x86_64"].url | endswith(".exe"))
|
|
||||||
' release-assets/latest.json > /dev/null; then
|
|
||||||
echo "ERROR: release-assets/latest.json failed its checks, registry left as is:"
|
|
||||||
cat release-assets/latest.json
|
cat release-assets/latest.json
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -622,7 +622,7 @@ Déclenché par les tags `v*`. Un seul job, `build-and-release`, dans un contene
|
||||||
Fonctionnalités :
|
Fonctionnalités :
|
||||||
- Signature des binaires (clés TAURI_SIGNING_PRIVATE_KEY)
|
- Signature des binaires (clés TAURI_SIGNING_PRIVATE_KEY)
|
||||||
- JSON d'updater (`latest.json`) écrit par `scripts/build-latest-json.sh` à partir des assets ([#320](https://git.lacompagniemaximus.com/maximus/simpl-resultat/issues/320)). `tauri-plugin-updater` cherche `{os}-{arch}-{installer}`, puis `{os}-{arch}` : le fichier porte `linux-x86_64-deb` et `linux-x86_64-rpm` (une clé par paquet Linux), `linux-x86_64` (le `.deb`, en repli : raison, lecteurs connus et condition de retrait dans `docs/qa-update-cycle.md`) et `windows-x86_64` (l'installeur NSIS). Le script échoue s'il ne trouve pas exactement un `.deb`, un `.rpm` et un `-setup.exe`, chacun avec un `.sig` non vide ; le JSON vient de `jq` seul (notes lues comme données par `--rawfile`), avec les noms de fichiers encodés par `@uri`, comme à l'envoi des assets. Testé par `scripts/build-latest-json.test.mjs` (vitest, lancé par `check-frontend`), qui l'exécute sous `bash -e -o pipefail` comme `release.yml`.
|
- JSON d'updater (`latest.json`) écrit par `scripts/build-latest-json.sh` à partir des assets ([#320](https://git.lacompagniemaximus.com/maximus/simpl-resultat/issues/320)). `tauri-plugin-updater` cherche `{os}-{arch}-{installer}`, puis `{os}-{arch}` : le fichier porte `linux-x86_64-deb` et `linux-x86_64-rpm` (une clé par paquet Linux), `linux-x86_64` (le `.deb`, en repli : raison, lecteurs connus et condition de retrait dans `docs/qa-update-cycle.md`) et `windows-x86_64` (l'installeur NSIS). Le script échoue s'il ne trouve pas exactement un `.deb`, un `.rpm` et un `-setup.exe`, chacun avec un `.sig` non vide ; le JSON vient de `jq` seul (notes lues comme données par `--rawfile`), avec les noms de fichiers encodés par `@uri`, comme à l'envoi des assets. Testé par `scripts/build-latest-json.test.mjs` (vitest, lancé par `check-frontend`), qui l'exécute sous `bash -e -o pipefail` comme `release.yml`.
|
||||||
- JSON publié sur `https://git.lacompagniemaximus.com/api/packages/maximus/generic/simpl-resultat/latest/latest.json` (registre de paquets, endpoint de `tauri.conf.json`). Avant le `DELETE` de l'ancienne version, une assertion `jq` (version égale au tag, exactement les quatre clés, signatures non vides, extension de chaque URL) arrête le job si le fichier ne la passe pas : le registre reste intact.
|
- JSON publié sur `https://git.lacompagniemaximus.com/api/packages/maximus/generic/simpl-resultat/latest/latest.json` (registre de paquets, endpoint de `tauri.conf.json`). Avant le `DELETE` de l'ancienne version, le filtre `scripts/check-latest-json.jq` (version égale au tag, exactement les quatre clés, signatures non vides, extension de chaque URL) arrête le job si le fichier ne le passe pas : le registre reste intact. Le test du script applique ce même filtre à chaque PR et compare sa liste de clés à celles du script et de `verify-release.sh`, si bien qu'un écart se voit avant le tag.
|
||||||
- Release Forgejo automatique avec assets et release notes extraites du CHANGELOG.md, transmises par `GITHUB_OUTPUT` sous un délimiteur aléatoire (une ligne du CHANGELOG égale à un délimiteur fixe terminait la valeur)
|
- Release Forgejo automatique avec assets et release notes extraites du CHANGELOG.md, transmises par `GITHUB_OUTPUT` sous un délimiteur aléatoire (une ligne du CHANGELOG égale à un délimiteur fixe terminait la valeur)
|
||||||
|
|
||||||
**Node.js 24** ([#376](https://git.lacompagniemaximus.com/maximus/simpl-resultat/issues/376)), installé comme dans les trois autres workflows par le script NodeSource (`curl … | bash`). Ce script tourne en root dans le conteneur du job, et le Node.js qu'il installe lance ensuite `npx tauri build` avec `TAURI_SIGNING_PRIVATE_KEY` dans son environnement : le job confie à NodeSource (script et dépôt apt) la clé de signature de l'updater, et les jetons de publication des étapes suivantes. Risque accepté par Max le 2026-10-10, alternative écartée : un tarball Node.js épinglé. La chaîne Rust, installée de la même façon par `rustup`, est dans le même cas. Ce workflow ne tourne qu'au tag : la première release après #376 est la première sous Node 24, à vérifier par `node --version` dans le journal du run (étape 9 de `/release`).
|
**Node.js 24** ([#376](https://git.lacompagniemaximus.com/maximus/simpl-resultat/issues/376)), installé comme dans les trois autres workflows par le script NodeSource (`curl … | bash`). Ce script tourne en root dans le conteneur du job, et le Node.js qu'il installe lance ensuite `npx tauri build` avec `TAURI_SIGNING_PRIVATE_KEY` dans son environnement : le job confie à NodeSource (script et dépôt apt) la clé de signature de l'updater, et les jetons de publication des étapes suivantes. Risque accepté par Max le 2026-10-10, alternative écartée : un tarball Node.js épinglé. La chaîne Rust, installée de la même façon par `rustup`, est dans le même cas. Ce workflow ne tourne qu'au tag : la première release après #376 est la première sous Node 24, à vérifier par `node --version` dans le journal du run (étape 9 de `/release`).
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,11 @@
|
||||||
* URLs must percent-encode. Expected values are literals, never rebuilt from
|
* URLs must percent-encode. Expected values are literals, never rebuilt from
|
||||||
* the inputs.
|
* the inputs.
|
||||||
*
|
*
|
||||||
|
* It also runs scripts/check-latest-json.jq, the filter release.yml applies
|
||||||
|
* before deleting the published manifest, so that check is exercised on every
|
||||||
|
* pull request instead of only at the tag, and compares the key lists of the
|
||||||
|
* script, of that filter and of verify-release.sh.
|
||||||
|
*
|
||||||
* Plain .mjs on purpose: tsconfig only covers src/, so a .ts file here would
|
* Plain .mjs on purpose: tsconfig only covers src/, so a .ts file here would
|
||||||
* look type-checked without being so. Needs bash and jq on PATH;
|
* look type-checked without being so. Needs bash and jq on PATH;
|
||||||
* check-frontend.yml installs jq for this file.
|
* check-frontend.yml installs jq for this file.
|
||||||
|
|
@ -20,12 +25,15 @@
|
||||||
|
|
||||||
import { describe, it, expect, beforeAll, beforeEach, afterEach } from "vitest";
|
import { describe, it, expect, beforeAll, beforeEach, afterEach } from "vitest";
|
||||||
import { spawnSync } from "node:child_process";
|
import { spawnSync } from "node:child_process";
|
||||||
import { mkdtempSync, mkdirSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
import { mkdtempSync, mkdirSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
||||||
import { tmpdir } from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
import { dirname, join } from "node:path";
|
import { dirname, join } from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
const SCRIPT = join(dirname(fileURLToPath(import.meta.url)), "build-latest-json.sh");
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const SCRIPT = join(HERE, "build-latest-json.sh");
|
||||||
|
const CHECK = join(HERE, "check-latest-json.jq");
|
||||||
|
const VERIFY_RELEASE = join(HERE, "..", ".claude", "skills", "release", "scripts", "verify-release.sh");
|
||||||
const BASE = "https://git.example.test/owner/Repo/releases/download/v0.17.1";
|
const BASE = "https://git.example.test/owner/Repo/releases/download/v0.17.1";
|
||||||
|
|
||||||
const DEB = "Simpl Resultat_0.17.1_amd64.deb";
|
const DEB = "Simpl Resultat_0.17.1_amd64.deb";
|
||||||
|
|
@ -62,6 +70,22 @@ function expectFailure(result, message) {
|
||||||
expect(result.stderr).toContain(message);
|
expect(result.stderr).toContain(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The manifest the script writes for the three bundles of v0.17.1.
|
||||||
|
function builtManifest() {
|
||||||
|
writeAllAssets();
|
||||||
|
const result = run();
|
||||||
|
expect(result.status).toBe(0);
|
||||||
|
return JSON.parse(result.stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Runs scripts/check-latest-json.jq as release.yml does, on a manifest written
|
||||||
|
// to a file: an object is serialized, a string is written as is.
|
||||||
|
function check(manifest, version = "0.17.1") {
|
||||||
|
const file = join(work, "latest.json");
|
||||||
|
writeFileSync(file, typeof manifest === "string" ? manifest : JSON.stringify(manifest));
|
||||||
|
return spawnSync("jq", ["-e", "--arg", "version", version, "-f", CHECK, file], { encoding: "utf8" });
|
||||||
|
}
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
const jq = spawnSync("jq", ["--version"], { encoding: "utf8" });
|
const jq = spawnSync("jq", ["--version"], { encoding: "utf8" });
|
||||||
if (jq.status !== 0) {
|
if (jq.status !== 0) {
|
||||||
|
|
@ -193,4 +217,85 @@ describe("build-latest-json.sh", () => {
|
||||||
|
|
||||||
expectFailure(run([assets, "0.17.1", notesFile]), "usage: build-latest-json.sh");
|
expectFailure(run([assets, "0.17.1", notesFile]), "usage: build-latest-json.sh");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// The other tests pass 0.17.1, the version the bundle names also carry: a
|
||||||
|
// script that ignored its argument would pass them all.
|
||||||
|
it("takes the version from its argument, not from the bundle names", () => {
|
||||||
|
writeAllAssets();
|
||||||
|
|
||||||
|
const result = run([assets, "0.18.0", notesFile, BASE]);
|
||||||
|
|
||||||
|
expect(result.status).toBe(0);
|
||||||
|
expect(JSON.parse(result.stdout).version).toBe("0.18.0");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fails on an empty version", () => {
|
||||||
|
writeAllAssets();
|
||||||
|
|
||||||
|
expectFailure(run([assets, "", notesFile, BASE]), "empty version");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("check-latest-json.jq", () => {
|
||||||
|
it("passes the script's output for the tag's version", () => {
|
||||||
|
const result = check(builtManifest());
|
||||||
|
|
||||||
|
expect(result.status).toBe(0);
|
||||||
|
expect(result.stdout).toBe("true\n");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("refuses a manifest whose version is not the tag's", () => {
|
||||||
|
expect(check(builtManifest(), "0.18.0").status).not.toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
["a missing key", (m) => delete m.platforms["linux-x86_64-rpm"]],
|
||||||
|
["an extra key", (m) => (m.platforms["linux-x86_64-appimage"] = m.platforms["linux-x86_64-deb"])],
|
||||||
|
["the .rpm key on the .deb", (m) => (m.platforms["linux-x86_64-rpm"] = m.platforms["linux-x86_64-deb"])],
|
||||||
|
["the .deb key on the .rpm", (m) => (m.platforms["linux-x86_64-deb"] = m.platforms["linux-x86_64-rpm"])],
|
||||||
|
["the Windows key on a .sig", (m) => (m.platforms["windows-x86_64"].url += ".sig")],
|
||||||
|
["an empty signature", (m) => (m.platforms["linux-x86_64-deb"].signature = "")],
|
||||||
|
["a signature that is not a string", (m) => (m.platforms["windows-x86_64"].signature = 42)],
|
||||||
|
["a key without a URL", (m) => delete m.platforms["linux-x86_64-rpm"].url],
|
||||||
|
["a manifest without platforms", (m) => delete m.platforms],
|
||||||
|
])("refuses %s", (_, mutate) => {
|
||||||
|
const manifest = builtManifest();
|
||||||
|
mutate(manifest);
|
||||||
|
|
||||||
|
expect(check(manifest).status).not.toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("refuses the shape published up to v0.17.1, without a key per Linux package", () => {
|
||||||
|
const published = {
|
||||||
|
version: "0.17.1",
|
||||||
|
notes: "### Fixed",
|
||||||
|
pub_date: "2026-10-09T01:44:40Z",
|
||||||
|
platforms: {
|
||||||
|
"linux-x86_64": { signature: "RWRkZWItc2lnbmF0dXJl", url: `${BASE}/Simpl%20Resultat_0.17.1_amd64.deb` },
|
||||||
|
"windows-x86_64": { signature: "RWRleGUtc2lnbmF0dXJl", url: `${BASE}/Simpl%20Resultat_0.17.1_x64-setup.exe` },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(check(published).status).not.toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("refuses a file that is not JSON", () => {
|
||||||
|
expect(check('{"version": "0.17.1", "platforms": ').status).not.toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("key lists", () => {
|
||||||
|
it("are the same in the script, in check-latest-json.jq and in verify-release.sh", () => {
|
||||||
|
const manifest = builtManifest();
|
||||||
|
const written = Object.keys(manifest.platforms).sort();
|
||||||
|
|
||||||
|
// The filter compares the exact key set: passing the script's output
|
||||||
|
// proves its list is the script's.
|
||||||
|
expect(check(manifest).status).toBe(0);
|
||||||
|
// verify-release.sh keeps its own list, in a Python dict literal.
|
||||||
|
const dict = readFileSync(VERIFY_RELEASE, "utf8").match(/^EXPECTED = \{([^}]*)\}/m);
|
||||||
|
expect(dict).not.toBeNull();
|
||||||
|
const expected = [...dict[1].matchAll(/"([a-z0-9_-]+)":/g)].map((m) => m[1]).sort();
|
||||||
|
expect(expected).toStrictEqual(written);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
27
scripts/check-latest-json.jq
Normal file
27
scripts/check-latest-json.jq
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
# Check of latest.json, the updater manifest, before release.yml deletes the
|
||||||
|
# published one from the package registry (#320). Once that version is
|
||||||
|
# deleted, installed apps find no update until a valid one is uploaded, so a
|
||||||
|
# manifest that fails this check stops the job and leaves the registry as is.
|
||||||
|
#
|
||||||
|
# Run as: jq -e --arg version <X.Y.Z> -f scripts/check-latest-json.jq latest.json
|
||||||
|
# -e turns false, null and any jq error (missing field, wrong type, invalid
|
||||||
|
# JSON) into a non-zero exit status: the check fails closed.
|
||||||
|
#
|
||||||
|
# The key set must stay the one scripts/build-latest-json.sh writes and
|
||||||
|
# .claude/skills/release/scripts/verify-release.sh expects.
|
||||||
|
# scripts/build-latest-json.test.mjs runs this file on the script's output and
|
||||||
|
# on broken manifests, and compares the three lists, on every pull request
|
||||||
|
# through check-frontend: a misaligned list shows before the tag, not at it.
|
||||||
|
# Written for jq 1.6, the version ubuntu:22.04 ships.
|
||||||
|
#
|
||||||
|
# Not checked here: that each signature belongs to its own file. The script's
|
||||||
|
# test checks it before the merge, and verify-release.sh after publication
|
||||||
|
# (the file: field of the minisign trusted comment).
|
||||||
|
|
||||||
|
.version == $version
|
||||||
|
and (.platforms | keys) == ["linux-x86_64", "linux-x86_64-deb", "linux-x86_64-rpm", "windows-x86_64"]
|
||||||
|
and all(.platforms[]; (.signature | type) == "string" and (.signature | length) > 0)
|
||||||
|
and (.platforms["linux-x86_64"].url | endswith(".deb"))
|
||||||
|
and (.platforms["linux-x86_64-deb"].url | endswith(".deb"))
|
||||||
|
and (.platforms["linux-x86_64-rpm"].url | endswith(".rpm"))
|
||||||
|
and (.platforms["windows-x86_64"].url | endswith(".exe"))
|
||||||
Loading…
Reference in a new issue