runPublish
uses node:url
Runs tcm publish: a standardSite(…) definition's records written as its account (only what changed, and its
posts' documents it no longer has removed), and whether the site says it's that publication.
import { atprotoSession } from "#codebase/tcmts/cli/atproto"; import { routePublish } from "#codebase/tcmts/cli/commands"; import { execute } from "#codebase/tcmts/cli/main"; import { PUBLISH_SCOPE, publishSite } from "#codebase/tcmts/cli/publish"; import type { Context } from "#codebase/tcmts/cli/run"; import { bold, dim, green, red, yellow } from "#codebase/tcmts/cli/style"; import { chooseName, UsageError } from "#codebase/tcmts/cli/ui"; import type { StandardSite } from "#codebase/tcmts/content"; import { exportId } from "#codebase/tcmts/core/hash"; import { materialize } from "#codebase/tcmts/loader"; import { parse } from "@bomb.sh/router"; import { pathToFileURL } from "node:url"; export async function runPublish({ cb, cmd, rest }: Context): Promise<number> { const m = execute(cmd, parse(routePublish(), { argv: rest })).model; const name = m.name ?? (await chooseName(cb, "publish <name> [--as <account>] [--dry-run]", "Publish what?", "value")); const h = cb.lookupOne(name, "value"); const rec = cb.store.getDef(h)!; if (rec.kind === "type") throw new UsageError(`${name} is a type`); // its value, here: the records, as its code makes them materialize([h], cb.source(), cb.lay); const mod = (await import(pathToFileURL(cb.modulePath(rec.componentHash)).href)) as Record< string, unknown >; const site = mod[exportId(h)] as StandardSite | undefined; if ( !site || typeof site !== "object" || site.publication?.collection !== "site.standard.publication" || !Array.isArray(site.documents) ) throw new UsageError( `${name} isn't a standard.site publication: make it with standardSite(…), the content library's (\`tcm lib add tcm content\`)`, ); const session = await atprotoSession({ who: m.as ?? site.did, scope: PUBLISH_SCOPE, why: `publishing ${name} writes records to your AT Protocol repository`, }); if (session.did !== site.did) throw new UsageError(`${name}'s records are ${site.did}'s: publish it as that account (--as)`); const who = session.handle ? `@${session.handle}` : session.did; const dry = m["dry-run"]; const c = await publishSite(session, site, { dryRun: dry, tell: (what, uri) => console.log(` ${what === "removed" ? red(what) : green(what)} ${uri}`), }); console.log( `${dry ? "would publish" : "published"} ${bold(name)} as ${who}: ${c.added} new, ${c.updated} updated, ${c.removed} removed, ${c.same} as they were`, ); console.log(dim(` ${site.publication.uri}`)); // (readers believe a publication its site says is its: asked as they would) const url = new URL(site.wellKnown.path, String(site.publication.record.url)).href; const says = await fetch(url, { signal: AbortSignal.timeout(10_000) }) .then((r) => (r.ok ? r.text() : `${r.status}`)) .catch((e: unknown) => (e as Error).message); if (says.trim() !== site.wellKnown.text) console.log( yellow( ` ${url} doesn't say it's this publication (${says.trim().slice(0, 80) || "nothing"}): serve ${site.wellKnown.text} there, and each post's page its <link rel="site.standard.document">`, ), ); return 0; }
uses
- tcmts.cli.atproto.atprotoSession
- tcmts.cli.commands.routePublish
- tcmts.cli.main.execute
- tcmts.cli.publish.PUBLISH_SCOPE
- tcmts.cli.publish.publishSite
- tcmts.cli.run.Context
- tcmts.cli.style.bold
- tcmts.cli.style.dim
- tcmts.cli.style.green
- tcmts.cli.style.red
- tcmts.cli.style.yellow
- tcmts.cli.ui.UsageError
- tcmts.cli.ui.chooseName
- tcmts.content.StandardSite
- tcmts.core.hash.exportId
- tcmts.loader.materialize