{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/webWorkflowCatalog/index.ts"],"sourcesContent":["import type {\n  WorkflowCatalogArtifactView,\n  WorkflowCatalogEntryView,\n  WorkflowCatalogInputView,\n  WorkflowCatalogJobView,\n} from '../../types/webWorkflows';\n/**\n * The cockpit's view of the workflows a session can launch.\n *\n * The engine already reads a repository's workflow files and parses one of\n * them into triggers, inputs, jobs, artifacts and compatible runners, so this\n * module only shapes that answer for the wire and remembers what it read.\n *\n * WHY A CACHE:\n * A session reads the catalog for its initial projection and when its lifecycle\n * publishes a workflow update. Entries are kept per path and reused while the\n * file's size and modification time are unchanged, which is the same test a\n * build tool makes and costs one stat per file.\n */\n\n/** What the engine reports for one workflow file before it is parsed. */\nexport interface CatalogListEntry {\n  path: string;\n  relativePath: string;\n  name: string;\n  description: string;\n  tags: string[];\n}\n\n/** One file's parsed detail, or the reason it could not be read. */\nexport interface CatalogDetail {\n  triggers: string[];\n  inputs: WorkflowCatalogInputView[];\n  jobs: WorkflowCatalogJobView[];\n  artifacts: WorkflowCatalogArtifactView[];\n  runners?: string[];\n  error?: string;\n}\n\n/** What a file looked like when it was last parsed. */\nexport interface CatalogFileStamp {\n  size: number;\n  modifiedAt: number;\n}\n\nexport interface WorkflowCatalogReaderDeps {\n  /** Every workflow file under a directory. */\n  list(directory: string): Promise<CatalogListEntry[]>;\n  /** One file's parsed detail; never throws, so a bad file cannot empty a board. */\n  summarize(path: string): CatalogDetail;\n  /** Size and modification time, or undefined when the file is gone. */\n  stamp(path: string): CatalogFileStamp | undefined;\n}\n\ninterface CachedEntry {\n  stamp: CatalogFileStamp;\n  detail: CatalogDetail;\n}\n\n/** Reads one directory's catalog, reusing what it parsed last time. */\nexport interface WorkflowCatalogReader {\n  read(directory: string): Promise<WorkflowCatalogEntryView[]>;\n  /** Drops what was parsed for files no longer listed, so a cache cannot grow forever. */\n  forget(keep: ReadonlySet<string>): void;\n}\n\nfunction unchanged(left: CatalogFileStamp | undefined, right: CatalogFileStamp | undefined): boolean {\n  return left !== undefined && right !== undefined && left.size === right.size && left.modifiedAt === right.modifiedAt;\n}\n\nexport function createWorkflowCatalogReader(deps: WorkflowCatalogReaderDeps): WorkflowCatalogReader {\n  const cache = new Map<string, CachedEntry>();\n\n  const detailFor = (path: string): CatalogDetail => {\n    const stamp = deps.stamp(path);\n    const cached = cache.get(path);\n    if (cached && unchanged(cached.stamp, stamp)) return cached.detail;\n    const detail = deps.summarize(path);\n    // A file that vanished between listing and stat is parsed anyway, which\n    // answers with its own error; it just cannot be cached against a stamp.\n    if (stamp !== undefined) cache.set(path, { stamp, detail });\n    return detail;\n  };\n\n  return {\n    async read(directory) {\n      const listed = await deps.list(directory);\n      return listed.map((entry) => {\n        const detail = detailFor(entry.path);\n        return {\n          path: entry.path,\n          relativePath: entry.relativePath,\n          name: entry.name === '' ? entry.relativePath : entry.name,\n          description: entry.description,\n          tags: entry.tags,\n          triggers: detail.triggers,\n          inputs: detail.inputs,\n          jobs: detail.jobs,\n          artifacts: detail.artifacts,\n          ...(detail.runners === undefined ? {} : { runners: detail.runners }),\n          ...(detail.error === undefined ? {} : { error: detail.error }),\n        };\n      });\n    },\n    forget(keep) {\n      for (const path of cache.keys()) {\n        if (!keep.has(path)) cache.delete(path);\n      }\n    },\n  };\n}\n\n/** The launchable rows first, then the ones that would not parse, name ordered. */\nexport function presentWorkflowCatalog(entries: readonly WorkflowCatalogEntryView[]): WorkflowCatalogEntryView[] {\n  return [...entries].sort((left, right) => {\n    if ((left.error === undefined) !== (right.error === undefined)) return left.error === undefined ? -1 : 1;\n    return left.name.localeCompare(right.name);\n  });\n}\n"],"mappings":";AAkEA,SAAS,UAAU,MAAoC,OAA8C;CACnG,OAAO,SAAS,KAAA,KAAa,UAAU,KAAA,KAAa,KAAK,SAAS,MAAM,QAAQ,KAAK,eAAe,MAAM;AAC5G;AAEA,SAAgB,4BAA4B,MAAwD;CAClG,MAAM,wBAAQ,IAAI,IAAyB;CAE3C,MAAM,aAAa,SAAgC;EACjD,MAAM,QAAQ,KAAK,MAAM,IAAI;EAC7B,MAAM,SAAS,MAAM,IAAI,IAAI;EAC7B,IAAI,UAAU,UAAU,OAAO,OAAO,KAAK,GAAG,OAAO,OAAO;EAC5D,MAAM,SAAS,KAAK,UAAU,IAAI;EAGlC,IAAI,UAAU,KAAA,GAAW,MAAM,IAAI,MAAM;GAAE;GAAO;EAAO,CAAC;EAC1D,OAAO;CACT;CAEA,OAAO;EACL,MAAM,KAAK,WAAW;GAEpB,QAAO,MADc,KAAK,KAAK,SAAS,EAAA,CAC1B,KAAK,UAAU;IAC3B,MAAM,SAAS,UAAU,MAAM,IAAI;IACnC,OAAO;KACL,MAAM,MAAM;KACZ,cAAc,MAAM;KACpB,MAAM,MAAM,SAAS,KAAK,MAAM,eAAe,MAAM;KACrD,aAAa,MAAM;KACnB,MAAM,MAAM;KACZ,UAAU,OAAO;KACjB,QAAQ,OAAO;KACf,MAAM,OAAO;KACb,WAAW,OAAO;KAClB,GAAI,OAAO,YAAY,KAAA,IAAY,CAAC,IAAI,EAAE,SAAS,OAAO,QAAQ;KAClE,GAAI,OAAO,UAAU,KAAA,IAAY,CAAC,IAAI,EAAE,OAAO,OAAO,MAAM;IAC9D;GACF,CAAC;EACH;EACA,OAAO,MAAM;GACX,KAAK,MAAM,QAAQ,MAAM,KAAK,GAC5B,IAAI,CAAC,KAAK,IAAI,IAAI,GAAG,MAAM,OAAO,IAAI;EAE1C;CACF;AACF;;AAGA,SAAgB,uBAAuB,SAA0E;CAC/G,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,MAAM,MAAM,UAAU;EACxC,IAAK,KAAK,UAAU,KAAA,OAAgB,MAAM,UAAU,KAAA,IAAY,OAAO,KAAK,UAAU,KAAA,IAAY,KAAK;EACvG,OAAO,KAAK,KAAK,cAAc,MAAM,IAAI;CAC3C,CAAC;AACH"}