Publish releases to a site¶
Goal: have every release appear on a page that renders from data — a changelog, a feed, a status board — without anybody copying it across.
The file adapter appends each release to a list in a file in another
repository and commits it. The push is what deploys, so the row is live a few
minutes later.
Configure it¶
# .colophon.yaml
announce:
file:
repo: phpboyscout/blog
token: $BLOG_TOKEN
branch: main
strip_prefix: phpboyscout/
path: data/releases/{path_dashed}.yaml
list: releases
key: url
entry:
name: "{name}"
tag: "{tag}"
date: "{released_at}"
url: "{url}"
header:
project: "{path}"
name: "{project}"
url: "{project_url}"
message: "chore(releases): {path} {tag}"
The manifest reference lists every placeholder. The ones that matter most:
{path_dashed}turnsgo/configintogo-config, which is what makes one file per project rather than one shared file. Two pipelines writing two files never conflict; two appending to one would race each other into merge conflicts, and releases in this estate arrive in trains.{released_at}is when the forge published the release, not when the job ran. A release cut late, or a job retried, still sorts where it belongs.
YAML or JSON¶
Both work. The extension decides: .yaml, .yml or .json. If your path has
no extension, or has one that does not match the contents, name it:
A JSON file is edited in place — the adapter inserts the entry and leaves the rest of the file exactly as it found it, rather than decoding and re-encoding, which would reshuffle the fields of every entry already there.
Give it a token¶
token names an environment variable. A value written into the manifest is
refused when the manifest is parsed, before anything runs.
The variable needs write_repository on the target repository and nothing
else. Set it as a CI variable on the project that releases, masked.
Run it after the release exists¶
colophon-announce:
stage: notify
rules:
- if: $CI_COMMIT_TAG
script:
- colophon --ci announce --tag "$CI_COMMIT_TAG"
The verb reads the release object, so it must run after the release exists — not beside the job that creates it.
Running it twice is safe¶
The adapter reads the file first and looks for an entry whose key already
matches this release. If it is there it writes nothing and reports:
That is worth relying on. A retried pipeline, a re-run job, or two jobs racing each other all end with one entry, because the record itself is the check — there is no marker file and no state kept anywhere else.
If the branch is protected¶
This pushes a branch and opens a merge request instead of committing directly. The branch name comes from the project and tag, so a re-run finds its own request rather than opening a second one.
merge: when_ready asks the forge to merge once its checks pass. A forge
that cannot wait for checks is reported rather than given a plain merge instead:
That refusal is deliberate. "Merge when the checks pass" and "merge now" differ exactly when the checks would have failed, which is the case you set it for.
What it will not do¶
It only ever appends. Existing entries are never reordered, reformatted or corrected, because the file is a record of what shipped: a person may have edited an entry by hand, and a release withdrawn upstream is still something that happened. Correcting an entry is a job for a merge request.
See also¶
- Announce a release to a chat channel — the Discord adapter
colophon announce- Announcing into a file