index.cjs 7.2 KB

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