only.js (2239B)
1 // src/lib/pipeline/only.js - restricting tasks to particular refs 2 // 3 // A task carrying an `only` rule runs when the ref that triggered the run 4 // matches one of its patterns, and is left out of the run entirely when it 5 // does not. Left out rather than recorded as skipped, because the 6 // scheduler treats a skipped task as a reason to fail the run: that is the 7 // right reading when a dependency collapsed, and the wrong one for a 8 // publish step that was never meant to run on this branch. 9 // 10 // Anything depending on an excluded task is excluded with it. The 11 // alternative, quietly dropping the dependency, would let a task run 12 // without something it declared it needed, which is a worse surprise than 13 // the task not running. 14 15 import { transitiveDependents } from './dag.js'; 16 17 // Glob matching over a whole ref. Only * is special, standing for any run 18 // of characters including none; everything else is literal. Deliberately 19 // not a regular expression, since these come out of a repository and are 20 // read by people who are not thinking about regular expression syntax. 21 export function refMatches(pattern, ref) { 22 if (typeof pattern !== 'string' || typeof ref !== 'string') return false; 23 if (!pattern.includes('*')) return pattern === ref; 24 25 const source = pattern 26 .split('*') 27 .map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) 28 .join('.*'); 29 return new RegExp(`^${source}$`).test(ref); 30 } 31 32 export function taskRunsOnRef(task, ref) { 33 if (!task.only) return true; 34 35 // A run with no ref at all, which a manual or api trigger may produce, 36 // cannot match a ref pattern. Restricted tasks stay out rather than 37 // being handed a ref they were never written for. 38 if (typeof ref !== 'string' || ref === '') return false; 39 40 return task.only.refs.some((pattern) => refMatches(pattern, ref)); 41 } 42 43 // The tasks that belong in a run for this ref, in their original order. 44 export function selectForRef(tasks, ref) { 45 const excluded = new Set(tasks.filter((task) => !taskRunsOnRef(task, ref)).map((task) => task.name)); 46 if (excluded.size === 0) return tasks; 47 48 for (const name of transitiveDependents(tasks, [...excluded])) excluded.add(name); 49 50 return tasks.filter((task) => !excluded.has(task.name)); 51 }