lib.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. import { checkCache, updateCache } from "./cache.js";
  2. import {
  3. createTrackedObject,
  4. fileExists,
  5. readFilesByGlob,
  6. removeBasePaths,
  7. removeCwd,
  8. replaceFileExtension,
  9. getValueAtPath
  10. } from "./util/index.js";
  11. import path from "path";
  12. import process from "node:process";
  13. import { getLogger } from "./logging.js";
  14. export async function getConfig() {
  15. const args = process.argv.slice(2);
  16. const defaultPath = path.join(process.cwd(), "rhedyn.config.js");
  17. const findConfigPath = (args, index = 0) => {
  18. if (index >= args.length) {
  19. return defaultPath;
  20. }
  21. if (
  22. (args[index] === "-c" || args[index] === "--config") &&
  23. index + 1 < args.length
  24. ) {
  25. return path.resolve(args[index + 1]);
  26. }
  27. return findConfigPath(args, index + 1);
  28. };
  29. const configPath = findConfigPath(args);
  30. const configFileExists = await fileExists(configPath);
  31. if (configFileExists) {
  32. try {
  33. const config = await import(configPath);
  34. return config.default || config;
  35. } catch (err) {
  36. console.error(`Error reading config file at ${configPath}:`, err);
  37. throw new Error("Failed reading config file");
  38. }
  39. } else {
  40. return;
  41. }
  42. }
  43. async function runTask({ meta, config, jobId }) {
  44. const log = getLogger(
  45. config.logLevel ? config.logLevel : meta.opts.logLevel,
  46. jobId
  47. );
  48. log.trace(`meta: ${JSON.stringify(meta, null, 2)}`);
  49. log.trace(`config: ${JSON.stringify(config, null, 2)}`);
  50. const stateObject = {
  51. meta,
  52. config
  53. };
  54. const cache =
  55. meta.opts.cacheDir && !config.skipCache
  56. ? await checkCache(jobId, stateObject, meta.opts)
  57. : { disabled: true, reason: "Cache disabled" };
  58. if (cache && cache.hit) {
  59. log.debug(`Loaded cache for ${jobId}: ${cache.filePath}`);
  60. return { ...cache.taskResult, fromCache: true };
  61. }
  62. log.debug(`Cache miss for ${jobId} (${cache.reason})`);
  63. const state = meta.opts.cacheDir
  64. ? createTrackedObject(stateObject)
  65. : { proxy: stateObject };
  66. const {
  67. detail = {},
  68. paths = [],
  69. deps: processorDeps,
  70. ref
  71. } = await config.processor(state.proxy);
  72. const taskResult = {
  73. detail,
  74. paths: paths.map(fileOutputPath =>
  75. fileOutputPath.replace(meta.opts.outDir, "")
  76. ),
  77. ref
  78. };
  79. log.debug(`Wrote ${taskResult.paths.length} files for ${jobId}`);
  80. if (cache && !cache.disabled) {
  81. log.debug(`Updating cache for ${jobId}: ${cache.filePath}`);
  82. const processorPathDeps = processorDeps?.paths || [];
  83. const processorStateDeps = processorDeps?.state || [];
  84. const configPathDeps = config.deps?.paths || [];
  85. const configStateDeps = config.deps?.state || [];
  86. const stateSelectors = config.stateSelectors || [];
  87. await updateCache(
  88. meta.opts.cacheDir,
  89. jobId,
  90. removeCwd(
  91. [...configPathDeps, ...processorPathDeps, config?.filePath].filter(
  92. item => !!item
  93. )
  94. ),
  95. [
  96. ...configStateDeps,
  97. ...stateSelectors,
  98. ...processorStateDeps,
  99. ...(state?.accessed || [])
  100. ].filter(item => !!item),
  101. taskResult,
  102. cache.updates,
  103. meta.opts.includeStateValues
  104. );
  105. }
  106. return taskResult;
  107. }
  108. async function expandFileTask(patternsToInclude, config, meta) {
  109. const filesToProcess = await readFilesByGlob(patternsToInclude);
  110. const pathsToStrip = config.stripPaths || [];
  111. const outputDir = config.outputDir || "";
  112. return await Promise.all(
  113. filesToProcess.map(async filePath => {
  114. const fileOutputPath = path.join(
  115. meta.opts.outDir,
  116. outputDir,
  117. replaceFileExtension(
  118. removeBasePaths(pathsToStrip, filePath),
  119. config.outputFileExtension
  120. )
  121. );
  122. const fileOutputDir = path.dirname(fileOutputPath);
  123. const jobConfig = {
  124. ...config,
  125. filePath,
  126. fileOutputDir,
  127. fileOutputPath
  128. };
  129. return runTask({
  130. meta,
  131. config: jobConfig,
  132. jobId: `${config.name} @ ${filePath}`
  133. });
  134. })
  135. );
  136. }
  137. async function expandStateTask(stateToExpand, config, meta) {
  138. const stateToProcess = stateToExpand
  139. .map(property => {
  140. const values = getValueAtPath(meta, property);
  141. const expandedValues = Array.isArray(values)
  142. ? values
  143. : Object.values(values);
  144. return expandedValues.map((value, index) => ({ property, index, value }));
  145. })
  146. .flat();
  147. return await Promise.all(
  148. stateToProcess.map(async stateJob => {
  149. const jobConfig = {
  150. ...config,
  151. ...stateJob.value.detail
  152. };
  153. return runTask({
  154. meta,
  155. config: jobConfig,
  156. jobId: `${config.name} @ ${stateJob.property}:${stateJob.index}`
  157. });
  158. })
  159. );
  160. }
  161. export async function expandAndRunTask(meta, config) {
  162. const includes = meta.opts?.include?.[config.name] || [];
  163. const patternsToInclude = [...(config?.inputFiles || []), ...includes];
  164. if (patternsToInclude.length) {
  165. return expandFileTask(patternsToInclude, config, meta);
  166. }
  167. if (config.stateSelectors) {
  168. return expandStateTask(config.stateSelectors, config, meta);
  169. }
  170. const jobId = config.jobId || config.name;
  171. const taskResult = await runTask({ meta, config, jobId });
  172. return [taskResult];
  173. }
  174. export async function processTask(meta, task) {
  175. const log = getLogger(meta.opts.logLevel, task.name);
  176. const startTime = performance.now();
  177. const taskResult = await expandAndRunTask(meta, task);
  178. const cached = taskResult.filter(taskResult => taskResult.fromCache);
  179. const processed = taskResult.filter(taskResult => !taskResult.fromCache);
  180. const resources = taskResult.reduce(
  181. (obj, tResult) => (tResult.ref ? { ...obj, [tResult.ref]: tResult } : obj),
  182. {}
  183. );
  184. const endTime = performance.now();
  185. const timeTaken = endTime - startTime;
  186. const hrTime =
  187. timeTaken > 1000
  188. ? `${Number.parseFloat(timeTaken / 1000).toFixed(2)}s`
  189. : `${Number.parseFloat(timeTaken).toFixed(2)}ms`;
  190. const filesWritten = processed.reduce(
  191. (acc, cur) => acc + cur.paths.length,
  192. 0
  193. );
  194. log.info(
  195. `written: ${filesWritten} | processed: ${processed.length} | from cache: ${
  196. cached.length
  197. } | ${hrTime}`
  198. );
  199. return {
  200. name: task.name,
  201. taskResult,
  202. cached,
  203. processed,
  204. resources,
  205. filesWritten
  206. };
  207. }