projectUtils.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. "use strict";
  2. var __create = Object.create;
  3. var __defProp = Object.defineProperty;
  4. var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
  5. var __getOwnPropNames = Object.getOwnPropertyNames;
  6. var __getProtoOf = Object.getPrototypeOf;
  7. var __hasOwnProp = Object.prototype.hasOwnProperty;
  8. var __export = (target, all) => {
  9. for (var name in all)
  10. __defProp(target, name, { get: all[name], enumerable: true });
  11. };
  12. var __copyProps = (to, from, except, desc) => {
  13. if (from && typeof from === "object" || typeof from === "function") {
  14. for (let key of __getOwnPropNames(from))
  15. if (!__hasOwnProp.call(to, key) && key !== except)
  16. __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  17. }
  18. return to;
  19. };
  20. var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  21. // If the importer is in node compatibility mode or this is not an ESM
  22. // file that has been converted to a CommonJS file using a Babel-
  23. // compatible transform (i.e. "__esModule" has not been set), then set
  24. // "default" to the CommonJS "module.exports" for node compatibility.
  25. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  26. mod
  27. ));
  28. var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
  29. var projectUtils_exports = {};
  30. __export(projectUtils_exports, {
  31. buildDependentProjects: () => buildDependentProjects,
  32. buildProjectsClosure: () => buildProjectsClosure,
  33. buildTeardownToSetupsMap: () => buildTeardownToSetupsMap,
  34. collectFilesForProject: () => collectFilesForProject,
  35. filterProjects: () => filterProjects,
  36. findTopLevelProjects: () => findTopLevelProjects
  37. });
  38. module.exports = __toCommonJS(projectUtils_exports);
  39. var import_fs = __toESM(require("fs"));
  40. var import_path = __toESM(require("path"));
  41. var import_util = require("util");
  42. var import_utils = require("playwright-core/lib/utils");
  43. var import_utilsBundle = require("playwright-core/lib/utilsBundle");
  44. var import_util2 = require("../util");
  45. const readFileAsync = (0, import_util.promisify)(import_fs.default.readFile);
  46. const readDirAsync = (0, import_util.promisify)(import_fs.default.readdir);
  47. function wildcardPatternToRegExp(pattern) {
  48. return new RegExp("^" + pattern.split("*").map(import_utils.escapeRegExp).join(".*") + "$", "ig");
  49. }
  50. function filterProjects(projects, projectNames) {
  51. if (!projectNames)
  52. return [...projects];
  53. const projectNamesToFind = /* @__PURE__ */ new Set();
  54. const unmatchedProjectNames = /* @__PURE__ */ new Map();
  55. const patterns = /* @__PURE__ */ new Set();
  56. for (const name of projectNames) {
  57. const lowerCaseName = name.toLocaleLowerCase();
  58. if (lowerCaseName.includes("*")) {
  59. patterns.add(wildcardPatternToRegExp(lowerCaseName));
  60. } else {
  61. projectNamesToFind.add(lowerCaseName);
  62. unmatchedProjectNames.set(lowerCaseName, name);
  63. }
  64. }
  65. const result = projects.filter((project) => {
  66. const lowerCaseName = project.project.name.toLocaleLowerCase();
  67. if (projectNamesToFind.has(lowerCaseName)) {
  68. unmatchedProjectNames.delete(lowerCaseName);
  69. return true;
  70. }
  71. for (const regex of patterns) {
  72. regex.lastIndex = 0;
  73. if (regex.test(lowerCaseName))
  74. return true;
  75. }
  76. return false;
  77. });
  78. if (unmatchedProjectNames.size) {
  79. const unknownProjectNames = Array.from(unmatchedProjectNames.values()).map((n) => `"${n}"`).join(", ");
  80. throw new Error(`Project(s) ${unknownProjectNames} not found. Available projects: ${projects.map((p) => `"${p.project.name}"`).join(", ")}`);
  81. }
  82. if (!result.length) {
  83. const allProjects = projects.map((p) => `"${p.project.name}"`).join(", ");
  84. throw new Error(`No projects matched. Available projects: ${allProjects}`);
  85. }
  86. return result;
  87. }
  88. function buildTeardownToSetupsMap(projects) {
  89. const result = /* @__PURE__ */ new Map();
  90. for (const project of projects) {
  91. if (project.teardown) {
  92. const setups = result.get(project.teardown) || [];
  93. setups.push(project);
  94. result.set(project.teardown, setups);
  95. }
  96. }
  97. return result;
  98. }
  99. function buildProjectsClosure(projects, hasTests) {
  100. const result = /* @__PURE__ */ new Map();
  101. const visit = (depth, project) => {
  102. if (depth > 100) {
  103. const error = new Error("Circular dependency detected between projects.");
  104. error.stack = "";
  105. throw error;
  106. }
  107. if (depth === 0 && hasTests && !hasTests(project))
  108. return;
  109. if (result.get(project) !== "dependency")
  110. result.set(project, depth ? "dependency" : "top-level");
  111. for (const dep of project.deps)
  112. visit(depth + 1, dep);
  113. if (project.teardown)
  114. visit(depth + 1, project.teardown);
  115. };
  116. for (const p of projects)
  117. visit(0, p);
  118. return result;
  119. }
  120. function findTopLevelProjects(config) {
  121. const closure = buildProjectsClosure(config.projects);
  122. return [...closure].filter((entry) => entry[1] === "top-level").map((entry) => entry[0]);
  123. }
  124. function buildDependentProjects(forProjects, projects) {
  125. const reverseDeps = new Map(projects.map((p) => [p, []]));
  126. for (const project of projects) {
  127. for (const dep of project.deps)
  128. reverseDeps.get(dep).push(project);
  129. }
  130. const result = /* @__PURE__ */ new Set();
  131. const visit = (depth, project) => {
  132. if (depth > 100) {
  133. const error = new Error("Circular dependency detected between projects.");
  134. error.stack = "";
  135. throw error;
  136. }
  137. result.add(project);
  138. for (const reverseDep of reverseDeps.get(project))
  139. visit(depth + 1, reverseDep);
  140. if (project.teardown)
  141. visit(depth + 1, project.teardown);
  142. };
  143. for (const forProject of forProjects)
  144. visit(0, forProject);
  145. return result;
  146. }
  147. async function collectFilesForProject(project, fsCache = /* @__PURE__ */ new Map()) {
  148. const extensions = /* @__PURE__ */ new Set([".js", ".ts", ".mjs", ".mts", ".cjs", ".cts", ".jsx", ".tsx", ".mjsx", ".mtsx", ".cjsx", ".ctsx"]);
  149. const testFileExtension = (file) => extensions.has(import_path.default.extname(file));
  150. const allFiles = await cachedCollectFiles(project.project.testDir, project.respectGitIgnore, fsCache);
  151. const testMatch = (0, import_util2.createFileMatcher)(project.project.testMatch);
  152. const testIgnore = (0, import_util2.createFileMatcher)(project.project.testIgnore);
  153. const testFiles = allFiles.filter((file) => {
  154. if (!testFileExtension(file))
  155. return false;
  156. const isTest = !testIgnore(file) && testMatch(file);
  157. if (!isTest)
  158. return false;
  159. return true;
  160. });
  161. return testFiles;
  162. }
  163. async function cachedCollectFiles(testDir, respectGitIgnore, fsCache) {
  164. const key = testDir + ":" + respectGitIgnore;
  165. let result = fsCache.get(key);
  166. if (!result) {
  167. result = await collectFiles(testDir, respectGitIgnore);
  168. fsCache.set(key, result);
  169. }
  170. return result;
  171. }
  172. async function collectFiles(testDir, respectGitIgnore) {
  173. if (!import_fs.default.existsSync(testDir))
  174. return [];
  175. if (!import_fs.default.statSync(testDir).isDirectory())
  176. return [];
  177. const checkIgnores = (entryPath, rules, isDirectory, parentStatus) => {
  178. let status = parentStatus;
  179. for (const rule of rules) {
  180. const ruleIncludes = rule.negate;
  181. if (status === "included" === ruleIncludes)
  182. continue;
  183. const relative = import_path.default.relative(rule.dir, entryPath);
  184. if (rule.match("/" + relative) || rule.match(relative)) {
  185. status = ruleIncludes ? "included" : "ignored";
  186. } else if (isDirectory && (rule.match("/" + relative + "/") || rule.match(relative + "/"))) {
  187. status = ruleIncludes ? "included" : "ignored";
  188. } else if (isDirectory && ruleIncludes && (rule.match("/" + relative, true) || rule.match(relative, true))) {
  189. status = "ignored-but-recurse";
  190. }
  191. }
  192. return status;
  193. };
  194. const files = [];
  195. const visit = async (dir, rules, status) => {
  196. const entries = await readDirAsync(dir, { withFileTypes: true });
  197. entries.sort((a, b) => a.name.localeCompare(b.name));
  198. if (respectGitIgnore) {
  199. const gitignore = entries.find((e) => e.isFile() && e.name === ".gitignore");
  200. if (gitignore) {
  201. const content = await readFileAsync(import_path.default.join(dir, gitignore.name), "utf8");
  202. const newRules = content.split(/\r?\n/).map((s) => {
  203. s = s.trim();
  204. if (!s)
  205. return;
  206. const rule = new import_utilsBundle.minimatch.Minimatch(s, { matchBase: true, dot: true, flipNegate: true });
  207. if (rule.comment)
  208. return;
  209. rule.dir = dir;
  210. return rule;
  211. }).filter((rule) => !!rule);
  212. rules = [...rules, ...newRules];
  213. }
  214. }
  215. for (const entry of entries) {
  216. if (entry.name === "." || entry.name === "..")
  217. continue;
  218. if (entry.isFile() && entry.name === ".gitignore")
  219. continue;
  220. if (entry.isDirectory() && entry.name === "node_modules")
  221. continue;
  222. const entryPath = import_path.default.join(dir, entry.name);
  223. const entryStatus = checkIgnores(entryPath, rules, entry.isDirectory(), status);
  224. if (entry.isDirectory() && entryStatus !== "ignored")
  225. await visit(entryPath, rules, entryStatus);
  226. else if (entry.isFile() && entryStatus === "included")
  227. files.push(entryPath);
  228. }
  229. };
  230. await visit(testDir, [], "included");
  231. return files;
  232. }
  233. // Annotate the CommonJS export names for ESM import in node:
  234. 0 && (module.exports = {
  235. buildDependentProjects,
  236. buildProjectsClosure,
  237. buildTeardownToSetupsMap,
  238. collectFilesForProject,
  239. filterProjects,
  240. findTopLevelProjects
  241. });