conductor

CI task system
git clone git://git.finwo.net/app/conductor
Log | Files | Refs | README | LICENSE

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 }