Ctrl K
ComunidadGuías…

desktop/updater-manifest.mjs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// Adds the update bundle `tauri build` just made to latest.json, the manifest
// installed apps poll for updates, and says what to publish where.
//
// The URLs point at the GitHub release for the version, which must exist
// with the bundles attached before the manifest lands on main.

import { execFileSync } from "node:child_process";
import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
import { basename, dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { arch, platform } from "node:process";

const here = dirname(fileURLToPath(import.meta.url));
const manifestPath = join(here, "latest.json");

const metadata = JSON.parse(
  execFileSync("cargo", ["metadata", "--format-version", "1", "--no-deps", "--manifest-path", join(here, "src-tauri/Cargo.toml")], { encoding: "utf8" }),
);
const version = metadata.packages.find((p) => p.name === "wispers-access-desktop").version;
const bundleDir = join(metadata.target_directory, "release", "bundle");
const config = JSON.parse(readFileSync(join(here, "src-tauri", "tauri.conf.json"), "utf8"));
// The base config names the staging app; release.mjs builds with the
// production overlay, whose product name the bundles carry.
const productName = JSON.parse(readFileSync(join(here, "src-tauri", "tauri.prod.conf.json"), "utf8")).productName;

/** The bundle in the bundle directory's `dir` whose name ends in `suffix`.
 * `tauri build` leaves older builds' bundles in the same directory, and
 * `0.1.1` and `1.0.0-rc1` sort before `1.0.0`, so a bundle's name must
 * contain the version being released. The macOS tarball carries no version
 * in its name, so for it the newest file wins. */
function find(dir, suffix) {
  const path = join(bundleDir, dir);
  if (!existsSync(path)) return undefined;
  const candidates = readdirSync(path)
    .filter((f) => f.endsWith(suffix) && (f === `${productName}${suffix}` || f.includes(`_${version}_`)))
    .map((f) => join(path, f))
    .sort((a, b) => statSync(b).mtimeMs - statSync(a).mtimeMs);
  return candidates[0];
}

/** The update bundles and the platform keys the updater looks them up under.
 * Linux has one per package format, since an installed app updates in its own
 * format: the updater looks for `linux-<arch>-<format>` first. */
function hostBundles() {
  const cpu = arch === "arm64" ? "aarch64" : "x86_64";
  switch (platform) {
    case "darwin":
      return [{ key: `darwin-${cpu}`, bundle: find("macos", ".app.tar.gz") }];
    case "win32":
      return [{ key: `windows-${cpu}`, bundle: find("nsis", "-setup.exe") }];
    case "linux":
      return [
        { key: `linux-${cpu}-appimage`, bundle: find("appimage", ".AppImage") },
        { key: `linux-${cpu}-deb`, bundle: find("deb", ".deb") },
      ];
    default:
      return [];
  }
}

const hosts = hostBundles();
if (hosts.length === 0) {
  console.error(`no update bundles for ${platform}`);
  process.exit(1);
}
for (const host of hosts) {
  if (!host.bundle || !existsSync(`${host.bundle}.sig`)) {
    console.error(`no signed ${host.key} update bundle under ${bundleDir}; build with the updater key set`);
    process.exit(1);
  }
  // The bundler only warns when the private key isn't the public key's half,
  // and installed apps would reject the update.
  host.signature = readFileSync(`${host.bundle}.sig`, "utf8").trim();
  if (keyId(host.signature) !== keyId(config.plugins.updater.pubkey)) {
    console.error(`${host.bundle}.sig is not by the key in tauri.conf.json; build with the updater key`);
    process.exit(1);
  }
}

/** The key ID in a base64'd minisign key or signature file. */
function keyId(file) {
  const line = Buffer.from(file, "base64").toString("utf8").split("\n")[1];
  return Buffer.from(line, "base64").subarray(2, 10).toString("hex");
}

const release = `desktop-v${version}`;
// GitHub turns the spaces in asset names into dots.
const asset = (bundle) => basename(bundle).replaceAll(" ", ".");
const hostKeys = hosts.map((host) => host.key);

let manifest = existsSync(manifestPath) ? JSON.parse(readFileSync(manifestPath, "utf8")) : {};
if (manifest.version !== version) {
  // Each platform's machine adds its own entry, so the other's must already
  // be in this checkout when it was first.
  console.warn(`latest.json is for ${manifest.version}, starting it over for ${version}. If another platform has released ${version} already, pull main and run this again.`);
  manifest = { version, platforms: {} };
}
// The first platform's machine creates the release, the next ones add to it.
const releaseExists = Object.keys(manifest.platforms).some((key) => !hostKeys.includes(key));
manifest.pub_date = new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
for (const host of hosts) {
  manifest.platforms[host.key] = {
    url: `https://github.com/s-te-ch/wispers-access/releases/download/${release}/${asset(host.bundle)}`,
    signature: host.signature,
  };
}
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);

const extra = platform === "darwin" ? readdirSync(join(bundleDir, "dmg")).filter((f) => f.endsWith(".dmg")).map((f) => join(bundleDir, "dmg", f)) : [];
const files = [...extra, ...hosts.flatMap((host) => [host.bundle, `${host.bundle}.sig`])].map((f) => `"${f}"`).join(" ");
const publish = releaseExists
  ? `gh release upload ${release} --clobber ${files}`
  : `gh release create ${release} --title "Wispers Access desktop ${version}" --generate-notes ${files}`;
console.log(`
Wrote desktop/latest.json for ${version} (${Object.keys(manifest.platforms).join(", ")}). To publish:

  ${publish}

then commit desktop/latest.json to main.
`);