Skip to main content

@flowwright/plugin-cache

Two helpers that pair a cache.restore and cache.save around a body, with the key derived from the files that determine the contents.

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

API

ExportKindSignature
withCachestep helper(opts: WithCacheOptions, body: () => Promise<void>) => Promise<void>
cachedStagestage builder(name, opts: CachedStageOptions, body) => StageDefinition

WithCacheOptions: path (required), keyFiles (required, string or array), prefix (default: a slug of path). CachedStageOptions is that plus every StageOptions key except run.

Example

pipeline.ts
import { pipeline, stage, sh } from "@flowwright/core";
import { cachedStage } from "@flowwright/plugin-cache";

export default pipeline({
name: "acme-api",
stages: [
cachedStage("Install", { id: "install", path: "node_modules", keyFiles: "package-lock.json" }, async () => {
await sh(["npm", "ci"]);
}),
stage("Test", {
needs: ["install"],
run: async () => {
await sh(["npm", "test"]);
},
}),
],
});

What it composes down to

withCache is four lines:

const key = `${prefix}-${await hashFiles(keyFiles)}`;
await cache.restore(path, { key });
await body();
await cache.save(path, { key });

So cachedStage("Install", { path: "node_modules", keyFiles: "package-lock.json" }, body) is a stage whose cacheKeys are a restore/save pair around whatever body records, keyed node-modules-<hash of package-lock.json>. The prefix is path slugified — node_modulesnode-modules, .venvvenv — unless you pass prefix.

Every StageOptions key except run passes straight through, so id, needs, container and the rest work as usual.

Gotchas

The body always runs. A cache hit doesn't skip anything — it just means the files are already there, so npm ci finds its work done and exits quickly. There's no "restore succeeded, skip the install" branch, and there can't be: the body was recorded before any cache was consulted. See Artifacts, cache and stash.

Keys are write-once. Saving to an existing key is a no-op forever. That's why the key contains a hash — change the lockfile and you get a new key. A hand-written prefix with static keyFiles will serve its first contents indefinitely.

A keyFiles pattern that matches nothing still produces a key — the hash of an empty set, which every other empty pattern also produces. Check the key in flow explain once.

plugin-node and plugin-python don't use this package. They hand-roll a <pm>-deps-<hash> key against the core primitives, so their cache entries and this one never share. See the overview.