Your first workflow
Your repository has tests, and nothing runs them when you push. You want each pull request into main to show a green check before anyone merges it.
A workflow is a TypeScript file in .source/workflows/. The forge runs its jobs for the pushes and pull requests that it names.
You need:
- Write access to the repository on TanStack Source. Step 5 needs admin access.
- The
srcCLI. Install the npm package@tanstack-source/cli, which has thesrcbinary. - A pnpm repository with vitest tests. For other tools, change the commands in step 1.
1. Write the workflow
Create .source/workflows/ci.ts:
import { createHash } from 'node:crypto'
import { readFile } from 'node:fs/promises'
import { join } from 'node:path'
import { cache, defineWorkflow, pullRequest, push, report, setup } from '@tanstack-source/ci'
export default defineWorkflow('ci', {
on: [pullRequest({ branches: ['main'] }), push({ branches: ['main'] })],
})
.job('test', {
egress: ['registry.npmjs.org'],
steps: [
setup.pnpm(),
{
name: 'Cache the pnpm store',
do: async (ctx) => {
const lockfile = await readFile(join(ctx.workspace, 'pnpm-lock.yaml'))
const hash = createHash('sha256').update(lockfile).digest('hex').slice(0, 16)
await cache({ key: `pnpm-${hash}`, paths: ['~/.local/share/pnpm/store'], restoreKeys: ['pnpm-'] }).do(ctx)
},
},
{ name: 'Install', run: 'pnpm install --frozen-lockfile' },
{
name: 'Test',
do: async (ctx) => {
const { code } = await ctx.sh.nothrow`pnpm vitest run --reporter=default --reporter=junit --outputFile.junit=junit.xml`
await report({ file: 'junit.xml', format: 'junit' }).do(ctx)
if (code !== 0) throw new Error(`vitest exited with code ${code}`)
},
},
],
})
.job('gate', {
needs: ['test'],
when: 'always',
steps: [
{
name: 'Check the jobs',
do: (ctx) => {
if (ctx.needs.test.result !== 'success') throw new Error(`test ended with ${ctx.needs.test.result}`)
},
},
],
})
What each part does:
on: a run starts for each pull request intomain, and for each push tomain.egress: the job reaches only these hosts, on port 443.pnpm installneedsregistry.npmjs.org.setup.pnpm()and the cache step: pnpm comes frompackageManagerinpackage.json. The pnpm store comes from the cache, and the job saves it when it passes. The key is a hash of the lockfile, because the forge writes an entry only once.- The test step:
report()adds each failed test, with its file and line, to the findings of the run. It runs before the step fails, so the findings of a failed test run reach the forge. gate: it runs aftertest, also whentestfailed (when: 'always'), and it fails whentestdid not pass. So one required check ongatecovers the whole workflow.
For types in your editor, add the package as a dev dependency: pnpm add -D @tanstack-source/ci. The forge does not need it, because its runner brings its own copy.
2. Run it on your machine
src ci plan
src ci run ci test
src ci plan loads each workflow file and shows its jobs, or the errors with the file to fix:
ci (.source/workflows/ci.ts)
on: pull request (branches: main), push (branches: main)
jobs:
test
gate: needs test; runs always
1 workflow, 2 jobs.
src ci run ci test runs the job on your machine. When it prints Job test passed., go to the next step. If it fails, Run CI jobs on your machine shows how to fix it there.
3. Push it to main
Push the workflow to the default branch before you open a pull request. A pull request run takes the secrets, permissions and egress of each job from the base branch. So a job that is new in a pull request runs with none of them, and pnpm install cannot reach the registry.
git add .source/workflows/ci.ts
git commit -m "ci: add the ci workflow"
git push origin main
If a branch rule refuses the push, merge the workflow through a pull request. The run of that pull request fails, because its jobs are new there.
4. See the run
main is the default branch, so the push starts a run of ci at the trust level protected.
- In the browser: open the CI tab of the repository (
/<owner>/<repo>/ci). Open the run to read the log, the findings and the summary of each job. The log shows each step by its name, likeInstall, with its result and how long it took. - In a terminal: set the forge variables, then list the runs.
export TSS_FORGE_URL=https://forge.example/api/v1
export TSS_TOKEN=<your personal access token>
export TSS_REPO=acme/app
src ci runs --branch main
ID STATUS WORKFLOW EVENT REF COMMIT
7c1e2d3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f success ci push main 4e5f6a7
If a job failed, src ci view <run> --log-failed prints the logs of the failed jobs. To let an agent fix a failed run for you, turn on CI self-heal.
5. Make the check required
A branch rule blocks a merge into main until ci/gate passes on the head commit of the pull request. A required check is <workflow>/<job>.
src ci rule set main --require-pull --check ci/gate
Set the branch rule main: requires a pull request; required checks ci/gate.
Branch rules has the settings page and every option of a rule.
6. Open a pull request
- Create a branch, change a file, and push the branch.
- Open a pull request into
main. - Wait until the
cirun of the pull request ends.
The checks of the pull request say All checks passed, and list the jobs of the ci workflow. Below them, above the merge button, Required checks shows ci/gate as Passed. The CLI shows the same result:
src ci checks 1
Pull request #1 into main, head 9f8e7d6
NAME STATE REASON
ci/gate passed Passed on 9f8e7d6
May merge: yes
Your pull request has a green check, and the merge button is available. The list of pull requests shows the same green check next to its title.