runAnnounce
uses node:path
Runs tcm announce: lists this codebase as the signed-in account (its URL, name and description kept for next
time), says whether people can read it there, or with --withdraw stops listing it.
import { atprotoSession } from "#codebase/tcmts/atproto"; import { ANNOUNCE_SCOPE, announceCodebase, CODEBASE_SETTING, withdrawCodebase } from "#codebase/tcmts/cli/announce"; import { routeAnnounce } from "#codebase/tcmts/cli/commands"; import { execute } from "#codebase/tcmts/cli/main"; import type { Context } from "#codebase/tcmts/cli/run"; import { bold, dim, green } from "#codebase/tcmts/cli/style"; import { HttpRemote } from "#codebase/tcmts/cli/sync"; import type { RemoteConfig } from "#codebase/tcmts/cli/sync"; import { UsageError } from "#codebase/tcmts/cli/ui"; import { codebaseLexicon, codebaseRecord, isCodebaseName } from "#codebase/tcmts/records"; import { parse } from "@bomb.sh/router"; import path_1 from "node:path"; export async function runAnnounce({ cb, cmd, rest }: Context): Promise<number> { const m = execute(cmd, parse(routeAnnounce(), { argv: rest })).model; const keptSetting = cb.store.getSetting(CODEBASE_SETTING); const kept = keptSetting ? (JSON.parse(keptSetting) as { name: string; url: string; description: string }) : undefined; const name = m.name ?? kept?.name ?? path_1 .basename(cb.lay.root) .replace(/[^A-Za-z0-9._-]+/g, "-") .replace(/^[._-]+/, "") .slice(0, 64); if (!isCodebaseName(name)) throw new UsageError( `--name ${name}: a codebase's name is letters, digits, '.', '_' and '-' (64 at most), from a letter or digit`, ); const sign = (why: string) => atprotoSession({ who: m.as, scope: ANNOUNCE_SCOPE, why }); if (m.withdraw) { if (m.url !== undefined || m.description !== undefined) throw new UsageError("--withdraw stops listing it: give no URL or --description with it"); const session = await sign( `withdrawing ${name} deletes a record from your AT Protocol repository`, ); const who = session.handle ? `@${session.handle}` : session.did; if (!(await withdrawCodebase(session, name))) { console.log(`${bold(name)} isn't listed as ${who}`); return 1; } console.log(`${green("withdrawn")} ${bold(name)}: registries stop listing it as ${who}'s`); return 0; } const origin = cb.store.getSetting("remote.origin"); const originUrl = origin && (JSON.parse(origin) as RemoteConfig).url; const url = m.url ?? kept?.url ?? (originUrl && /^https?:\/\//.test(originUrl) ? originUrl : undefined); if (!url) throw new UsageError( "listing this codebase needs the URL it's served at: tcm announce <url> (a `tcm serve --public` server; kept for next time)", ); const most = codebaseLexicon.defs.main.record.properties; if (!/^https?:\/\/[^/]/i.test(url) || url.length > most.url.maxLength) throw new UsageError( `${url}: a codebase is listed at a server's URL (http or https), as \`tcm serve\` serves it`, ); const description = (m.description ?? kept?.description ?? "").trim(); const graphemes = [...new Intl.Segmenter().segment(description)].length; if ( graphemes > most.description.maxGraphemes || Buffer.byteLength(description) > most.description.maxLength ) throw new UsageError( `--description is ${graphemes} characters: a listing says what it is in ${most.description.maxGraphemes} at most`, ); const session = await sign(`listing ${name} writes a record to your AT Protocol repository`); const { uri, was } = await announceCodebase(session, codebaseRecord({ name, url, description })); cb.store.setSetting(CODEBASE_SETTING, JSON.stringify({ name, url, description })); const who = session.handle ? `@${session.handle}` : session.did; console.log( was === "same" ? `${bold(name)} is already listed as ${who}, as it is: ${uri}` : `${green(was === "new" ? "listed" : "listed again")} ${bold(name)} as ${who}${was === "updated" ? " (as it is now)" : ""}: ${uri}`, ); console.log(dim(` served at ${url}`)); // (whether people can read it there: asked as anyone would, with no token) const why = await new HttpRemote(url).whoami().then( () => undefined, (e: unknown) => (e as Error).message.split("\n")[0], ); if (why) console.log( dim( ` ${url} didn't answer without a token (${why}): people can't browse it until it does (\`tcm serve --public\`)`, ), ); return 0; }
uses
- tcmts.atproto.atprotoSession
- tcmts.cli.announce.ANNOUNCE_SCOPE
- tcmts.cli.announce.CODEBASE_SETTING
- tcmts.cli.announce.announceCodebase
- tcmts.cli.announce.withdrawCodebase
- tcmts.cli.commands.routeAnnounce
- tcmts.cli.main.execute
- tcmts.cli.run.Context
- tcmts.cli.style.bold
- tcmts.cli.style.dim
- tcmts.cli.style.green
- tcmts.cli.sync.HttpRemote
- tcmts.cli.sync.RemoteConfig
- tcmts.cli.ui.UsageError
- tcmts.records.codebaseLexicon
- tcmts.records.codebaseRecord
- tcmts.records.isCodebaseName