projectUtils.js 9.2 KB

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