announceRelease

tcmts.cli.announce.announceRelease · #on5tbrvk · term · runs anywhere

Write a release's record to the signed-in account's repository. A version is one record: announced again, it's left as it is when it says the same, has its sources replaced when only they differ, and is refused when it names something else.

import { pdsFetch } from "#codebase/tcmts/atproto";
import type { OAuthSession } from "#codebase/tcmts/atproto";
import { UsageError } from "#codebase/tcmts/cli/ui";
import { recordKey, RELEASE_COLLECTION } from "#codebase/tcmts/records";
import type { ReleaseRecord } from "#codebase/tcmts/records";

export async function announceRelease(
  s: OAuthSession,
  record: ReleaseRecord,
): Promise<{ uri: string; was: "new" | "same" | "updated" }> {
  const rkey = recordKey(record);
  const uri = `at://${s.did}/${RELEASE_COLLECTION}/${rkey}`;
  const q = new URLSearchParams({ repo: s.did, collection: RELEASE_COLLECTION, rkey });
  const got = await fetch(`${s.pds}/xrpc/com.atproto.repo.getRecord?${q}`, {
    headers: { accept: "application/json" },
  });
  const old = got.ok ? ((await got.json()) as { cid: string; value: ReleaseRecord }) : undefined;
  if (!old && got.status !== 400 && got.status !== 404)
    throw new Error(`reading ${uri}: ${got.status} ${await got.text().catch(() => "")}`);
  const same = (a: ReleaseRecord, b: ReleaseRecord, keys: (keyof ReleaseRecord)[]) =>
    keys.every((k) => JSON.stringify(a[k]) === JSON.stringify(b[k]));
  if (old) {
    if (!same(old.value, record, ["package", "version", "namespace", "entry", "hashFormat"]))
      throw new UsageError(
        `${uri} already announces ${record.package}@${record.version} as #${old.value.namespace}, not #${record.namespace}: a version names one release`,
      );
    if (same(old.value, record, ["notes", "sources", "createdAt"])) return { uri, was: "same" };
  }
  const write = (method: string, body: Record<string, unknown>) =>
    pdsFetch(s, `/xrpc/com.atproto.repo.${method}`, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ repo: s.did, collection: RELEASE_COLLECTION, rkey, record, ...body }),
    });
  const res = old
    ? await write("putRecord", { swapRecord: old.cid })
    : await write("createRecord", {});
  if (!res.ok) {
    const e = (await res.json().catch(() => ({}))) as { error?: string; message?: string };
    throw new Error(`writing ${uri}: ${res.status} ${e.message ?? e.error ?? ""}`.trim());
  }
  return { uri, was: old ? "updated" : "new" };
}

uses

used by