runRelease

tcmts.cli.run.runRelease · #pt6uvyrw · term · runs on the server

uses node:path

Runs tcm release.

import { atprotoSession } from "#codebase/tcmts/atproto";
import type { OAuthSession } from "#codebase/tcmts/atproto";
import { ANNOUNCE_SCOPE, announceAndTell, announceSources } from "#codebase/tcmts/cli/announce";
import { routeRelease } from "#codebase/tcmts/cli/commands";
import { author } from "#codebase/tcmts/cli/contributions";
import { execute, printCheck, releaseAt, releaseSpec, when } from "#codebase/tcmts/cli/main";
import { findPackage, packagesIn } from "#codebase/tcmts/cli/manifests";
import { loadMirror, recordRelease, releaseCommit } from "#codebase/tcmts/cli/mirror";
import {
  blockers, contributionsOn, generatedNotes, MARKER, planRelease, reasonFor, releasePackage, writeDraft,
} from "#codebase/tcmts/cli/releases";
import type { Context } from "#codebase/tcmts/cli/run";
import { bold, dim, green, red } from "#codebase/tcmts/cli/style";
import { listRemotes } from "#codebase/tcmts/cli/sync";
import { choose, UsageError } from "#codebase/tcmts/cli/ui";
import { shortHash } from "#codebase/tcmts/core/hash";
import { parse } from "@bomb.sh/router";
import path_1 from "node:path";

export async function runRelease({ cb, cmd, rest, cwd }: Context): Promise<number> {
  const r = execute(cmd, parse(routeRelease(), { argv: rest }));
  if (r.route === "/view") {
    const spec =
      r.model.release ??
      (await choose(
        "release view <package>@<version>",
        "Which release?",
        cb.store.releases().map((x) => ({
          value: `${x.package}@${x.version}`,
          label: `${x.package}@${x.version}`,
          hint: when(x.ts),
        })),
        false,
      ));
    const rel = releaseAt(cb, spec);
    console.log(rel.notes.replace(`${MARKER}\n\n`, "").trimEnd());
    return 0;
  }
  if (r.route === "/check") {
    const plan = planRelease(cb, r.model.package, undefined, true);
    printCheck(plan);
    // (and whether releasing it would be refused, as it stands)
    const rows = cb.store.names();
    const blocked = blockers(cb, plan.pkg, packagesIn(cb, rows), rows);
    if (!blocked) return 0;
    console.log(red(`not released as it stands: ${blocked}`));
    return 1;
  }
  const m = r.model;
  const { name, version } = releaseSpec(m.package);
  if (m.draft) {
    const plan = planRelease(cb, name, version, m.force);
    const file = writeDraft(
      cb,
      plan,
      generatedNotes(cb, plan, [
        ...cb.store.contributions(),
        ...(await contributionsOn(cb, m.remote)),
      ]),
    );
    console.log(
      `wrote ${path_1.relative(cwd, file)} (${plan.pkg.name}'s next release, ${plan.version} as things stand): write what it's about above the marker, then \`tcm release ${plan.pkg.name}\`; the changes below the marker are regenerated`,
    );
    return 0;
  }
  // announcing: where it's fetched from and who announces it are settled first, so a release isn't made and then
  // left unannounced for want of them
  let announcing: { sources: string[]; session: OAuthSession } | undefined;
  if (m.announce) {
    const pkg = name ?? findPackage(packagesIn(cb, cb.store.names()), undefined).name;
    const sources = announceSources(cb, pkg, m.source);
    announcing = {
      sources,
      session: await atprotoSession({
        who: m.as,
        scope: ANNOUNCE_SCOPE,
        why: `announcing ${pkg} writes a record to your AT Protocol repository`,
      }),
    };
    const existing = version === undefined ? undefined : cb.store.release(pkg, version);
    if (existing) {
      await announceAndTell(cb, existing, announcing);
      return 0;
    }
  } else if (m.source.length || m.as !== undefined)
    throw new UsageError("--source and --as say how to announce: give --announce too");
  // a mirrored codebase's releases are kept with its files, in git: made from a commit, so they can be made again
  const mirror = loadMirror(cb);
  let commit: string | undefined;
  try {
    commit = mirror && releaseCommit(mirror);
  } catch (e) {
    throw new UsageError((e as Error).message);
  }
  const { release: rel, plan } = await releasePackage(cb, {
    name,
    version,
    notesFile: m.notes === undefined ? undefined : path_1.resolve(cwd, m.notes),
    remote: m.remote,
    force: m.force,
    author: author(),
  });
  console.log(
    `${green("released")} ${bold(`${rel.package}@${rel.version}`)} ${dim(`as ${shortHash(rel.causal)}${plan.prev ? `, after ${plan.prev.version}` : ""}: ${reasonFor(plan)}`)}\n`,
  );
  console.log(rel.notes.replace(`${MARKER}\n\n`, "").trimEnd());
  if (mirror && commit) {
    recordRelease(mirror, {
      package: rel.package,
      version: rel.version,
      commit,
      notes: rel.notes,
      author: rel.author,
      ts: rel.ts,
    });
    console.log(
      dim(
        `\n  recorded in ${path_1.relative(cwd, path_1.join(mirror.dir, "tcm-releases.json"))}, from commit ${commit.slice(0, 8)}: commit it, and the release can be made again from git`,
      ),
    );
  }
  if (announcing) {
    console.log();
    try {
      await announceAndTell(cb, rel, announcing);
    } catch (e) {
      console.log(
        `${rel.package}@${rel.version} is released, but not announced: ${(e as Error).message}\n  ${dim(`\`tcm release ${rel.package}@${rel.version} --announce\` announces it`)}`,
      );
      return 1;
    }
  } else if (listRemotes(cb).length) console.log(dim(`\n  \`tcm push\` publishes it`));
  return 0;
}

uses

used by