Contributing To Build Infrastructure
August 18, 2026 ยท View on GitHub
The goal is to keep the specification repositories uncluttered: each spec repo should call clear package scripts, while this package owns the tool versions and reusable logic.
Mental Model
There are four layers:
- Specification repositories contain Markdown, schemas, tests, workflows, and
spec.config.json. - Their
package.jsonscripts call commands installed by@oai/build-infra. - This repository implements those commands and owns the JavaScript dependencies they need.
- GitHub Actions in each specification repository run the same Yarn commands that maintainers run locally.
If something is reusable across specification repositories, put it here. If it is specific to one repository's governance, labels, reviewers, or branch policy, leave it in that repository.
Command Overview
Build and validation:
oai-spec-buildrenders Markdown to HTML using Markdown-it and ReSpec.oai-spec-format-markdownapplies shared Markdown formatting.oai-spec-validate-markdownruns markdownlint and linkspector.oai-spec-publish-schemaspublishes dated JSON schema iterations.
Tests:
oai-spec-testrunsc8andvitest.@oai/build-infra/testre-exports Vitest helpers.@oai/build-infra/schema/test-configregisters YAML schema loading and any configured custom vocabulary keywords.@oai/build-infra/schema/vitestre-exports schema coverage helpers.
Release lifecycle:
oai-spec-start-releasestarts the next development PR branch.oai-spec-adjust-release-branchprepares a release branch for merge tomain.
Before Changing Code
Read the consuming repository's spec.config.json and package.json first.
Most behavior is configured there.
Be especially careful with release commands. They create branches, commit files, delete configured paths, and may push branches. Test release-command changes in a scratch repository before asking maintainers to trust them.
Testing
Run the package tests:
corepack enable
yarn install --immutable
yarn test
These tests include self-contained fixture repositories. They exercise the public commands against temporary consumer-shaped Git repositories, so they can run locally and in GitHub CI without another checkout.
For changes that affect behavior not covered by those fixtures, also test in at least one specification repository with a temporary local dependency:
{
"dependencies": {
"@oai/build-infra": "file:../build-infra"
}
}
Then run the relevant consumer scripts:
yarn install
yarn test
yarn validate-markdown
yarn build
yarn build-src
Not every repository has all of those scripts.
Before committing consumer changes, change the dependency back to the GitHub dependency. Refresh the lockfile only after the build-infra commit is available on GitHub.
Dependency Maintenance
This repository owns most JavaScript dependencies for the specification
repositories. Direct dependencies are pinned exactly so consumers receive the
versions tested here. Dependabot's JavaScript ecosystem is named npm, even for
projects whose lockfile and commands use Yarn.
When Dependabot opens a pull request:
-
Read the release notes for major updates and security updates.
-
Run
yarn install --immutableandyarn test. -
If the update touches build, markdown, schema, or test behavior, test a consumer repository with the local
file:../build-infradependency. -
Merge the build-infra update.
-
In each consumer repository that should pick up the change, run:
yarn up -R @oai/build-infra yarn install --immutable -
Run the consumer's relevant tests, validation, and builds.
-
Commit the consumer repository's
yarn.lockupdate.
The consumer package.json should keep requesting
git+https://github.com/OAI/build-infra.git#main. The consumer
yarn.lock records both that branch request and the exact Git commit resolved
from it. That commit is intentional: immutable installs do not silently change
behavior when OAI/build-infra moves forward. yarn up -R refreshes the
resolution without changing package.json.
Yarn Lockfile And Security Settings
yarn.lock is the only dependency lockfile. Yarn generates a complete,
cross-platform dependency graph, including platform-specific optional packages.
Never edit it manually or copy entries from another lockfile. There is no
packaged lockfile snapshot and no consumer lockfile merge step.
Use yarn install --immutable for routine installs and CI. Use a command that
may change the lockfile only when that change is intentional:
- Use
yarn installonce when creating a new repository's first lockfile. - Use
yarn up <package>for an intentional package version update. - Use
yarn up -R @oai/build-infrato refresh a consumer's#mainresolution.
Yarn 4.18 has two security defaults that every consumer must configure:
.yarnrc.ymlmust usenodeLinker: node-modules, because the shared shell commands inspect that layout, and must list the build-infra URL underapprovedGitRepositories.- The top-level
package.jsonmust setdependenciesMeta.puppeteer.built: true. This narrowly permits Puppeteer's browser installation script, which linkspector needs. Do not enable all third-party install scripts merely to silence a warning. Review any new package that requests a build script and allow it only when build-infra needs the generated artifact.
The expected Node.js version is declared in .nvmrc, package.json, and CI;
the Yarn version is declared in packageManager. When updating either runtime,
change build-infra first, run all tests, and then align consumer repositories.
Release Command Maintenance
The release commands are intentionally conservative.
oai-spec-adjust-release-branch:
- must run on a branch named
vX.Y.Z-rel; - requires a clean working tree;
- copies the active source Markdown to
versions/X.Y.Z.md; - replaces
| TBD |with the current date; - copies
EDITORS.mdtoversions/X.Y.Z-editors.md, unless disabled; - removes paths listed in
release.removeOnReleaseBranch.
oai-spec-start-release:
- must run on a branch named
vX.Y-dev; - requires a clean working tree;
- finds the latest published version under
versions/on the configured main branch; - creates
vX.Y-dev-start-X.Y.Z; - resets the active source Markdown history from the previous published version;
- updates the version heading and history table;
- optionally rewrites schema/test files for a new minor version;
- pushes the branch unless
--no-pushis used.
Use --no-push in scratch tests.
Common Failure Modes
- Yarn rejects the build-infra Git URL: add
https://github.com/OAI/build-infra.gittoapprovedGitRepositoriesin the consumer's.yarnrc.yml. - Yarn reports that Puppeteer's build is disabled: add the documented
dependenciesMeta.puppeteer.builtallowlist to the consumer's top-levelpackage.json, then runyarn installand commit the resulting lockfile. yarn install --immutablereports that it would changeyarn.lock: the manifest and lockfile disagree. If a dependency change was intentional, run the appropriateyarn upcommand and review the lockfile diff. Otherwise, restore the matching manifest or lockfile from Git.- A consumer cannot fetch its locked build-infra commit: make sure that commit has been pushed to GitHub before refreshing the consumer lockfile.
- Release command says the working tree is dirty: commit or stash local changes first. These commands intentionally refuse to mix release edits with unrelated work.
oai-spec-start-releasecannot find published versions: check the configured remote, main branch, andversions/directory.- Generated HTML looks wrong: check
spec.config.jsonfirst, especiallyslug,shortName,titleName,specSrc, and metadata links.
What Belongs Here
Good candidates for this repository:
- shared command-line tools;
- shared JavaScript dependency versions;
- Markdown, link, schema, and ReSpec behavior;
- release lifecycle mechanics used by multiple specification repositories;
- templates for new specification repositories.
Keep these in individual specification repositories:
- contributor policy;
- branch sync policy;
- CODEOWNERS;
- issue templates;
- labels, reviewers, and pull request wording;
- one-off scripts for that repository's issue management or governance.