import { LogLevel, PatchError, TspError, } from '../system'; import { getTsPackage } from '../ts-package'; import chalk from 'chalk'; import { getModuleFile, getTsModule, TsModule, ModuleFile } from '../module'; import path from 'path'; import { getInstallerOptions, InstallerOptions } from '../options'; import { writeFileWithLock } from '../utils'; import { getPatchedSource } from '../patch/get-patched-source'; /* ****************************************************************************************************************** */ // region: Utils /* ****************************************************************************************************************** */ /** * Patch a TypeScript module */ export function patch(moduleName: string, opts?: Partial): boolean export function patch(moduleNames: string[], opts?: Partial): boolean export function patch(moduleNameOrNames: string | string[], opts?: Partial): boolean { 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, skipCache } = options; /* Load Package */ const tsPackage = getTsPackage(dir); /* Skip non-patchable modules (e.g. thin wrappers in TS 6+) */ const patchableModuleNames = targetModuleNames.filter(m => { if (!TsModule.isPatchable(m, tsPackage.majorVer)) { log([ '~', `${chalk.blueBright(m)} is not independently patchable in TS ${tsPackage.majorVer}+ ` + `(delegates to ${chalk.blueBright('typescript.js')})` ]); return false; } return true; }); /* Get modules to patch and patch info */ const moduleFiles: [ string, ModuleFile ][] = patchableModuleNames.map(m => [ m, getModuleFile(tsPackage.getModulePath(m)) ]); /* Determine files not already patched or outdated */ const patchableFiles = moduleFiles.filter(entry => { const [ moduleName, moduleFile ] = entry; if (!moduleFile.patchDetail || moduleFile.patchDetail.isOutdated) return true; else { log([ '!', `${chalk.blueBright(moduleName)} is already patched with the latest version. For details, run: ` + chalk.bgBlackBright('ts-patch check') ]); return false; } }); if (!patchableFiles.length) return true; /* Patch modules */ const failedModulePaths: string[] = []; for (let entry of patchableFiles) { /* Load Module */ const { 1: moduleFile } = entry; const tsModule = getTsModule(tsPackage, moduleFile, { skipCache: true }); const { moduleName, modulePath, moduleContentFilePath } = tsModule; log( [ '~', `Patching ${chalk.blueBright(moduleName)} in ${chalk.blueBright(path.dirname(modulePath ))}` ], LogLevel.verbose ); try { const { js, dts, loadedFromCache } = getPatchedSource(tsModule, { skipCache, log }); /* Write Patched Module */ log( [ '~', `Writing patched ${chalk.blueBright(moduleName)} to ` + `${chalk.blueBright(moduleContentFilePath)}${loadedFromCache ? ' (cached)' : ''}` ], LogLevel.verbose ); writeFileWithLock(moduleContentFilePath, js!); if (dts) writeFileWithLock(tsModule.dtsPath!, dts!); log([ '+', chalk.green(`Successfully patched ${chalk.bold.yellow(moduleName)}.\r\n`) ], LogLevel.verbose); } catch (e) { if (e instanceof TspError || options.logLevel >= LogLevel.verbose) log([ '!', e.message ]); failedModulePaths.push(tsModule.modulePath); } } if (failedModulePaths.length > 1) { log([ '!', `Some files can't be patched! You can run again with --verbose to get specific error detail. The following files are unable to be ` + `patched:\n - ${failedModulePaths.join('\n - ')}` ]); return false; } return true; } // endregion