Skip to main content

Plugins

A FlowWright plugin is a package that exports plain functions. There is no registry, no lifecycle hooks, nothing to enable in pipeline.ts, and no flow plugin install. You import a function and call it.

Everything a plugin produces goes through the same recording pass your own code does, so flow explain shows no difference between a plugin's stages and hand-written ones. If a helper doesn't fit, stop using it and write the stages yourself — that's the same amount of code, just longer.

The seven packages

PackageFor
plugin-stdWorkspace housekeeping — clean, delete, write files
plugin-gitCloning, checking out, tagging, pushing
plugin-cacheWrapping a stage body in a content-keyed cache
plugin-artifactsDeclaring build outputs
plugin-dockerBuilding, tagging and pushing images
plugin-nodeA whole Node CI lifecycle
plugin-pythonA whole Python CI lifecycle

Want X → use Y

You want toUse
Check out a repo at the start of a runcheckoutStage()git
Cache node_modules between runsnodeStages() does it; standalone, cachedStage()cache
Build and push an imagedockerStages()docker
A standard install → lint → test → build for a Node reponodeStages()node
The same for PythonpythonStages()python
Declare dist/ as an outputarchiveStage()artifacts — or just produces
Empty the workspace before a stepcleanWs()std

Two kinds of export

Every function in these packages is one of two shapes, and they're used in different places:

Step helpers are async functions you call inside a stage body. They record steps, exactly like sh.

stage("Clean", async () => {
await cleanWs();
});

Stage builders are called at pipeline level and return StageDefinition[] (or a single one) to spread into stages.

stages: [checkoutStage("git@github.com:acme/api.git"), ...nodeStages()];

Composing: group()

Plugin stage ids are fixed — install, lint, docker-build. Use two plugins that both emit an install, or the same plugin twice, and they collide. group() namespaces them:

pipeline.ts
import { pipeline, group } from "@flowwright/core";
import { nodeStages } from "@flowwright/plugin-node";

export default pipeline({
name: "monorepo",
stages: [...group("api", nodeStages()), ...group("web", nodeStages())],
});

That yields api-install, api-lint, …, web-install, and rewires each group's internal needs. See Dependencies — and note that group() namespaces ids only, not cache keys or produces paths.

Rejected option combinations

An option a configuration can't honor throws at plan time rather than silently doing nothing:

PassedPackageWhy
artifactPath with build disablednode, pythonnothing would be produced
cache: true with install: falsenode, pythonthere is nothing to cache
cache: true with packageManager: "pip"pythonpip has no in-project venv
push or login to dockerBuild()dockerthose build the other two stages
name to nodeStages()/pythonStages()node, pythononly the *ServicePipeline wrappers read it

Only an explicitly passed value counts. A default the configuration can't use is narrowed quietly — pythonStages({ packageManager: "pip" }) doesn't throw just because cache defaults to true.

Where the plugins differ

These are worth knowing before you assume the set is uniform. Each is only visible by comparison, which is why they're here rather than on the individual pages.

node and python are not symmetric

plugin-nodeplugin-python
build defaulttruefalse
Pin a toolchain versionnodeVersion
Version matrixnodeVersions
Audit stageaudit
artifactPath defaultnone"dist"
How tools runscripts, via <pm> run <script>tools directly, or via uv run / poetry run

Three cache-key formats, and node/python don't use plugin-cache

ProducerKey format
plugin-cache<slug(path)>-<hash(keyFiles)>
plugin-node install<pm>-deps-<hash(lockfile)>
plugin-python install<pm>-deps-<hash(lockfile)>

nodeStages() and pythonStages() call the @flowwright/core cache primitives directly and hand-roll their key — they don't go through plugin-cache. The practical consequence: a cachedStage({ path: "node_modules" }) and nodeStages() cache the same directory under different keys, so they never share and never collide.

Four credential declaration styles

StyleUsed by
{ id, as: "sshPrivateKey", env }git checkoutStage
{ id, as: "usernamePassword", usernameEnv, passwordEnv }docker login
{ id, as: "secretText", env }python publish
"npm-token" — a bare stringnode publish

All four are valid — see Credentials. The bare string is the odd one out: it means secretText bound to the derived env name, which for "npm-token" is NPM_TOKEN.

Only node and python bundle a whole pipeline

nodeServicePipeline() and pythonServicePipeline() return a complete PipelineDefinition. Everything else gives you stages to compose.

Installing

Each package is published independently and takes @flowwright/core as a peer:

npm install --save-dev @flowwright/plugin-node

plugin-std pulls in plugin-git, since it re-exports the checkout helpers.