18d3f3824SCedric van Puttenimport spawnAsync, { SpawnPromise, SpawnResult } from '@expo/spawn-async';
28d3f3824SCedric van Puttenimport assert from 'assert';
38d3f3824SCedric van Puttenimport fs from 'fs';
48d3f3824SCedric van Puttenimport path from 'path';
58d3f3824SCedric van Putten
68d3f3824SCedric van Puttenimport { PackageManager, PackageManagerOptions } from '../PackageManager';
78d3f3824SCedric van Puttenimport { PendingSpawnPromise } from '../utils/spawn';
88d3f3824SCedric van Putten
98d3f3824SCedric van Puttenexport abstract class BasePackageManager implements PackageManager {
108d3f3824SCedric van Putten  readonly silent: boolean;
118d3f3824SCedric van Putten  readonly log?: (...args: any) => void;
128d3f3824SCedric van Putten  readonly options: PackageManagerOptions;
138d3f3824SCedric van Putten
148d3f3824SCedric van Putten  constructor({ silent, log, env = process.env, ...options }: PackageManagerOptions = {}) {
158d3f3824SCedric van Putten    this.silent = !!silent;
168d3f3824SCedric van Putten    this.log = log ?? (!silent ? console.log : undefined);
178d3f3824SCedric van Putten    this.options = {
18*efdcf92fSCedric van Putten      stdio: silent ? undefined : 'inherit',
198d3f3824SCedric van Putten      ...options,
208d3f3824SCedric van Putten      env: { ...this.getDefaultEnvironment(), ...env },
218d3f3824SCedric van Putten    };
228d3f3824SCedric van Putten  }
238d3f3824SCedric van Putten
248d3f3824SCedric van Putten  /** Get the name of the package manager */
258d3f3824SCedric van Putten  abstract readonly name: string;
268d3f3824SCedric van Putten  /** Get the executable binary of the package manager */
278d3f3824SCedric van Putten  abstract readonly bin: string;
288d3f3824SCedric van Putten  /** Get the lockfile for this package manager */
298d3f3824SCedric van Putten  abstract readonly lockFile: string;
308d3f3824SCedric van Putten
318d3f3824SCedric van Putten  /** Get the default environment variables used when running the package manager. */
328d3f3824SCedric van Putten  protected getDefaultEnvironment(): Record<string, string> {
338d3f3824SCedric van Putten    return {
348d3f3824SCedric van Putten      ADBLOCK: '1',
358d3f3824SCedric van Putten      DISABLE_OPENCOLLECTIVE: '1',
368d3f3824SCedric van Putten    };
378d3f3824SCedric van Putten  }
388d3f3824SCedric van Putten
398d3f3824SCedric van Putten  abstract addAsync(
408d3f3824SCedric van Putten    namesOrFlags: string[]
418d3f3824SCedric van Putten  ): SpawnPromise<SpawnResult> | PendingSpawnPromise<SpawnResult>;
428d3f3824SCedric van Putten  abstract addDevAsync(
438d3f3824SCedric van Putten    namesOrFlags: string[]
448d3f3824SCedric van Putten  ): SpawnPromise<SpawnResult> | PendingSpawnPromise<SpawnResult>;
458d3f3824SCedric van Putten  abstract addGlobalAsync(
468d3f3824SCedric van Putten    namesOrFlags: string[]
478d3f3824SCedric van Putten  ): SpawnPromise<SpawnResult> | PendingSpawnPromise<SpawnResult>;
488d3f3824SCedric van Putten
498d3f3824SCedric van Putten  abstract removeAsync(
508d3f3824SCedric van Putten    namesOrFlags: string[]
518d3f3824SCedric van Putten  ): SpawnPromise<SpawnResult> | PendingSpawnPromise<SpawnResult>;
528d3f3824SCedric van Putten  abstract removeDevAsync(
538d3f3824SCedric van Putten    namesOrFlags: string[]
548d3f3824SCedric van Putten  ): SpawnPromise<SpawnResult> | PendingSpawnPromise<SpawnResult>;
558d3f3824SCedric van Putten  abstract removeGlobalAsync(
568d3f3824SCedric van Putten    namesOrFlags: string[]
578d3f3824SCedric van Putten  ): SpawnPromise<SpawnResult> | PendingSpawnPromise<SpawnResult>;
588d3f3824SCedric van Putten
598d3f3824SCedric van Putten  abstract workspaceRoot(): PackageManager | null;
608d3f3824SCedric van Putten
618d3f3824SCedric van Putten  /** Ensure the CWD is set to a non-empty string */
628d3f3824SCedric van Putten  protected ensureCwdDefined(method?: string): string {
638d3f3824SCedric van Putten    const cwd = this.options.cwd?.toString();
648d3f3824SCedric van Putten    const className = (this.constructor as typeof BasePackageManager).name;
658d3f3824SCedric van Putten    const methodName = method ? `.${method}` : '';
668d3f3824SCedric van Putten    assert(cwd, `cwd is required for ${className}${methodName}`);
678d3f3824SCedric van Putten    return cwd;
688d3f3824SCedric van Putten  }
698d3f3824SCedric van Putten
708d3f3824SCedric van Putten  runAsync(command: string[]) {
718d3f3824SCedric van Putten    this.log?.(`> ${this.name} ${command.join(' ')}`);
72*efdcf92fSCedric van Putten    return spawnAsync(this.bin, command, this.options);
738d3f3824SCedric van Putten  }
748d3f3824SCedric van Putten
758d3f3824SCedric van Putten  async versionAsync() {
768d3f3824SCedric van Putten    return await this.runAsync(['--version']).then(({ stdout }) => stdout.trim());
778d3f3824SCedric van Putten  }
788d3f3824SCedric van Putten
798d3f3824SCedric van Putten  async getConfigAsync(key: string) {
808d3f3824SCedric van Putten    return await this.runAsync(['config', 'get', key]).then(({ stdout }) => stdout.trim());
818d3f3824SCedric van Putten  }
828d3f3824SCedric van Putten
838d3f3824SCedric van Putten  async removeLockfileAsync() {
848d3f3824SCedric van Putten    const cwd = this.ensureCwdDefined('removeLockFile');
858d3f3824SCedric van Putten    const filePath = path.join(cwd, this.lockFile);
868d3f3824SCedric van Putten    await fs.promises.rm(filePath, { force: true });
878d3f3824SCedric van Putten  }
888d3f3824SCedric van Putten
898d3f3824SCedric van Putten  installAsync(flags: string[] = []): SpawnPromise<SpawnResult> | PendingSpawnPromise<SpawnResult> {
908d3f3824SCedric van Putten    return this.runAsync(['install', ...flags]);
918d3f3824SCedric van Putten  }
928d3f3824SCedric van Putten
938d3f3824SCedric van Putten  async uninstallAsync() {
948d3f3824SCedric van Putten    const cwd = this.ensureCwdDefined('uninstallAsync');
958d3f3824SCedric van Putten    const modulesPath = path.join(cwd, 'node_modules');
968d3f3824SCedric van Putten    await fs.promises.rm(modulesPath, { force: true, recursive: true });
978d3f3824SCedric van Putten  }
988d3f3824SCedric van Putten}
99