1
0

package-json-ref.ts 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. import { note } from '@clack/prompts';
  2. import spawn from 'cross-spawn';
  3. import fs from 'fs-extra';
  4. import path from 'path';
  5. import { Project } from 'ts-morph';
  6. export interface PackageToInstall {
  7. pkg: string;
  8. version?: string;
  9. isDevDependency?: boolean;
  10. installInRoot?: boolean;
  11. }
  12. export class PackageJson {
  13. private _vendurePackageJsonPath: string | undefined;
  14. private _rootPackageJsonPath: string | undefined;
  15. constructor(private readonly project: Project) {}
  16. get vendurePackageJsonPath() {
  17. return this.locatePackageJsonWithVendureDependency();
  18. }
  19. get rootPackageJsonPath() {
  20. return this.locateRootPackageJson();
  21. }
  22. determineVendureVersion(): string | undefined {
  23. const packageJson = this.getPackageJsonContent();
  24. return packageJson.dependencies['@vendure/core'];
  25. }
  26. async installPackages(requiredPackages: PackageToInstall[]) {
  27. const packageJson = this.getPackageJsonContent();
  28. const packagesToInstall = requiredPackages.filter(({ pkg, version, isDevDependency }) => {
  29. const hasDependency = isDevDependency
  30. ? packageJson.devDependencies[pkg]
  31. : packageJson.dependencies[pkg];
  32. return !hasDependency;
  33. });
  34. const depsToInstall = packagesToInstall
  35. .filter(p => !p.isDevDependency && packageJson.dependencies?.[p.pkg] === undefined)
  36. .map(p => `${p.pkg}${p.version ? `@${p.version}` : ''}`);
  37. const devDepsToInstall = packagesToInstall
  38. .filter(p => p.isDevDependency && packageJson.devDependencies?.[p.pkg] === undefined)
  39. .map(p => `${p.pkg}${p.version ? `@${p.version}` : ''}`);
  40. if (depsToInstall.length) {
  41. await this.runPackageManagerInstall(depsToInstall, false);
  42. }
  43. if (devDepsToInstall.length) {
  44. await this.runPackageManagerInstall(devDepsToInstall, true);
  45. }
  46. }
  47. getPackageJsonContent() {
  48. const packageJsonPath = this.locatePackageJsonWithVendureDependency();
  49. if (!packageJsonPath || !fs.existsSync(packageJsonPath)) {
  50. note(
  51. `Could not find a package.json in the current directory. Please run this command from the root of a Vendure project.`,
  52. );
  53. return false;
  54. }
  55. return fs.readJsonSync(packageJsonPath);
  56. }
  57. determinePackageManager(): 'yarn' | 'npm' | 'pnpm' {
  58. const rootDir = this.getPackageRootDir().getPath();
  59. const yarnLockPath = path.join(rootDir, 'yarn.lock');
  60. const npmLockPath = path.join(rootDir, 'package-lock.json');
  61. const pnpmLockPath = path.join(rootDir, 'pnpm-lock.yaml');
  62. if (fs.existsSync(yarnLockPath)) {
  63. return 'yarn';
  64. }
  65. if (fs.existsSync(npmLockPath)) {
  66. return 'npm';
  67. }
  68. if (fs.existsSync(pnpmLockPath)) {
  69. return 'pnpm';
  70. }
  71. return 'npm';
  72. }
  73. addScript(scriptName: string, script: string) {
  74. const packageJson = this.getPackageJsonContent();
  75. packageJson.scripts = packageJson.scripts || {};
  76. packageJson.scripts[scriptName] = script;
  77. const packageJsonPath = this.vendurePackageJsonPath;
  78. if (packageJsonPath) {
  79. fs.writeJsonSync(packageJsonPath, packageJson, { spaces: 2 });
  80. }
  81. }
  82. getPackageRootDir() {
  83. const rootDir = this.project.getDirectory('.');
  84. if (!rootDir) {
  85. throw new Error('Could not find the root directory of the project');
  86. }
  87. return rootDir;
  88. }
  89. locateRootPackageJson() {
  90. if (this._rootPackageJsonPath) {
  91. return this._rootPackageJsonPath;
  92. }
  93. const rootDir = this.getPackageRootDir().getPath();
  94. const rootPackageJsonPath = path.join(rootDir, 'package.json');
  95. if (fs.existsSync(rootPackageJsonPath)) {
  96. this._rootPackageJsonPath = rootPackageJsonPath;
  97. return rootPackageJsonPath;
  98. }
  99. return null;
  100. }
  101. locatePackageJsonWithVendureDependency() {
  102. if (this._vendurePackageJsonPath) {
  103. return this._vendurePackageJsonPath;
  104. }
  105. const rootDir = this.getPackageRootDir().getPath();
  106. const potentialMonorepoDirs = ['packages', 'apps', 'libs'];
  107. const rootPackageJsonPath = path.join(this.getPackageRootDir().getPath(), 'package.json');
  108. if (this.hasVendureDependency(rootPackageJsonPath)) {
  109. return rootPackageJsonPath;
  110. }
  111. for (const dir of potentialMonorepoDirs) {
  112. const monorepoDir = path.join(rootDir, dir);
  113. // Check for a package.json in all subdirs
  114. if (fs.existsSync(monorepoDir)) {
  115. for (const subDir of fs.readdirSync(monorepoDir)) {
  116. const packageJsonPath = path.join(monorepoDir, subDir, 'package.json');
  117. if (this.hasVendureDependency(packageJsonPath)) {
  118. this._vendurePackageJsonPath = packageJsonPath;
  119. return packageJsonPath;
  120. }
  121. }
  122. }
  123. }
  124. return null;
  125. }
  126. private hasVendureDependency(packageJsonPath: string) {
  127. if (!fs.existsSync(packageJsonPath)) {
  128. return false;
  129. }
  130. const packageJson = fs.readJsonSync(packageJsonPath);
  131. return !!packageJson.dependencies?.['@vendure/core'];
  132. }
  133. private async runPackageManagerInstall(dependencies: string[], isDev: boolean) {
  134. return new Promise<void>((resolve, reject) => {
  135. const packageManager = this.determinePackageManager();
  136. let command = '';
  137. let args: string[] = [];
  138. if (packageManager === 'yarn') {
  139. command = 'yarnpkg';
  140. args = ['add', '--exact', '--ignore-engines'];
  141. if (isDev) {
  142. args.push('--dev');
  143. }
  144. args = args.concat(dependencies);
  145. } else if (packageManager === 'pnpm') {
  146. command = 'pnpm';
  147. args = ['add', '--save-exact'].concat(dependencies);
  148. if (isDev) {
  149. args.push('--save-dev', '--workspace-root');
  150. }
  151. } else {
  152. command = 'npm';
  153. args = ['install', '--save', '--save-exact', '--loglevel', 'error'].concat(dependencies);
  154. if (isDev) {
  155. args.push('--save-dev');
  156. }
  157. }
  158. const child = spawn(command, args, { stdio: 'ignore' });
  159. child.on('close', code => {
  160. if (code !== 0) {
  161. const message = 'An error occurred when installing dependencies.';
  162. reject({
  163. message,
  164. command: `${command} ${args.join(' ')}`,
  165. });
  166. return;
  167. }
  168. resolve();
  169. });
  170. });
  171. }
  172. }