diff --git a/packages/cli/src/util/cache.ts b/packages/cli/src/util/cache.ts index 846cb9cf2..6113509f8 100644 --- a/packages/cli/src/util/cache.ts +++ b/packages/cli/src/util/cache.ts @@ -8,7 +8,8 @@ import type { Logger } from './logger'; export const CACHE_DIR = '.cli-cache'; -// TODO this is all a bit over complicated tbh +// When called without workflowName/stepId, returns the CACHE_DIR root. +// This is used directly in saveToCache to locate the .gitignore. export const getCachePath = ( options: Pick, workflowName?: string, @@ -24,7 +25,8 @@ export const getCachePath = ( const basePath = path.resolve( baseDir ?? process.cwd(), - `${CACHE_DIR}/${workflowName}` + CACHE_DIR, + workflowName ?? '' ); if (stepId) { @@ -33,38 +35,33 @@ export const getCachePath = ( return basePath; }; -const ensureGitIgnore = (options: any, cachePath: string) => { +const ensureGitIgnore = (options: any, cacheRoot: string) => { if (!options._hasGitIgnore) { - // Find the root cache folder - let root = cachePath; - while (root.length > 1 && !root.endsWith(CACHE_DIR)) { - root = path.dirname(root); - } - // From the root cache, look for a .gitignore - const ignorePath = path.resolve(root, '.gitignore'); + const ignorePath = path.join(cacheRoot, '.gitignore'); try { fs.accessSync(ignorePath); } catch (e) { // doesn't exist! fs.writeFileSync(ignorePath, '*'); } + options._hasGitIgnore = true; } - options._hasGitIgnore = true; }; export const saveToCache = async ( plan: ExecutionPlan, stepId: string, output: any, - options: Pick, + options: Pick, logger: Logger ) => { if (options.cacheSteps) { - const cachePath = await getCachePath(options, plan.workflow.name, stepId); + const cachePath = getCachePath(options, plan.workflow.name, stepId); // Note that this is sync because other execution order gets messed up fs.mkdirSync(path.dirname(cachePath), { recursive: true }); - ensureGitIgnore(options, path.dirname(cachePath)); + // getCachePath with no workflowName returns the CACHE_DIR root + ensureGitIgnore(options, getCachePath(options)); logger.info(`Writing ${stepId} output to ${cachePath}`); fs.writeFileSync(cachePath, JSON.stringify(output)); @@ -73,10 +70,10 @@ export const saveToCache = async ( export const clearCache = async ( plan: ExecutionPlan, - options: Pick, + options: Pick, logger: Logger ) => { - const cacheDir = await getCachePath(options, plan.workflow?.name); + const cacheDir = getCachePath(options, plan.workflow?.name); try { await rmdir(cacheDir, { recursive: true }); diff --git a/packages/cli/test/execute/execute.test.ts b/packages/cli/test/execute/execute.test.ts index 2b500fa1b..f96ee0c32 100644 --- a/packages/cli/test/execute/execute.test.ts +++ b/packages/cli/test/execute/execute.test.ts @@ -418,6 +418,55 @@ test.serial('.cli-cache has a gitignore', async (t) => { t.is(gitignore, '*'); }); +// Regression test for https://github.com/OpenFn/kit/issues/669 +test.serial('cache steps when running a .js expression', async (t) => { + mockFs({ + '/job.js': `${fn}fn((state) => ({ ...state, x: 1 }));`, + }); + + const options = { + ...defaultOptions, + expressionPath: '/job.js', + baseDir: '/', + cacheSteps: true, + }; + + const result = await handler(options, logger); + t.is(result.x, 1); + + // workflow name is derived from the filename: 'job' + // step id is a generated uuid, so list the directory + const files = await fs.readdir('/.cli-cache/job'); + t.is(files.length, 1); + t.true(files[0].endsWith('.json')); + + const cached = JSON.parse( + await fs.readFile(`/.cli-cache/job/${files[0]}`, 'utf8') + ); + t.is(cached.x, 1); +}); + +test.serial( + '.cli-cache has a gitignore when caching a .js expression', + async (t) => { + mockFs({ + '/job.js': `${fn}fn((state) => ({ ...state, x: 1 }));`, + }); + + const options = { + ...defaultOptions, + expressionPath: '/job.js', + baseDir: '/', + cacheSteps: true, + }; + + await handler(options, logger); + + const gitignore = await fs.readFile('/.cli-cache/.gitignore', 'utf8'); + t.is(gitignore, '*'); + } +); + test.serial('run a workflow with initial state from stdin', async (t) => { const workflow = { workflow: {