18d3f3824SCedric van Puttenimport JsonFile from '@expo/json-file'; 28d3f3824SCedric van Puttenimport npmPackageArg from 'npm-package-arg'; 38d3f3824SCedric van Puttenimport path from 'path'; 48d3f3824SCedric van Putten 58a424bebSJames Ideimport { BasePackageManager } from './BasePackageManager'; 68d3f3824SCedric van Puttenimport { findYarnOrNpmWorkspaceRoot, NPM_LOCK_FILE } from '../utils/nodeWorkspaces'; 78d3f3824SCedric van Puttenimport { createPendingSpawnAsync } from '../utils/spawn'; 88d3f3824SCedric van Putten 98d3f3824SCedric van Puttenexport class NpmPackageManager extends BasePackageManager { 108d3f3824SCedric van Putten readonly name = 'npm'; 118d3f3824SCedric van Putten readonly bin = 'npm'; 128d3f3824SCedric van Putten readonly lockFile = NPM_LOCK_FILE; 138d3f3824SCedric van Putten 148d3f3824SCedric van Putten workspaceRoot() { 158d3f3824SCedric van Putten const root = findYarnOrNpmWorkspaceRoot(this.ensureCwdDefined('workspaceRoot')); 168d3f3824SCedric van Putten if (root) { 178d3f3824SCedric van Putten return new NpmPackageManager({ 188d3f3824SCedric van Putten ...this.options, 198d3f3824SCedric van Putten silent: this.silent, 208d3f3824SCedric van Putten log: this.log, 218d3f3824SCedric van Putten cwd: root, 228d3f3824SCedric van Putten }); 238d3f3824SCedric van Putten } 248d3f3824SCedric van Putten 258d3f3824SCedric van Putten return null; 268d3f3824SCedric van Putten } 278d3f3824SCedric van Putten 288d3f3824SCedric van Putten addAsync(namesOrFlags: string[] = []) { 298d3f3824SCedric van Putten if (!namesOrFlags.length) { 308d3f3824SCedric van Putten return this.installAsync(); 318d3f3824SCedric van Putten } 328d3f3824SCedric van Putten 338d3f3824SCedric van Putten const { flags, versioned, unversioned } = this.parsePackageSpecs(namesOrFlags); 348d3f3824SCedric van Putten 358d3f3824SCedric van Putten return createPendingSpawnAsync( 368d3f3824SCedric van Putten () => this.updatePackageFileAsync(versioned, 'dependencies'), 378d3f3824SCedric van Putten () => 388d3f3824SCedric van Putten !unversioned.length 398d3f3824SCedric van Putten ? this.runAsync(['install', ...flags]) 408d3f3824SCedric van Putten : this.runAsync(['install', '--save', ...flags, ...unversioned.map((spec) => spec.raw)]) 418d3f3824SCedric van Putten ); 428d3f3824SCedric van Putten } 438d3f3824SCedric van Putten 448d3f3824SCedric van Putten addDevAsync(namesOrFlags: string[] = []) { 458d3f3824SCedric van Putten if (!namesOrFlags.length) { 468d3f3824SCedric van Putten return this.installAsync(); 478d3f3824SCedric van Putten } 488d3f3824SCedric van Putten 498d3f3824SCedric van Putten const { flags, versioned, unversioned } = this.parsePackageSpecs(namesOrFlags); 508d3f3824SCedric van Putten 518d3f3824SCedric van Putten return createPendingSpawnAsync( 528d3f3824SCedric van Putten () => this.updatePackageFileAsync(versioned, 'devDependencies'), 538d3f3824SCedric van Putten () => 548d3f3824SCedric van Putten !unversioned.length 558d3f3824SCedric van Putten ? this.runAsync(['install', ...flags]) 568d3f3824SCedric van Putten : this.runAsync([ 578d3f3824SCedric van Putten 'install', 588d3f3824SCedric van Putten '--save-dev', 598d3f3824SCedric van Putten ...flags, 608d3f3824SCedric van Putten ...unversioned.map((spec) => spec.raw), 618d3f3824SCedric van Putten ]) 628d3f3824SCedric van Putten ); 638d3f3824SCedric van Putten } 648d3f3824SCedric van Putten 658d3f3824SCedric van Putten addGlobalAsync(namesOrFlags: string[] = []) { 668d3f3824SCedric van Putten if (!namesOrFlags.length) { 678d3f3824SCedric van Putten return this.installAsync(); 688d3f3824SCedric van Putten } 698d3f3824SCedric van Putten 708d3f3824SCedric van Putten return this.runAsync(['install', '--global', ...namesOrFlags]); 718d3f3824SCedric van Putten } 728d3f3824SCedric van Putten 738d3f3824SCedric van Putten removeAsync(namesOrFlags: string[]) { 748d3f3824SCedric van Putten return this.runAsync(['uninstall', ...namesOrFlags]); 758d3f3824SCedric van Putten } 768d3f3824SCedric van Putten 778d3f3824SCedric van Putten removeDevAsync(namesOrFlags: string[]) { 788d3f3824SCedric van Putten return this.runAsync(['uninstall', '--save-dev', ...namesOrFlags]); 798d3f3824SCedric van Putten } 808d3f3824SCedric van Putten 818d3f3824SCedric van Putten removeGlobalAsync(namesOrFlags: string[]) { 828d3f3824SCedric van Putten return this.runAsync(['uninstall', '--global', ...namesOrFlags]); 838d3f3824SCedric van Putten } 848d3f3824SCedric van Putten 858d3f3824SCedric van Putten /** 868d3f3824SCedric van Putten * Parse all package specifications from the names or flag list. 878d3f3824SCedric van Putten * The result from this method can be used for `.updatePackageFileAsync`. 888d3f3824SCedric van Putten */ 898d3f3824SCedric van Putten private parsePackageSpecs(namesOrFlags: string[]) { 908d3f3824SCedric van Putten const result: { 918d3f3824SCedric van Putten flags: string[]; 928d3f3824SCedric van Putten versioned: npmPackageArg.Result[]; 938d3f3824SCedric van Putten unversioned: npmPackageArg.Result[]; 948d3f3824SCedric van Putten } = { flags: [], versioned: [], unversioned: [] }; 958d3f3824SCedric van Putten 968d3f3824SCedric van Putten namesOrFlags 978d3f3824SCedric van Putten .map((name) => { 988d3f3824SCedric van Putten if (name.trim().startsWith('-')) { 998d3f3824SCedric van Putten result.flags.push(name); 1008d3f3824SCedric van Putten return null; 1018d3f3824SCedric van Putten } 1028d3f3824SCedric van Putten 1038d3f3824SCedric van Putten return npmPackageArg(name); 1048d3f3824SCedric van Putten }) 1058d3f3824SCedric van Putten .forEach((spec) => { 1068d3f3824SCedric van Putten // When using a dist-tag version of a library, we need to consider it as "unversioned". 1078d3f3824SCedric van Putten // Doing so will install that version with `npm install --save(-dev)`, and resolve the dist-tag properly. 1088d3f3824SCedric van Putten if (spec && spec.rawSpec && spec.type !== 'tag') { 1098d3f3824SCedric van Putten result.versioned.push(spec); 1108d3f3824SCedric van Putten } else if (spec) { 1118d3f3824SCedric van Putten result.unversioned.push(spec); 1128d3f3824SCedric van Putten } 1138d3f3824SCedric van Putten }); 1148d3f3824SCedric van Putten 1158d3f3824SCedric van Putten return result; 1168d3f3824SCedric van Putten } 1178d3f3824SCedric van Putten 1188d3f3824SCedric van Putten /** 1198d3f3824SCedric van Putten * Older npm versions have issues with mismatched nested dependencies when adding exact versions. 1208d3f3824SCedric van Putten * This propagates as issues like mismatched `@expo/config-pugins` versions. 1218d3f3824SCedric van Putten * As a workaround, we update the `package.json` directly and run `npm install`. 1228d3f3824SCedric van Putten */ 1238d3f3824SCedric van Putten private async updatePackageFileAsync( 1248d3f3824SCedric van Putten packageSpecs: npmPackageArg.Result[], 1258d3f3824SCedric van Putten packageType: 'dependencies' | 'devDependencies' 1268d3f3824SCedric van Putten ) { 1278d3f3824SCedric van Putten if (!packageSpecs.length) { 1288d3f3824SCedric van Putten return; 1298d3f3824SCedric van Putten } 1308d3f3824SCedric van Putten 1318d3f3824SCedric van Putten const pkgPath = path.join(this.options.cwd?.toString() || '.', 'package.json'); 132*22f8ba00STomasz Sapeta const pkg = 133*22f8ba00STomasz Sapeta await JsonFile.readAsync<Record<typeof packageType, { [pkgName: string]: string }>>(pkgPath); 1348d3f3824SCedric van Putten 1358d3f3824SCedric van Putten packageSpecs.forEach((spec) => { 1368d3f3824SCedric van Putten pkg[packageType] = pkg[packageType] || {}; 1378d3f3824SCedric van Putten pkg[packageType][spec.name!] = spec.rawSpec; 1388d3f3824SCedric van Putten }); 1398d3f3824SCedric van Putten 1408d3f3824SCedric van Putten await JsonFile.writeAsync(pkgPath, pkg, { json5: false }); 1418d3f3824SCedric van Putten } 1428d3f3824SCedric van Putten} 143