torb publish
torb publish builds and checks a package's archive as a registry receives it, prints its tree hash and capabilities, and writes it into a directory registry or uploads it with a token or through trusted publishing.
publish turns a package into the archive a registry holds (docs/design/RELEASE.md section 7.2) and checks everything
a registry would refuse before anything leaves the machine. --dry-run stops once the archive is built. Without it,
the release is written into a file: registry, or sent to the write service of a registry on the network
(tools/registry, the service behind packages.torb.dev), which checks it once more and refuses in words this command
prints.
Synopsis#
torb publish Build, check and publish the package around the working directory
--dry-run Build and check the archive, and upload nothing
--project <directory> The package to publishWhat it does#
In order, stopping at the first step that refuses:
- The settings.
project.trbis evaluated in the sandbox where the VM can, and read statically otherwise.namehas to beowner/nameand notstd/...,versiona version,descriptionandlicenseset, and no runtime dependency may come from apath:source, which nobody who installs the package has.keywordsis optional: at most five, each 2 to 24 characters ofa-z,0-9and-starting with a letter or digit, and none twice. - The check. The package is checked like
torb checkof its directory; a package that does not check is refused. - The files. Every
.trbbelow the package directory that is not inside a nested package, skipping hidden directories,build,targetandnode_modules, plusREADME.mdand the license files. There is no include or exclude list. The archive'sproject.lock.trbis the package'ssettingsblock and the workspace'sgraph. - The archive. A deterministic
tarinside gzip, at most 10 000 files and 10 MiB, and its tree hash. - The summary, and with
--dry-runnothing more. Where the evaluation ofproject.trbread files or variables of the environment, areads:line names each file by its path, sorted, with(file),(directory),(unreadable)or(missing), and then each variable, sorted, with(set)or(unset)and never its value; the archive's lock records them in thefrom { }block of itssettings:
$ torb publish --dry-run --project sources/json-1.2.0
acme/json 1.2.0
checked: 2 files, no problems
capabilities: files (src/lib.trb)
files: 3, 1180 bytes, archive 842 bytes
hash: sha256:7689cf8f7a49b8349e13f3cccb395192ecb20b839f1e277c366e20560206fe33
nothing was uploaded (--dry-run)- The upload. For a
file:registry, the archive goes toarchives/<owner>/<name>/<version>.tar.gzand the release is appended toindex/<owner>/<name>.trb; thesettingsblock of the package is written into the workspace'sproject.lock.trb. A version that is published already is refused: a published version is never replaced, and afile:registry that signs its index - itsindex/config.trblists keys - is refused too, because this command cannot sign the record. For a registry on the network (https://...), the archive is sent withPUT <registry>/api/1/packages/<owner>/<name>/<version>and its tree hash inTorb-Tree-Hash, throughcurl(elsewget, or the oneTORB_FETCHnames). The registry unpacks it, reads the settings of its lock, refuses what this command refuses, and writes the archive and the index entry, signed with its key; its answer is printed, and thesettingsblock goes into the workspace's lock as forfile:.
The token#
A registry on the network needs to know who publishes:
TORB_TOKEN: a token of the registry whose actions includepublishand whose scopes include the package. It is sent in a header thatcurlreads from a file, never on a command line.- The stored token, where
TORB_TOKENis not set: the onetorb loginstored for this registry in the user'scredentials.trb. It goes only to anhttps:registry, or to anhttp:one on the loopback address. - Trusted publishing, where there is neither and the command runs in a CI job that may ask for an OpenID
Connect token - a GitHub Actions job with
permissions: id-token: write, or a Forgejo Actions job withenable-openid-connect: trueon a forge the registry trusts (git.torb.dev): the job's token is requested with the registry's host as its audience (TORB_AUDIENCEnames another) and exchanged at/api/1/trusted-publishingfor a token that lives fifteen minutes and publishes this one package. The package's owners configure which repository (acme/httpon GitHub,git.torb.dev/acme/httpon the forge), workflow file, ref and - on GitHub - environment may do that, and no secret is stored anywhere.
Without any of them, nothing is sent:
$ torb publish
error: publishing to https://packages.torb.dev needs a token: `torb login --registry https://packages.torb.dev` stores a token of the registry that may publish acme/json, TORB_TOKEN gives one, or publish from a CI job that may ask for an identity token (GitHub Actions: `permissions: id-token: write`, Forgejo Actions: `enable-openid-connect: true`) and a trusted publisher the owners of acme/json configuredA manifest that computes its version from the environment and a file says what it read:
$ torb publish --dry-run
acme/env 1.0.1
...
reads: file VERSION (file)
reads: PACKAGES_CHANNEL (unset)
reads: PACKAGES_RELEASE (set)
nothing was uploaded (--dry-run)Pitfalls#
A variable or a file the manifest read is part of the release. A version computed from CI_COMMIT_TAG or from a
VERSION file is only as reproducible as they are: reads: shows which ones the evaluation used, so a release built on
a machine without one is noticed before it is uploaded, and from { } in the archive's lock keeps the hash of each for
whoever installs it. The value of a variable is never printed and never written, because it may be a secret.
The registry trusts nothing the client computed. It recomputes the tree hash, reads the settings from the archive's
own lock - never by evaluating a project.trb - and derives the capabilities again, so a refusal on the server is the
refusal --dry-run shows locally. It does not check the package: its toolchain may be another patch release.
The capability summary is what the package's own imports reach: files for std/fs, network for std/http,
std/network and std/tls, processes, environment, entropy for Entropy, the operating system for the rest
of std/os, clock for Clock and sleep, scripts, and foreign functions for a foreign block. What its
dependencies reach is theirs. Package capabilities has the table of modules and the pure names.
Every imported name decides, not its package. A capability is reading something, and a type is no reading:
use Timestamp from "std/time" puts a point in time into a signature and reaches nothing, while
use Clock from "std/time" reaches clock. The values a capability package answers in are pure - Duration,
Instant, Timestamp, Date, Weekday and TimeParseError of std/time; OsError, SystemVersion, EnvironmentVariables and the target constants
of std/os - and a name std/os re-exports reaches what its own module does, so Entropy is entropy from std/os
as from std/os/entropy. A use * as time from "std/time" can reach every name, so it reaches the package's
capability.
Examples#
A package that is published already is refused after the archive is built, because a published version is never replaced:
$ torb publish --project sources/json-1.0.0
acme/json 1.0.0
checked: 2 files, no problems
capabilities: none - the package imports nothing that reaches the machine
files: 3, 698 bytes, archive 569 bytes
hash: sha256:76ebb106f4c8fad658f40ddec9a4f2edfe7724f89a5951283121724e454bb37e
error: acme/json 1.0.0 is published already, and a published version is never replacedRelated#
- project.lock.trb - the
settingsblockpublishwrites. - project.trb -
description,licenseand the other settings it requires. - torb add - the other side: a package from a registry into a project.
- torb login - stores the token
publishsends. - torb yank - withdraws a published version from new resolutions.
- Package capabilities - which module is which capability, and how the registry shows a version's set.