Release Process for t3code: Complete Guide to Automated CI/CD
t3code uses a single GitHub Actions workflow in .github/workflows/release.yml that automates the entire release pipeline—including pre-flight checks, multi-platform desktop builds, npm publishing, and GitHub release creation—triggered by version tags, scheduled cron jobs, or manual dispatch.
The t3code repository (hosted at pingdotgg/t3code) maintains a fully automated release process that handles both stable and nightly distributions. This guide examines the complete CI/CD pipeline, from quality gates to final version bumps, based on the actual implementation in the source code.
Workflow Overview and Triggers
The release orchestration lives entirely in .github/workflows/release.yml. This single workflow definition handles three distinct trigger types under the on: section【.github/workflows/release.yml#L4-L15】:
- Tag push (
v*.*.*): Initiates stable releases when you push a semantic version tag - Scheduled cron (
0 */3 * * *): Runs every 3 hours to check for changes and potentially generate a new nightly build - Workflow dispatch: Allows manual triggering via the GitHub UI with inputs for
channel(stable/nightly) and explicit version strings
Pre-flight Checks and Version Resolution
The preflight job runs quality gates before any artifacts are built. It executes bun run lint, bun run typecheck, and bun run test to ensure code integrity【.github/workflows/release.yml#L54-L61】.
For version handling, the workflow distinguishes between release channels:
- Stable releases: Validates the incoming tag matches semantic versioning (supporting
X.Y.Zor prerelease formats) and extracts version components【.github/workflows/release.yml#L31-L45】 - Nightly releases: Invokes
scripts/resolve-nightly-release.tsto generate deterministic version strings like0.0.18-nightly.20240917.3based on the current date, run number, and short commit SHA【scripts/resolve-nightly-release.ts#L46-L60】
The resolved metadata (channel, version, tag, ref) is exported as job outputs for downstream consumption【.github/workflows/release.yml#L71-L80】.
Building Desktop Artifacts
The build job uses a matrix strategy to compile native desktop installers across four platform/architecture combinations:
| Platform | Target | Architecture |
|---|---|---|
| macOS | dmg |
arm64 |
| macOS | dmg |
x64 |
| Linux | AppImage |
x64 |
| Windows | nsis |
x64 |
(Windows arm64 support exists in the configuration but is currently commented out.)【.github/workflows/release.yml#L71-L100】
Each matrix entry performs the following steps:
- Checks out the exact commit referenced by
needs.preflight.outputs.ref - Installs dependencies via Bun
- Aligns all package versions using
scripts/update-release-package-versions.ts【.github/workflows/release.yml#L26-L28】 - Optional code signing: macOS binaries are signed using Apple Developer secrets, while Windows uses Azure Trusted Signing. If secrets are absent, the build proceeds unsigned with a console notice【.github/workflows/release.yml#L79-L89】
- Invokes the desktop build command:
bun run dist:desktop:artifactwith--platform,--target, and--archparameters【.github/workflows/release.yml#L71-L107】 - Collects output files (
*.dmg,*.zip,*.AppImage,*.exe,*.blockmap,*.yml) into arelease-publishdirectory and uploads them as build artifacts nameddesktop-<platform>-<arch>【.github/workflows/release.yml#L109-L152】
Publishing the CLI Package
The publish_cli job handles npm distribution after successful builds. It:
- Checks out the release commit
- Re-runs the version alignment script to ensure the CLI package recognizes the correct version
- Builds the CLI filtering for
@t3tools/webandt3workspaces - Publishes the
t3npm package using OIDC trusted publishing vianode apps/server/scripts/cli.ts publishwith the appropriate dist-tag (latestfor stable,nightlyfor nightly)【.github/workflows/release.yml#L84-L88】
Creating the GitHub Release
The release job aggregates artifacts from all platform builds. It:
- Downloads all
desktop-*artifacts usingactions/download-artifact - Merges macOS updater manifests using
scripts/merge-update-manifests.tsto produce a unifiedlatest-mac.yml(ornightly-mac.yml)【.github/workflows/release.yml#L22-L44】 - Uses softprops/action-gh-release to publish the release with:
- The tag from
needs.preflight.outputs.tag - Name formatted as
T3 Code vX.Y.Z - Automatic release notes (GitHub compares against the previous tag)
- Correct
prereleaseandmake_latestflags based on channel - All built artifacts and updater metadata【.github/workflows/release.yml#L69-L108】
- The tag from
The job handles the "first release" scenario (no previous tag) separately from subsequent releases when constructing the release payload.
Finalizing the Release
After a stable release completes, the finalize job performs repository maintenance:
- Creates a GitHub App token to attribute the subsequent push to the automation bot
- Runs
scripts/update-release-package-versions.tswith--github-outputto capture whether anypackage.jsonfiles actually changed - Re-formats modified
package.jsonfiles usingoxfmt - Refreshes the
bun.locklockfile - Commits the version bump (e.g.,
chore(release): prepare v1.2.4) and pushes tomain【.github/workflows/release.yml#L110-L87】
This ensures the source repository remains in sync with the released version.
Supporting Scripts
Two TypeScript utilities underpin the release logic:
| Script | Purpose |
|---|---|
scripts/resolve-nightly-release.ts |
Calculates nightly version metadata. Generates strings like 0.0.18-nightly.20240917.3 using the base version, current date, GitHub run number, and short commit SHA. Can be run locally for debugging. |
scripts/update-release-package-versions.ts |
Synchronizes version strings across apps/server, apps/desktop, apps/web, and packages/contracts. Updates each package.json and reports whether changes occurred. Supports --github-output for CI consumption. |
Both scripts are Effect-TS programs that can be executed locally:
# Generate nightly metadata
node scripts/resolve-nightly-release.ts \
--date 20240917 \
--run-number 3 \
--sha $(git rev-parse --short HEAD)
# Bump all package versions to 2.0.0
node scripts/update-release-package-versions.ts 2.0.0
Summary
- t3code uses a unified GitHub Actions workflow (
.github/workflows/release.yml) triggered by version tags, scheduled cron jobs, or manual dispatch. - The preflight job enforces quality gates (lint, typecheck, tests) and resolves version metadata using
scripts/resolve-nightly-release.tsfor nightlies or tag parsing for stable releases. - A matrix build compiles desktop artifacts for macOS (arm64/x64), Linux (x64), and Windows (x64), with optional code signing and version alignment via
scripts/update-release-package-versions.ts. - The publish_cli job distributes the
t3npm package using OIDC trusted publishing with appropriate dist-tags (latestornightly). - The release job aggregates artifacts, merges macOS updater manifests, and publishes GitHub Releases using
softprops/action-gh-release. - The finalize job bumps version strings in
package.jsonfiles and pushes the commit back tomainafter stable releases.
Frequently Asked Questions
How do I manually trigger a nightly release?
You can dispatch the workflow manually from the GitHub Actions tab or via the CLI. Specify channel: nightly to force a nightly build regardless of the cron schedule:
gh workflow run release.yml -f channel=nightly
The workflow will check if the main branch has changed since the last nightly tag. If changes exist, it generates a new nightly version (e.g., 0.0.18-nightly.20240917.3) and builds all artifacts.
What happens if code signing secrets are missing?
The build proceeds unsigned. During the build job, the workflow attempts to configure macOS signing (Apple Developer secrets) and Windows signing (Azure Trusted Signing). If the required secrets are not present in the repository, the scripts print a notice and continue building unsigned binaries. This ensures forks and pull requests can still produce testable artifacts.
How does the workflow handle versioning across multiple packages?
The scripts/update-release-package-versions.ts script synchronizes version strings across the monorepo. It walks apps/server, apps/desktop, apps/web, and packages/contracts, updating each package.json to the target release version. This runs before building to ensure the desktop app and CLI report consistent version numbers, and runs again during the finalize job to commit the bumped versions back to the repository.
Can I publish a stable release without creating a tag first?
No. Stable releases require a version tag to trigger the workflow. You must create and push a tag matching the v*.*.* pattern (or just *.*.* which the workflow validates):
git tag 1.2.3
git push origin 1.2.3
The preflight job validates this tag, extracts the version components, and proceeds with the full release pipeline. Without a tag push, the workflow will only run if manually dispatched with explicit parameters, but standard stable releases are tag-driven.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →