import { LogLevel, PatchError, RestoreError } from '../system'; import chalk from 'chalk'; import path from 'path'; import { getTsPackage } from '../ts-package'; import { getModuleFile, getTsModule, ModuleFile } from '../module'; import fs from 'fs'; import { getInstallerOptions, InstallerOptions } from '../options'; import { copyFileWithLock } from '../utils'; /* ****************************************************************************************************************** */ // region: Utils /* ****************************************************************************************************************** */ export function unpatch(moduleName: string, opts?: Partial): boolean export function unpatch(moduleNames: string[], opts?: Partial): boolean export function unpatch(moduleNameOrNames: string | string[], opts?: Partial): boolean { let res = false; const targetModuleNames = [ moduleNameOrNames ].flat(); if (!targetModuleNames.length) throw new PatchError(`Must provide at least one module name to patch`); const options = getInstallerOptions(opts); const { logger: log, dir } = options; /* Load Package */ const tsPackage = getTsPackage(dir); /* Get modules to patch and patch info */ const moduleFiles: [ string, ModuleFile ][] = targetModuleNames.map(m => [ m, getModuleFile(tsPackage.getModulePath(m)) ]); /* Determine patched files */ const unpatchableFiles = moduleFiles.filter(entry => { const [ moduleName, moduleFile ] = entry; if (moduleFile.patchDetail) return true; else { log([ '!', `${chalk.blueBright(moduleName)} is not patched. For details, run: ` + chalk.bgBlackBright('ts-patch check') ]); return false; } }); /* Restore files */ const errors: Record = {}; for (const entry of unpatchableFiles) { /* Load Module */ const { 1: moduleFile } = entry; const tsModule = getTsModule(tsPackage, moduleFile, { skipCache: true }); try { /* Get Backups */ const backupPaths: string[] = [] backupPaths.push(tsModule.backupCachePaths.js); if (tsModule.backupCachePaths.dts) backupPaths.push(tsModule.backupCachePaths.dts); const baseNames = backupPaths.map(p => path.basename(p)).join(' & '); log( [ '~', `Restoring ${chalk.blueBright(baseNames)} in ${chalk.blueBright(path.dirname(tsPackage.libDir))}` ], LogLevel.verbose ); /* Restore files */ for (const backupPath of backupPaths) { if (!fs.existsSync(backupPath)) throw new Error(`Cannot find backup file: ${backupPath}. Try reinstalling typescript.`); const moduleDir = path.dirname(tsModule.modulePath); /* Determine destination path (Need to use moduleContentPath if we're working with a cached module file */ const baseFileName = path.basename(backupPath); const destPathName = baseFileName === tsModule.moduleName ? path.basename(tsModule.moduleContentFilePath) : baseFileName; const destPath = path.join(moduleDir, destPathName); copyFileWithLock(backupPath, destPath); } log([ '+', chalk.green(`Successfully restored ${chalk.bold.yellow(baseNames)}.\r\n`) ], LogLevel.verbose); } catch (e) { errors[tsModule.moduleName] = e; } } /* Handle errors */ if (Object.keys(errors).length > 0) { Object.values(errors).forEach(e => { log([ '!', e.message ], LogLevel.verbose) }); log(''); throw new RestoreError( `[${Object.keys(errors).join(', ')}]`, 'Try reinstalling typescript.' + (options.logLevel < LogLevel.verbose ? ' (Or, run uninstall again with --verbose for specific error detail)' : '') ); } else { res = true; } return res; } // endregion