pipeline.ts owns the workflow. GitHub Actions and package.json are read surfaces other tools depend on — so instead of taking them over, @async/pipeline generates exactly the pieces you opt into, records ownership, and fails loudly on drift or collision.
sync: {
github: true, // one bootloader workflow + lock
tasks: true // job scripts in package.json
}
Both are independent. Omit either and that surface is never touched.
GitHub decides whether to start a workflow from committed YAML before any of your code runs. That YAML is the only thing sync puts there:
async-pipeline sync github generate
writes two files:
.github/workflows/async-pipeline.yml # pinned actions, contents: read, calls the CLI
.github/async-pipeline.lock.json # hash of the trigger/job metadata it was built from
The workflow checks out, sets up Node, restores the task cache, runs async-pipeline github check (so a stale workflow fails its own run), and delegates job selection back to the CLI. Task commands, dependency order, caching, and retries never appear in YAML — they stay in pipeline.ts, which means changing a task does not require touching CI.
What this deliberately does not do:
env.secret("NAME") renders as $ references.contents: read unless a job declares more (for example npm provenance).sync.tasks writes package-manager aliases for the jobs you pick — and nothing else:
sync: {
tasks: {
prefix: "pipeline",
jobs: ["verify"],
scripts: { "sync:check": "sync check" }
}
}
{
"scripts": {
"pipeline:verify": "async-pipeline run verify",
"pipeline:sync:check": "async-pipeline sync check"
}
}
Every generated script is namespaced under your chosen prefix and recorded in .async-pipeline/tasks.lock.json. That lock is the ownership boundary: sync only ever rewrites scripts the lock claims. If a generated name collides with a script it does not own, generation fails with ASYNC_PIPELINE_SYNC_CONFLICT instead of overwriting your work. Your hand-written scripts are never rewritten, reordered, or removed.
The generated files are committed, so they are reviewable in every PR — and checkable:
async-pipeline sync check # all synced surfaces match pipeline.ts, or non-zero
async-pipeline sync generate # regenerate everything that is stale
Run sync check in the pipeline itself (this repo does) and a hand-edited workflow or script fails CI with a diffable reason. The boundary stays honest in both directions: pipeline.ts cannot silently diverge from what GitHub runs, and nobody can quietly move logic into YAML.
The exit test for any tool that generates files: what happens when you remove it?
sync block from pipeline.ts..github/workflows/async-pipeline.yml, the lock files, and the pipeline:* scripts.That is the whole list. The generated workflow and scripts are plain artifacts with no runtime hook back into the package — until that day, they are simply the parts of your pipeline you chose to publish to GitHub and npm.
sync field.