import { forEach } from 'lodash'; import { join } from 'path'; import { merge, Observable, Subject } from 'rxjs'; import { map, mergeMap, shareReplay } from 'rxjs/operators'; import { assertParameter } from '../utils/assert'; import { importModule, rxReadTypescriptFile, TypescriptFile } from '../utils/ast.utils'; import { firstOf } from '../utils/file.utils'; import { rxWriteTextFile } from '../utils/rx.file'; import { getPackageJson, parseReference } from '../utils/semver.utils'; import { APP_MODULE_FILE, CMD_ADD, OPTION_MODULE, OPTION_NAME, OPTION_PKG_DIR, OPTION_SOURCE, OPTION_URL, OPTION_VERBOSE, } from './../constants'; import { Options, optNormalizeDir } from './../options'; import { readFile as rxReadFile } from './../utils/rx.walk'; import { AbstractCommand } from './abstract.command'; import { Command } from './command'; import { HELP_LIBRARY_NAME, HELP_MODULE_FILE, HELP_PKG_DIR, HELP_PROJECT_SOURCE, HELP_REGISTRY_URL, HELP_VERBOSE, } from './help'; function _modifySource(aCfg: any, aFile: TypescriptFile, aLogger: Subject): TypescriptFile { // process the ng-module imports const ngModules = aCfg['ngModules'] || {}; // iterate let file = aFile; forEach(ngModules, (fromLocation, moduleName) => { // log this aLogger.next(`Importing module [${moduleName}] from [${fromLocation}] ...`); // add the module file = importModule(moduleName, fromLocation, file); }); // ok return file; } function _extractConfig(aPkg: any): any { // root config const root = aPkg['wchConfig'] || {}; // install const install = root['install'] || {}; // ok return install; } function _rewriteModule(aModuleFile: string, aLibPkg: any, aLogger: Subject): Observable { // log this aLogger.next(`Rewriting module [${aModuleFile}] ...`); // load the file const rxModule = rxReadTypescriptFile(aModuleFile); const cfg = _extractConfig(aLibPkg); // update const rxUpdated = rxModule.pipe(map(mod => _modifySource(cfg, mod, aLogger))); // write back return rxUpdated.pipe(mergeMap(mod => rxWriteTextFile(aModuleFile, mod.source))); } function _updatePackageFile(aDstPkg: any, aLibPkg: any): any { // update const dstDeps = aDstPkg.dependencies || (aDstPkg.dependencies = {}); // update dstDeps[aLibPkg.name] = `^${aLibPkg.version}`; // ok return aDstPkg; } function _rewritePackageJson(aPkgDir: string, aLibPkg: any, aLogger: Subject): Observable { // add the library const pkgFile = join(aPkgDir, 'package.json'); // log this aLogger.next(`Rewriting package ${pkgFile} ...`); // read, transform, write return rxReadFile(pkgFile).pipe( map(data => JSON.parse(data)), map(pkg => _updatePackageFile(pkg, aLibPkg)), map(pkg => JSON.stringify(pkg, undefined, 2)), mergeMap(data => rxWriteTextFile(pkgFile, data)) ); } export class AddLibraryCommand extends AbstractCommand implements Command { static readonly TYPE = 'library'; static readonly COMMAND = CMD_ADD; constructor(options: Options) { super(options); } printHelp(): void { // log the command line console.log(AddLibraryCommand.COMMAND, AddLibraryCommand.TYPE, `--${OPTION_NAME} `, `[--${OPTION_SOURCE} ]`, `[--${OPTION_PKG_DIR} ]`, `[--${OPTION_MODULE} ]`, `[--${OPTION_URL} ]`, `[--${OPTION_VERBOSE}]`); // help on the options console.log(`--${OPTION_NAME} `, ':', HELP_LIBRARY_NAME); console.log(`--${OPTION_SOURCE} `, ':', HELP_PROJECT_SOURCE); console.log(`--${OPTION_PKG_DIR} `, ':', HELP_PKG_DIR); console.log(`--${OPTION_MODULE} `, ':', HELP_MODULE_FILE); console.log(`--${OPTION_URL} `, ':', HELP_REGISTRY_URL); console.log(`--${OPTION_VERBOSE}`, ':', HELP_VERBOSE); } process(): Observable { // logger const logger = this.getLogger(); // data directory const opts = this.getOptions(); // module file const srcDirOpt = optNormalizeDir(firstOf(opts.src) || join(this.getAppDir(), 'app')); const moduleFile = opts.module || join(srcDirOpt, APP_MODULE_FILE); // package directory const pkgDir = this.getPackageDir(); // registry URL const regUrl = opts.url || 'https://registry.npmjs.org/'; // name of the library assertParameter(opts.name, OPTION_NAME); const name = opts.name!; // parse the name into a reference const ref = parseReference(name); // validate the directory const rxSrcDir = this.rxMkdirp(srcDirOpt); // log this logger.next(`Library: ${ref.name}, version: ${ref.spec}, ngModule: ${moduleFile}, registry: ${regUrl}`); // load the package const rxLibraryPkg = getPackageJson(regUrl, ref).pipe( shareReplay() ); // rewrite package const rxRewritePackage = rxLibraryPkg.pipe(mergeMap(libPkg => _rewritePackageJson(pkgDir, libPkg, logger))); // rewrite module const rxRewriteModule = rxLibraryPkg.pipe(mergeMap(libPkg => _rewriteModule(moduleFile, libPkg, logger))); // done const rxTotal = merge(rxRewritePackage, rxRewriteModule); // ok return rxTotal; } }