index.mjs 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. import { existsSync } from 'node:fs';
  2. import { readFile } from 'node:fs/promises';
  3. import { normalize, resolve, join } from 'pathe';
  4. import { createRequire } from 'node:module';
  5. import { withTrailingSlash } from 'ufo';
  6. async function findup(cwd, match, options = {}) {
  7. const segments = normalize(cwd).split("/");
  8. while (segments.length > 0) {
  9. const path = segments.join("/");
  10. const result = await match(path);
  11. if (result || !options.includeParentDirs) {
  12. return result;
  13. }
  14. segments.pop();
  15. }
  16. }
  17. function cached(fn) {
  18. let v;
  19. return () => {
  20. if (v === void 0) {
  21. v = fn().then((r) => {
  22. v = r;
  23. return v;
  24. });
  25. }
  26. return v;
  27. };
  28. }
  29. const importExeca = cached(() => import('execa').then((r) => r.execa));
  30. const hasCorepack = cached(async () => {
  31. try {
  32. const execa = await importExeca();
  33. await execa("corepack", ["--version"]);
  34. return true;
  35. } catch {
  36. return false;
  37. }
  38. });
  39. async function executeCommand(command, args, options = {}) {
  40. const execaArgs = command === "npm" || command === "bun" || !await hasCorepack() ? [command, args] : ["corepack", [command, ...args]];
  41. const execa = await importExeca();
  42. await execa(execaArgs[0], execaArgs[1], {
  43. cwd: resolve(options.cwd || process.cwd()),
  44. stdio: options.silent ? "pipe" : "inherit"
  45. });
  46. }
  47. const NO_PACKAGE_MANAGER_DETECTED_ERROR_MSG = "No package manager auto-detected.";
  48. async function resolveOperationOptions(options = {}) {
  49. const cwd = options.cwd || process.cwd();
  50. const packageManager = (typeof options.packageManager === "string" ? packageManagers.find((pm) => pm.name === options.packageManager) : options.packageManager) || await detectPackageManager(options.cwd || process.cwd());
  51. if (!packageManager) {
  52. throw new Error(NO_PACKAGE_MANAGER_DETECTED_ERROR_MSG);
  53. }
  54. return {
  55. cwd,
  56. silent: options.silent ?? false,
  57. packageManager,
  58. dev: options.dev ?? false,
  59. workspace: options.workspace
  60. };
  61. }
  62. function getWorkspaceArgs(options) {
  63. if (!options.workspace) {
  64. return [];
  65. }
  66. const workspacePkg = typeof options.workspace === "string" && options.workspace !== "" ? options.workspace : void 0;
  67. if (options.packageManager.name === "pnpm") {
  68. return workspacePkg ? ["--dir", workspacePkg] : ["--workspace-root"];
  69. }
  70. if (options.packageManager.name === "npm") {
  71. return workspacePkg ? ["-w", workspacePkg] : ["--workspaces"];
  72. }
  73. if (options.packageManager.name === "yarn") {
  74. if (!options.packageManager.majorVersion || options.packageManager.majorVersion === "1") {
  75. return workspacePkg ? ["--cwd", workspacePkg] : ["-W"];
  76. } else {
  77. return workspacePkg ? ["workspace", workspacePkg] : [];
  78. }
  79. }
  80. return [];
  81. }
  82. function doesDependencyExist(name, options) {
  83. const require = createRequire(withTrailingSlash(options.cwd));
  84. try {
  85. const resolvedPath = require.resolve(name);
  86. return resolvedPath.startsWith(options.cwd);
  87. } catch {
  88. return false;
  89. }
  90. }
  91. const packageManagers = [
  92. { name: "npm", command: "npm", lockFile: "package-lock.json" },
  93. {
  94. name: "pnpm",
  95. command: "pnpm",
  96. lockFile: "pnpm-lock.yaml",
  97. files: ["pnpm-workspace.yaml"]
  98. },
  99. {
  100. name: "bun",
  101. command: "bun",
  102. lockFile: "bun.lockb"
  103. },
  104. {
  105. name: "yarn",
  106. command: "yarn",
  107. majorVersion: "1.0.0",
  108. lockFile: "yarn.lock"
  109. },
  110. {
  111. name: "yarn",
  112. command: "yarn",
  113. majorVersion: "3.0.0",
  114. lockFile: "yarn.lock",
  115. files: [".yarnrc.yml"]
  116. }
  117. ];
  118. async function detectPackageManager(cwd, options = {}) {
  119. const detected = await findup(
  120. cwd,
  121. async (path) => {
  122. if (!options.ignorePackageJSON) {
  123. const packageJSONPath = join(path, "package.json");
  124. if (existsSync(packageJSONPath)) {
  125. const packageJSON = JSON.parse(
  126. await readFile(packageJSONPath, "utf8")
  127. );
  128. if (packageJSON?.packageManager) {
  129. const [name, version = "0.0.0"] = packageJSON.packageManager.split("@");
  130. const majorVersion = version.split(".")[0];
  131. const packageManager = packageManagers.find(
  132. (pm) => pm.name === name && pm.majorVersion === majorVersion
  133. ) || packageManagers.find((pm) => pm.name === name);
  134. return {
  135. ...packageManager,
  136. name,
  137. command: name,
  138. version,
  139. majorVersion
  140. };
  141. }
  142. }
  143. }
  144. if (!options.ignoreLockFile) {
  145. for (const packageManager of packageManagers) {
  146. const detectionsFiles = [
  147. packageManager.lockFile,
  148. ...packageManager.files || []
  149. ].filter(Boolean);
  150. if (detectionsFiles.some((file) => existsSync(resolve(path, file)))) {
  151. return {
  152. ...packageManager
  153. };
  154. }
  155. }
  156. }
  157. },
  158. {
  159. includeParentDirs: options.includeParentDirs ?? true
  160. }
  161. );
  162. return detected;
  163. }
  164. async function installDependencies(options = {}) {
  165. const resolvedOptions = await resolveOperationOptions(options);
  166. await executeCommand(resolvedOptions.packageManager.command, ["install"], {
  167. cwd: resolvedOptions.cwd,
  168. silent: resolvedOptions.silent
  169. });
  170. }
  171. async function addDependency(name, options = {}) {
  172. const resolvedOptions = await resolveOperationOptions(options);
  173. const names = Array.isArray(name) ? name : [name];
  174. const args = (resolvedOptions.packageManager.name === "yarn" ? [
  175. ...getWorkspaceArgs(resolvedOptions),
  176. "add",
  177. resolvedOptions.dev ? "-D" : "",
  178. ...names
  179. ] : [
  180. resolvedOptions.packageManager.name === "npm" ? "install" : "add",
  181. ...getWorkspaceArgs(resolvedOptions),
  182. resolvedOptions.dev ? "-D" : "",
  183. ...names
  184. ]).filter(Boolean);
  185. await executeCommand(resolvedOptions.packageManager.command, args, {
  186. cwd: resolvedOptions.cwd,
  187. silent: resolvedOptions.silent
  188. });
  189. }
  190. async function addDevDependency(name, options = {}) {
  191. await addDependency(name, { ...options, dev: true });
  192. }
  193. async function removeDependency(name, options = {}) {
  194. const resolvedOptions = await resolveOperationOptions(options);
  195. const args = (resolvedOptions.packageManager.name === "yarn" ? [
  196. ...getWorkspaceArgs(resolvedOptions),
  197. "remove",
  198. resolvedOptions.dev ? "-D" : "",
  199. name
  200. ] : [
  201. resolvedOptions.packageManager.name === "npm" ? "uninstall" : "remove",
  202. ...getWorkspaceArgs(resolvedOptions),
  203. resolvedOptions.dev ? "-D" : "",
  204. name
  205. ]).filter(Boolean);
  206. await executeCommand(resolvedOptions.packageManager.command, args, {
  207. cwd: resolvedOptions.cwd,
  208. silent: resolvedOptions.silent
  209. });
  210. }
  211. async function ensureDependencyInstalled(name, options = {}) {
  212. const resolvedOptions = await resolveOperationOptions(options);
  213. const dependencyExists = doesDependencyExist(name, resolvedOptions);
  214. if (dependencyExists) {
  215. return true;
  216. }
  217. await addDependency(name, resolvedOptions);
  218. }
  219. export { addDependency, addDevDependency, detectPackageManager, ensureDependencyInstalled, installDependencies, packageManagers, removeDependency };