run.js 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. import { performance } from "node:perf_hooks"
  2. import { getTaskKey, getTaskName, processTask } from "./lib.js"
  3. import { getLogger } from "./logging.js"
  4. export async function runWithConfig(config) {
  5. if (!config || typeof config !== "object") {
  6. throw new Error("Rhedyn config must export an object with `opts` and `tasks`.")
  7. }
  8. const startTime = performance.now()
  9. const { opts, tasks } = config
  10. if (!opts || !tasks) {
  11. throw new Error("Rhedyn config must define both `opts` and `tasks`.")
  12. }
  13. const log = getLogger(opts.logLevel, "main")
  14. const flatTasks = tasks.flatMap(step => (Array.isArray(step) ? step : [step]))
  15. const duplicateTaskKeys = []
  16. const seenTaskKeys = new Set()
  17. for (const task of flatTasks) {
  18. const taskKey = getTaskKey(task)
  19. if (!taskKey) {
  20. throw new Error("Each task must define `key` (or legacy `name`).")
  21. }
  22. if (seenTaskKeys.has(taskKey)) {
  23. duplicateTaskKeys.push(taskKey)
  24. } else {
  25. seenTaskKeys.add(taskKey)
  26. }
  27. }
  28. if (duplicateTaskKeys.length > 0) {
  29. const uniqueDuplicates = [...new Set(duplicateTaskKeys)]
  30. throw new Error(`Duplicate task keys found: ${uniqueDuplicates.join(", ")}`)
  31. }
  32. log.info(`Processing ${tasks.length} steps`)
  33. log.debug(`Running directory: ${opts.runDir}`)
  34. log.debug(`Output directory: ${opts.outDir}`)
  35. if (opts.cacheDir) {
  36. log.debug(`Cache directory: ${opts.cacheDir}`)
  37. } else {
  38. log.warn("Cache disabled")
  39. }
  40. const taskRunner = tasks.reduce(
  41. async (metaPromise, step) => {
  42. const stepTasks = Array.isArray(step) ? step : [step]
  43. const { meta, filesWritten } = await metaPromise
  44. const stepTaskNames = stepTasks.map(task => getTaskName(task))
  45. log.info(`Starting tasks: ${stepTaskNames.join(", ")}`)
  46. const stepResults = await Promise.all(stepTasks.map(async task => {
  47. const taskLog = getLogger(opts.logLevel, getTaskName(task))
  48. const taskResult = await processTask(meta, task)
  49. taskLog.trace(`taskResult: ${JSON.stringify(taskResult)}`)
  50. return taskResult
  51. }))
  52. return stepResults.reduce((newState, taskResult) => {
  53. const resources = Object.keys(taskResult.resources).length > 0
  54. ? {
  55. ...newState.meta.resources,
  56. [taskResult.key]: taskResult.resources,
  57. }
  58. : { ...newState.meta.resources }
  59. return {
  60. meta: {
  61. ...newState.meta,
  62. resources,
  63. },
  64. filesWritten: newState.filesWritten + taskResult.filesWritten,
  65. }
  66. }, { meta, filesWritten })
  67. },
  68. Promise.resolve({ meta: { opts }, filesWritten: 0 }),
  69. )
  70. const finalState = await taskRunner
  71. log.trace(`Final state: ${JSON.stringify(finalState, null, 2)}`)
  72. const endTime = performance.now()
  73. const timeTaken = endTime - startTime
  74. const hrTime = timeTaken > 1000
  75. ? `${Number.parseFloat(timeTaken / 1000).toFixed(2)}s`
  76. : `${Number.parseFloat(timeTaken).toFixed(2)}ms`
  77. log.info(
  78. `Completed ${tasks.length} steps in ${hrTime}, wrote ${finalState.filesWritten} files.`,
  79. )
  80. return finalState
  81. }