build-registry.mjs
Derives registry.json — the register of onboarded repositories — by walking the estate for .redline.json.
What this is
Derives registry.json — the register of onboarded repositories — by walking every repo in the org for a .redline.json. The register is never hand-edited and redline init never writes it: an entry exists exactly as long as the repository's own file does, so a repo that removes Redline leaves the register on the next run.
How to onboard it
Nothing to onboard, and nothing an onboarded repository ever runs. This is maintainer tooling: it ships in this repository and runs where it already has an environment.
- Runs in: This (source) repo.
- Trigger: .github/workflows/registry.yml — nightly at 04:00 UTC, plus workflow_dispatch.
To run it yourself you need a checkout of this repository and Node 22 or newer. There are no runtime dependencies to install — every script uses only Node builtins — so a clone and the environment below is the whole setup.
How to use it
$ GH_TOKEN=... npx redlinegate registry --org acme --source owner/repoEnvironment:
GH_TOKEN — read access to org repos.ORG — the owner whose repositories are walked.SOURCE — owner/name of this repo, recorded in the register so a consumer knows which estate it describes.OUT — output path, default registry.json.
Nothing, normally — the nightly workflow runs it. Run it by hand after onboarding a batch of repositories if you do not want to wait for the next refresh.
Expected output
registry.json, with entries ordered by org then repo so a nightly commit only diffs when the estate actually changed. Prints one line per problem — a repository whose .redline.json is malformed, one with no default branch — and exits 1 without writing anything if no onboarded repository was found at all: an empty register is indistinguishable from a token that lost access, and publishing it would erase the dashboard's coverage figure and every sync target in one commit.
How to edit it
- Edit the .mjs file directlyNothing generates these — scripts/ is hand-written maintainer tooling with no runtime dependencies. Keep it that way: package.json declares none, and these run in CI with only Node's builtins available.
- node --check scripts/<file>.mjsCI's lint job parses every script in scripts/. A syntax error there fails the build without running anything.
- Run it locally with the same env CI gives itEach script is env-configured with no argument parsing, so a local run is the CI run. The environment variables it needs are listed above.
The full file
#!/usr/bin/env node
// Builds registry.json: the derived register of onboarded repositories.
//
// Runs in the Redline source repo on a schedule. The register is DERIVED — it is
// discovered from .redline.json across the org, never hand-edited, and never written
// by `redline init`. An entry exists exactly as long as the repository's own file
// does, so a repository that removes Redline leaves the register on its next run.
//
// Requires `npm run build` first: it imports the compiled CLI from dist/.
//
// Env: GH_TOKEN (read access to org repos), ORG, SOURCE (owner/name of this repo),
// [OUT=registry.json]
import { writeFileSync } from 'node:fs';
import { createGitHubClient } from '../dist/platforms/github/client.js';
import { discoverGitHub } from '../dist/registry/discover.js';
import { serializeRegistry } from '../dist/registry/serialize.js';
const { GH_TOKEN, ORG, SOURCE, OUT = 'registry.json' } = process.env;
if (!GH_TOKEN || !ORG || !SOURCE) throw new Error('GH_TOKEN, ORG and SOURCE are required');
const client = createGitHubClient({ token: GH_TOKEN });
const { entries, problems } = await discoverGitHub(client, ORG);
for (const problem of problems) console.warn(` ${problem}`);
// An empty register is indistinguishable from a token that lost access, and
// publishing it would erase the dashboard's coverage figure and every sync
// target at once. Refuse rather than overwrite.
if (entries.length === 0) {
console.error(
`No onboarded repositories discovered in ${ORG}. Refusing to write an empty register.`
);
process.exit(1);
}
writeFileSync(
OUT,
serializeRegistry({ generatedAt: new Date().toISOString(), source: SOURCE, entries })
);
console.log(`${OUT}: ${entries.length} onboarded repositories, ${problems.length} problem(s)`);