import * as fs from 'fs-extra'; import * as path from 'path'; import semver from 'semver'; import { IEnvironment } from '../types/environment'; import { spawnProcess } from '../util/run'; /** * Execute NPM commands */ export class NPMExecutor { private installed: Record = {}; private packageFile: string = path.join( this.environment.path, 'package.json' ); constructor(private environment: IEnvironment, private coreModule: string) {} /** * Create a package.json file and install the core. */ public async init(): Promise { // Initialize npm environment const c1 = await spawnProcess('npm', ['init', '-y'], this.environment); if (c1.code > 0) { throw new Error(c1.stderr.join('\n')); } // Install core module const c2 = await spawnProcess( 'npm', ['install', this.coreModule], this.environment ); if (c2.code > 0) { throw new Error(c2.stderr.join('\n')); } } /** * Load a package.json file into memory */ public async loadPackageFile(): Promise { if (!(await fs.pathExists(this.packageFile))) { await this.init(); } const jsonData = await fs.readJson(this.packageFile); if (!jsonData.dependencies) { return; } this.installed = jsonData.dependencies; } /** * Install a npm package (examples: `@squeebot/core`, `@squeebot/core@3.3.3`, `node-ical`) * @param pkg Package name * @param development Install as devDependency */ public async installPackage(pkg: string, development = false): Promise { if (!(await fs.pathExists(this.packageFile))) { await this.init(); } let spi = 0; if (pkg.indexOf('@') === 0) { spi = 1; } const pkgRefSplit = pkg.split('@'); const pkgFullName = (spi === 1 ? '@' : '') + pkgRefSplit[spi]; const pkgVersion = this.removeVersionWildcard(pkgRefSplit[spi + 1]) || 'latest'; const installedVersion = this.installed[pkgFullName]; if (installedVersion) { // Ignore git or http version if ( installedVersion.startsWith('http') || installedVersion.startsWith('git+') || installedVersion.startsWith('github:') ) return; if (installedVersion !== 'latest' && pkgVersion !== 'latest') { const cardless = this.removeVersionWildcard(installedVersion) as string; if (semver.lte(pkgVersion, cardless)) { return; } } } const { code, stderr, stdout } = await spawnProcess( 'npm', development ? ['install', '--save-dev', pkg] : ['install', pkg], this.environment ); if (code > 0) { throw new Error(stderr.join('\n')); } await this.loadPackageFile(); } /** * Uninstall a npm package. * * See `installPackage` for more info. * @param pkg Package name */ public async uninstallPackage(pkg: string): Promise { if (!(await fs.pathExists(this.packageFile))) { await this.init(); } const { code, stderr, stdout } = await spawnProcess( 'npm', ['remove', pkg], this.environment ); if (code > 0) { throw new Error(stderr.join('\n')); } await this.loadPackageFile(); } private removeVersionWildcard(str?: string): string | undefined { return str?.replace(/\^|>|=/, ''); } }