f568ef0ef2acb15a2663b04de4ec3b353bce2c2825c2c1bc2882ece2bd130c4877a38de07470ef41c045833fb582d1f56e0b95dfcb2f416f122d70095575ce 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. import { statSync, promises } from 'node:fs';
  2. import { resolve, join, isAbsolute, dirname } from 'pathe';
  3. import { resolvePath } from 'mlly';
  4. import { parseJSON, parseJSONC, stringifyJSON, stringifyJSONC } from 'confbox';
  5. const defaultFindOptions = {
  6. startingFrom: ".",
  7. rootPattern: /^node_modules$/,
  8. reverse: false,
  9. test: (filePath) => {
  10. try {
  11. if (statSync(filePath).isFile()) {
  12. return true;
  13. }
  14. } catch {
  15. }
  16. }
  17. };
  18. async function findFile(filename, _options = {}) {
  19. const filenames = Array.isArray(filename) ? filename : [filename];
  20. const options = { ...defaultFindOptions, ..._options };
  21. const basePath = resolve(options.startingFrom);
  22. const leadingSlash = basePath[0] === "/";
  23. const segments = basePath.split("/").filter(Boolean);
  24. if (leadingSlash) {
  25. segments[0] = "/" + segments[0];
  26. }
  27. let root = segments.findIndex((r) => r.match(options.rootPattern));
  28. if (root === -1) {
  29. root = 0;
  30. }
  31. if (options.reverse) {
  32. for (let index = root + 1; index <= segments.length; index++) {
  33. for (const filename2 of filenames) {
  34. const filePath = join(...segments.slice(0, index), filename2);
  35. if (await options.test(filePath)) {
  36. return filePath;
  37. }
  38. }
  39. }
  40. } else {
  41. for (let index = segments.length; index > root; index--) {
  42. for (const filename2 of filenames) {
  43. const filePath = join(...segments.slice(0, index), filename2);
  44. if (await options.test(filePath)) {
  45. return filePath;
  46. }
  47. }
  48. }
  49. }
  50. throw new Error(
  51. `Cannot find matching ${filename} in ${options.startingFrom} or parent directories`
  52. );
  53. }
  54. function findNearestFile(filename, _options = {}) {
  55. return findFile(filename, _options);
  56. }
  57. function findFarthestFile(filename, _options = {}) {
  58. return findFile(filename, { ..._options, reverse: true });
  59. }
  60. function definePackageJSON(package_) {
  61. return package_;
  62. }
  63. function defineTSConfig(tsconfig) {
  64. return tsconfig;
  65. }
  66. const FileCache = /* @__PURE__ */ new Map();
  67. async function readPackageJSON(id, options = {}) {
  68. const resolvedPath = await resolvePackageJSON(id, options);
  69. const cache = options.cache && typeof options.cache !== "boolean" ? options.cache : FileCache;
  70. if (options.cache && cache.has(resolvedPath)) {
  71. return cache.get(resolvedPath);
  72. }
  73. const blob = await promises.readFile(resolvedPath, "utf8");
  74. let parsed;
  75. try {
  76. parsed = parseJSON(blob);
  77. } catch {
  78. parsed = parseJSONC(blob);
  79. }
  80. cache.set(resolvedPath, parsed);
  81. return parsed;
  82. }
  83. async function writePackageJSON(path, package_) {
  84. await promises.writeFile(path, stringifyJSON(package_));
  85. }
  86. async function readTSConfig(id, options = {}) {
  87. const resolvedPath = await resolveTSConfig(id, options);
  88. const cache = options.cache && typeof options.cache !== "boolean" ? options.cache : FileCache;
  89. if (options.cache && cache.has(resolvedPath)) {
  90. return cache.get(resolvedPath);
  91. }
  92. const text = await promises.readFile(resolvedPath, "utf8");
  93. const parsed = parseJSONC(text);
  94. cache.set(resolvedPath, parsed);
  95. return parsed;
  96. }
  97. async function writeTSConfig(path, tsconfig) {
  98. await promises.writeFile(path, stringifyJSONC(tsconfig));
  99. }
  100. async function resolvePackageJSON(id = process.cwd(), options = {}) {
  101. const resolvedPath = isAbsolute(id) ? id : await resolvePath(id, options);
  102. return findNearestFile("package.json", {
  103. startingFrom: resolvedPath,
  104. ...options
  105. });
  106. }
  107. async function resolveTSConfig(id = process.cwd(), options = {}) {
  108. const resolvedPath = isAbsolute(id) ? id : await resolvePath(id, options);
  109. return findNearestFile("tsconfig.json", {
  110. startingFrom: resolvedPath,
  111. ...options
  112. });
  113. }
  114. const lockFiles = [
  115. "yarn.lock",
  116. "package-lock.json",
  117. "pnpm-lock.yaml",
  118. "npm-shrinkwrap.json",
  119. "bun.lockb",
  120. "bun.lock"
  121. ];
  122. async function resolveLockfile(id = process.cwd(), options = {}) {
  123. const resolvedPath = isAbsolute(id) ? id : await resolvePath(id, options);
  124. const _options = { startingFrom: resolvedPath, ...options };
  125. try {
  126. return await findNearestFile(lockFiles, _options);
  127. } catch {
  128. }
  129. throw new Error("No lockfile found from " + id);
  130. }
  131. async function findWorkspaceDir(id = process.cwd(), options = {}) {
  132. const resolvedPath = isAbsolute(id) ? id : await resolvePath(id, options);
  133. const _options = { startingFrom: resolvedPath, ...options };
  134. try {
  135. const r = await findNearestFile(".git/config", _options);
  136. return resolve(r, "../..");
  137. } catch {
  138. }
  139. try {
  140. const r = await resolveLockfile(resolvedPath, {
  141. ..._options,
  142. reverse: true
  143. });
  144. return dirname(r);
  145. } catch {
  146. }
  147. try {
  148. const r = await findFile(resolvedPath, _options);
  149. return dirname(r);
  150. } catch {
  151. }
  152. throw new Error("Cannot detect workspace root from " + id);
  153. }
  154. export { definePackageJSON, defineTSConfig, findFarthestFile, findFile, findNearestFile, findWorkspaceDir, readPackageJSON, readTSConfig, resolveLockfile, resolvePackageJSON, resolveTSConfig, writePackageJSON, writeTSConfig };