Skip to content
Source

Share steps across repositories

Ten repositories set up pnpm and install their dependencies the same way, and each one has its own copy of the steps. A fix to the setup needs ten pull requests, and the copies become different over time.

Put the steps in a TypeScript module in one repository. Each workflow imports the module with a src: import, and .source/src.lock pins the exact commit that it runs.

You need the src CLI (the npm package @tanstack-source/cli), and these forge variables:

export TSS_FORGE_URL=https://forge.example/api/v1
export TSS_TOKEN=<your personal access token>

1. Write the module

Create a repository for the shared steps, like acme/ci-steps. Add the file pnpm.ts:

import { setup, type Step } from '@tanstack-source/ci'

/** Sets up the pnpm that package.json names, then installs the dependencies. */
export function pnpmInstall(): Step[] {
  return [setup.pnpm(), { name: 'Install', run: 'pnpm install --frozen-lockfile' }]
}

A module can import only these:

  • Relative files of the same repository, at the same commit.
  • Other src: modules.
  • @tanstack-source/ci. The runner gives its own copy.
  • node: built-ins.

Push the module, then tag a version and push the tag:

git tag v1.0.0
git push origin v1.0.0

2. Import it in a workflow

In the repository acme/app, import the module in .source/workflows/ci.ts:

import { defineWorkflow, pullRequest } from '@tanstack-source/ci'
import { pnpmInstall } from 'src:acme/ci-steps@v1.0.0/pnpm.ts'

export default defineWorkflow('ci', { on: [pullRequest({ branches: ['main'] })] })
  .job('test', {
    egress: ['registry.npmjs.org'],
    steps: [...pnpmInstall(), { name: 'Test', run: 'pnpm test' }],
  })

A specifier is src:<owner>/<repo>@<ref>/<path>.ts. The ref is one tag, branch or commit name, with no / in it. Import a module only with a static import. The forge refuses import() of a src: module.

3. Pin it with src ci update

Run src ci update in the root of acme/app:

src ci update
acme/ci-steps@v1.0.0: new pin at 3f2a1b9
  added pnpm.ts: 6 lines
Wrote .source/src.lock.
Synced acme/ci-steps@v1.0.0 at 3f2a1b9: 1 file.
Added .source/.modules/ to .gitignore.
Wrote .source/tsconfig.json.
  • src.lock pins the commit of each <owner>/<repo>@<ref>, its tree, and a hash of the files that the workflows load.
  • The module files go to .source/.modules/, and git ignores that folder.
  • .source/tsconfig.json maps src: imports to those files, so your editor has types.
  • TSS_TOKEN must be able to read acme/ci-steps.

Commit the workflow and the lock file, and push:

git add .source/workflows/ci.ts .source/src.lock .gitignore
git commit -m "ci: use the shared pnpm steps"
git push

After a new clone, run src ci sync. It downloads the pinned files, for your editor and for src ci plan and src ci run. It changes no pin.

Who can import a module

  • Any repository can import a module of a public repository.
  • A private repository can import a module of a private repository of the same owner, when an admin allows it.
  • A public repository cannot import a module of a private repository.

To allow a private repository, an admin of the repository that imports adds it to the allowed workflow sources:

export TSS_REPO=acme/app
src ci source add acme/ci-steps
Workflows may import src: modules from acme/ci-steps.

In the browser, open Settings, then CI settings (/<owner>/<repo>/settings/ci). Under Allowed workflow sources, enter acme/ci-steps and click Allow.

If the forge cannot use the repository, the plan stops with this error. The words are the same for a repository that does not exist:

src:acme/ci-steps@v1.0.0/pnpm.ts: the repository acme/ci-steps was not found. A private repository works only when it has the same owner, and an admin of acme/app adds it to the allowed workflow sources.

A push or a pull request with a src: error records a failed run with no jobs. The run shows the error on the CI tab, on the run page, and in the checks of the pull request. To find errors before you push, run src ci plan on your machine. It shows the errors of your checkout, like a src: import with no entry in src.lock.

When a tag moves

Someone can move the tag v1.0.0 to another commit. Then the tag names a commit that src.lock does not pin. The forge does not plan the workflows, so no job runs. A push records a failed run with the error. The new code of the module runs only after you pin it.

The failed run on the CI tab shows the error. The Run workflow form shows it too, for the branch that you enter:

The workflows of main could not be read: acme/ci-steps@v1.0.0 moved: it points at 9c8d7e6 now, but .source/src.lock pins 3f2a1b9. Run `src ci update` to review the change and pin it.

To fix it:

  1. Run src ci update for that import. It shows what changed in the module:

    src ci update acme/ci-steps@v1.0.0
    acme/ci-steps@v1.0.0: moved from 3f2a1b9 to 9c8d7e6
      changed pnpm.ts: 1 line added, 1 removed
    Wrote .source/src.lock.
    Synced acme/ci-steps@v1.0.0 at 9c8d7e6: 1 file.
    Wrote .source/tsconfig.json.
  2. Read the new code in .source/.modules/acme/ci-steps@v1.0.0/.

  3. If you accept the new code, commit src.lock and push.

  4. If you do not accept it, import another tag or a commit. Then run src ci update again.

A commit never moves. To pin a module for good, import it at a commit: src:acme/ci-steps@<40-character commit>/pnpm.ts.

Other errors

The error saysDo this
... has no entry in .source/src.lockRun src ci update.
The files imported from acme/ci-steps@v1.0.0 changed since .source/src.lock pinned themA workflow imports another file of the module. Run src ci update.
... is not in .source/.modules/This error is on your machine only. Run src ci sync.

Caps

For one plan, the forge reads all src: modules together, with these caps:

  • At most 1 MiB of module files.
  • At most 100 module files.
  • Imports at most 8 deep. The src: import of a workflow has depth 1, and a file that it imports has depth 2.

Two repositories, one pinned step

Do steps 2 and 3 in a second repository, acme/web. src ci update pins the same commit:

acme/ci-steps@v1.0.0: new pin at 3f2a1b9
  added pnpm.ts: 6 lines

Both repositories run the same reviewed setup steps from commit 3f2a1b9. A change to acme/ci-steps reaches a repository only when that repository runs src ci update.