import fs from 'node:fs/promises'; import { inject, injectable } from 'inversify'; import { CompressionService, EnvService, HttpService, PathService, } from '../services/index.ts'; import { LinkToolService, type ShellWrapperConfig } from '../services/index.ts'; import { NoInitTools, NoPrepareTools } from '../tools/index.ts'; import { type InstallToolType, type SpawnOptions, type SpawnResult, isValid, spawn, } from '../utils/index.ts'; @injectable() export abstract class BaseInstallService { @inject(PathService) protected readonly pathSvc!: PathService; @inject(EnvService) protected readonly envSvc!: EnvService; @inject(HttpService) protected readonly http!: HttpService; @inject(CompressionService) protected readonly compress!: CompressionService; @inject(LinkToolService) private readonly _link!: LinkToolService; /** * Optional tool alias used to refer to this tool as parent. */ get alias(): string { return this.name; } /** * Tool name */ abstract readonly name: string; /** * A tool can depend on another tool to work. * Eg. composer depends on php. */ readonly parent?: string; /** * Some tools can only be installed as root, so they are only available at * image build time. Eg. git is installed via apt. */ readonly needsRoot: boolean = false; /** * Tools which are not installed into a versioned tool path can't be * uninstalled, eg. git is installed system wide via apt. */ readonly canUninstall: boolean = true; /** * Optional tool type for dynamic uninstallation support. * Currently `npm`, `gem` or `pip`. */ readonly type?: InstallToolType; abstract install(version: string): Promise; /** * @deprecated Unused */ async isInstalled(version: string): Promise { return !!(await this.pathSvc.findVersionedToolPath(this.name, version)); } async isInitialized(): Promise { return await this.pathSvc.isInitialized(this.name); } async isPrepared(): Promise { return await this.pathSvc.isPrepared(this.name); } abstract link(version: string): Promise; needsInitialize(): boolean { return !NoInitTools.includes(this.name); } needsPrepare(): boolean { return !NoPrepareTools.includes(this.name); } /** * Post-installation steps. * Used for relinking executables. * @param version Version that was installed */ postInstall(_version: string): Promise { return Promise.resolve(); } test(_version: string): Promise { return Promise.resolve(); } toString(): string { return this.name; } async uninstall(version: string): Promise { await fs.rm(this.pathSvc.versionedToolPath(this.name, version), { recursive: true, force: true, }); } validate(version: string): Promise { return Promise.resolve(isValid(version)); } protected shellwrapper(options: ShellWrapperConfig): Promise { return this._link.shellwrapper(this.name, options); } protected _spawn( command: string, args: string[], options?: SpawnOptions, ): Promise { return spawn(command, args, { cwd: this.envSvc.tmpDir, ...options }); } }