Something went wrong. Try again.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167/** * `corpora build` on the command line. * * The private key is read here and passed straight to the builder. It is never printed, never * echoed back in an error, and never written to the output. */
import { readFile } from 'node:fs/promises'import { parseArgs } from 'node:util'import { message } from '../message.js'import { build } from './index.js'import { isDevelopmentDid } from './diddoc.js'import { dnsCheckMode } from './dns.js'import { BuildError } from './errors.js'
const USAGE = `corpora build — sign a lexicon directory into dist/
Usage: corpora build --lexicons <dir> --out <dir> --did <did> [options]
Options: --lexicons <dir> directory of lexicon JSON files, read recursively (required) --out <dir> output directory (default: dist) --did <did> did:web:<host> or did:plc:<id> (required) --key-file <file> secp256k1 private key in hex; or set CORPORA_SIGNING_KEY --service <url> service endpoint to publish (default: the did:web origin) --rev <tid|time> commit rev: a TID, or an ISO 8601 instant to derive one from (default: derived from the MST root, so builds are reproducible) --against <origin> compare against a deployed authority instead of --out --dns-check <mode> authority-domain TXT checks (default: strict) strict every record must exist and name --did cutover every record must exist; naming another DID is a warning, for building a new authority before the records are flipped to it off no lookups at all --no-dns-check alias for --dns-check off --force write even when nothing changed --quiet print nothing on success`
/** * @param {string[]} argv * @returns {Promise<number>} */export async function run(argv) { const { values } = parseArgs({ args: argv, options: { lexicons: { type: 'string' }, out: { type: 'string', default: 'dist' }, did: { type: 'string' }, 'key-file': { type: 'string' }, service: { type: 'string' }, rev: { type: 'string' }, against: { type: 'string' }, 'dns-check': { type: 'string' }, 'no-dns-check': { type: 'boolean', default: false }, force: { type: 'boolean', default: false }, quiet: { type: 'boolean', default: false }, help: { type: 'boolean', default: false }, }, })
if (values.help) { process.stdout.write(USAGE) return 0 }
try { if (!values.lexicons) throw new BuildError('--lexicons <dir> is required') if (!values.did) throw new BuildError('--did <did> is required') if (values['no-dns-check'] && values['dns-check'] && values['dns-check'] !== 'off') { throw new BuildError( `--no-dns-check is an alias for --dns-check off, which contradicts --dns-check ${values['dns-check']}`, ) }
const report = await build({ lexicons: values.lexicons, out: values.out, did: values.did, key: await readKey(values['key-file']), service: values.service, rev: values.rev, against: values.against, dnsCheck: values['no-dns-check'] ? 'off' : dnsCheckMode(values['dns-check']), force: values.force, })
for (const warning of report.warnings) process.stderr.write(`warning: ${warning}\n`) if (!values.quiet) process.stdout.write(summarize(report, values.out)) return 0 } catch (err) { if (!(err instanceof BuildError)) throw err process.stderr.write(`corpora build: ${err.message}\n`) return 1 }}
/** * @param {string | undefined} keyFile * @returns {Promise<string>} */async function readKey(keyFile) { if (keyFile) { try { return (await readFile(keyFile, 'utf8')).trim() } catch (err) { throw new BuildError(`cannot read --key-file ${keyFile}: ${message(err)}`) } } const fromEnv = process.env.CORPORA_SIGNING_KEY?.trim() if (fromEnv) return fromEnv throw new BuildError('no signing key: pass --key-file <file> or set CORPORA_SIGNING_KEY (`corpora keygen` makes one)')}
/** * @param {import('./index.js').BuildReport} report * @param {string} out */function summarize(report, out) { const lines = [ `did ${report.did}`, `service ${report.service}${isDevelopmentDid(report.did) ? ' (development only, not resolvable elsewhere)' : ''}`, `signing key ${report.signingKey}`, `rev ${report.rev}`, `commit ${report.commitCid}`, `mst root ${report.mstRoot}`, '', ]
// `report.files` and `report.nsids` are built from the same `sources` array by the same // build, and `planDist` emits a `records/` and a `proofs/` entry for every NSID in it. Every // path looked up below therefore exists; indexing states that, where a `find` would invite a // fallback for a report this build cannot produce. /** @type {Record<string, number>} */ const size = {} for (const file of report.files) size[file.path] = file.bytes
const nsidWidth = Math.max(...report.nsids.map((n) => n.length)) for (const nsid of report.nsids) { const record = size[`records/${nsid}.json`] const proof = size[`proofs/${nsid}.car`] lines.push(`${nsid.padEnd(nsidWidth)} ${bytes(record)} record ${bytes(proof)} proof`) } lines.push('')
const domains = Object.entries(report.authorityDomains) const domainWidth = Math.max(...domains.map(([d]) => d.length)) for (const [domain, nsids] of domains) { lines.push(`${domain.padEnd(domainWidth)} ${nsids.length} lexicon${nsids.length === 1 ? '' : 's'}`) } lines.push('')
lines.push( report.written ? `wrote ${report.files.length} files to ${out}, ${bytes(report.totalBytes)} of served artifacts` : `unchanged: ${report.unchanged}\nnothing written; pass --force to rebuild anyway`, ) return `${lines.join('\n')}\n`}
/** @param {number} n */function bytes(n) { return `${n.toLocaleString('en-US')} B`}