"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.generator = exports.BaseGenerator = exports.TsHelper = exports.createTsHelperInstance = exports.getDefaultGeneratorConfig = exports.defaultConfig = void 0; const tslib_1 = require("tslib"); const assert_1 = tslib_1.__importDefault(require("assert")); const events_1 = require("events"); const fs_1 = tslib_1.__importDefault(require("fs")); const crypto_1 = tslib_1.__importDefault(require("crypto")); const chalk_1 = tslib_1.__importDefault(require("chalk")); const path_1 = tslib_1.__importDefault(require("path")); const generator = tslib_1.__importStar(require("./generator")); exports.generator = generator; const dot_prop_1 = require("dot-prop"); const config_1 = require("./config"); const watcher_1 = tslib_1.__importDefault(require("./watcher")); const base_1 = require("./generators/base"); Object.defineProperty(exports, "BaseGenerator", { enumerable: true, get: function () { return base_1.BaseGenerator; } }); const utils = tslib_1.__importStar(require("./utils")); const globby_1 = tslib_1.__importDefault(require("globby")); const isInUnitTest = process.env.NODE_ENV === 'test'; exports.defaultConfig = { cwd: utils.convertString(process.env.ETS_CWD, process.cwd()), framework: utils.convertString(process.env.ETS_FRAMEWORK, 'egg'), typings: utils.convertString(process.env.ETS_TYPINGS, './typings'), caseStyle: utils.convertString(process.env.ETS_CASE_STYLE, 'lower'), autoRemoveJs: utils.convertString(process.env.ETS_AUTO_REMOVE_JS, true), throttle: utils.convertString(process.env.ETS_THROTTLE, 500), watch: utils.convertString(process.env.ETS_WATCH, false), watchOptions: undefined, execAtInit: utils.convertString(process.env.ETS_EXEC_AT_INIT, false), silent: utils.convertString(process.env.ETS_SILENT, isInUnitTest), generatorConfig: {}, configFile: utils.convertString(process.env.ETS_CONFIG_FILE, '') || ['./tshelper', './tsHelper'], }; // default watch dir function getDefaultGeneratorConfig(opt) { const baseConfig = {}; // extend baseConfig.extend = { directory: 'app/extend', generator: 'extend', }; // controller baseConfig.controller = { directory: 'app/controller', interface: config_1.declMapping.controller, generator: 'class', }; // middleware baseConfig.middleware = { directory: 'app/middleware', interface: config_1.declMapping.middleware, generator: 'object', }; // proxy baseConfig.proxy = { directory: 'app/proxy', interface: 'IProxy', generator: 'class', enabled: false, }; // model baseConfig.model = { directory: 'app/model', generator: 'function', interface: 'IModel', caseStyle: 'upper', enabled: !(0, dot_prop_1.get)(opt === null || opt === void 0 ? void 0 : opt.eggInfo, 'config.customLoader.model'), }; // config baseConfig.config = { directory: 'config', generator: 'config', trigger: ['add', 'unlink', 'change'], }; // plugin baseConfig.plugin = { directory: 'config', generator: 'plugin', trigger: ['add', 'unlink', 'change'], }; // service baseConfig.service = { directory: 'app/service', interface: config_1.declMapping.service, generator: 'auto', }; // egg baseConfig.egg = { directory: 'app', generator: 'egg', watch: false, }; // custom loader baseConfig.customLoader = { generator: 'custom', trigger: ['add', 'unlink', 'change'], }; return baseConfig; } exports.getDefaultGeneratorConfig = getDefaultGeneratorConfig; class TsHelper extends events_1.EventEmitter { constructor(options) { super(); this.watcherList = []; this.cacheDist = {}; this.dtsFileList = []; // utils this.utils = utils; // configure ets this.configure(options); // init watcher this.initWatcher(); } // build all watcher build() { // clean old files this.cleanFiles(); this.watcherList.forEach(watcher => watcher.execute()); return this; } // destroy destroy() { this.removeAllListeners(); this.watcherList.forEach(item => item.destroy()); this.watcherList.length = 0; } // log log(info, ignoreSilent) { if (!ignoreSilent && this.config.silent) { return; } utils.log(info); } warn(info) { this.log(chalk_1.default.yellow(info), !isInUnitTest); } // create oneForAll file createOneForAll(dist) { const config = this.config; const oneForAllDist = (typeof dist === 'string') ? dist : path_1.default.join(config.typings, './ets.d.ts'); const oneForAllDistDir = path_1.default.dirname(oneForAllDist); // create d.ts includes all types. const distContent = config_1.dtsComment + this.dtsFileList .map(file => { const importUrl = path_1.default .relative(oneForAllDistDir, file.replace(/\.d\.ts$/, '')) .replace(/\/|\\/g, '/'); return `import '${importUrl.startsWith('.') ? importUrl : `./${importUrl}`}';`; }) .join('\n'); this.emit('update', oneForAllDist); utils.writeFileSync(oneForAllDist, distContent); } // init watcher initWatcher() { Object.keys(this.config.generatorConfig).forEach(key => { this.registerWatcher(key, this.config.generatorConfig[key], false); }); } // destroy watcher destroyWatcher(...refs) { this.watcherList = this.watcherList.filter(w => { if (refs.includes(w.ref)) { w.destroy(); return false; } return true; }); } // clean old files in startup cleanFiles() { const cwd = this.config.typings; globby_1.default.sync(['**/*.d.ts', '!**/node_modules'], { cwd }) .forEach(file => { const fileUrl = path_1.default.resolve(cwd, file); const content = fs_1.default.readFileSync(fileUrl, 'utf-8'); const isGeneratedByEts = content.match(config_1.dtsCommentRE); if (isGeneratedByEts) fs_1.default.unlinkSync(fileUrl); }); } // register watcher registerWatcher(name, watchConfig, removeDuplicate = true) { if (removeDuplicate) { this.destroyWatcher(name); } if (watchConfig.hasOwnProperty('enabled') && !watchConfig.enabled) { return; } const directories = Array.isArray(watchConfig.directory) ? watchConfig.directory : [watchConfig.directory]; // support array directory. return directories.map(dir => { const options = Object.assign({ name, ref: name, execAtInit: this.config.execAtInit }, watchConfig); if (dir) { options.directory = dir; } if (!this.config.watch) { options.watch = false; } const watcher = new watcher_1.default(this); watcher.on('update', this.generateTs.bind(this)); watcher.init(options); this.watcherList.push(watcher); return watcher; }); } loadWatcherConfig(config, options) { const configFile = options.configFile || config.configFile; const eggInfo = config.eggInfo; const getConfigFromPkg = pkg => (pkg.egg || {}).tsHelper; // read from enabled plugins if (eggInfo.plugins) { Object.keys(eggInfo.plugins) .forEach(k => { const pluginInfo = eggInfo.plugins[k]; if (pluginInfo.enable && pluginInfo.path) { this.mergeConfig(config, getConfigFromPkg(utils.getPkgInfo(pluginInfo.path))); } }); } // read from eggPaths if (eggInfo.eggPaths) { eggInfo.eggPaths.forEach(p => { this.mergeConfig(config, getConfigFromPkg(utils.getPkgInfo(p))); }); } // read from package.json this.mergeConfig(config, getConfigFromPkg(utils.getPkgInfo(config.cwd))); // read from local file( default to tshelper | tsHelper ) (Array.isArray(configFile) ? configFile : [configFile]).forEach(f => { this.mergeConfig(config, utils.requireFile(path_1.default.resolve(config.cwd, f))); }); // merge local config and options to config this.mergeConfig(config, options); // create extra config config.tsConfig = utils.loadTsConfig(path_1.default.resolve(config.cwd, './tsconfig.json')); } // configure // options > configFile > package.json configure(options) { if (options.cwd) { options.cwd = path_1.default.resolve(exports.defaultConfig.cwd, options.cwd); } // base config const config = Object.assign({}, exports.defaultConfig); config.id = crypto_1.default.randomBytes(16).toString('base64'); config.cwd = options.cwd || config.cwd; config.customLoader = config.customLoader || options.customLoader; // load egg info config.eggInfo = utils.getEggInfo({ cwd: config.cwd, cacheIndex: config.id, customLoader: config.customLoader, }); config.framework = options.framework || exports.defaultConfig.framework; config.generatorConfig = getDefaultGeneratorConfig(config); config.typings = path_1.default.resolve(config.cwd, config.typings); this.config = config; // load watcher config this.loadWatcherConfig(this.config, options); // deprecated framework if (this.config.framework && this.config.framework !== exports.defaultConfig.framework) { this.warn(`options.framework are deprecated, using default value(${exports.defaultConfig.framework}) instead`); } } generateTs(result, file, startTime) { const updateTs = (result, file) => { const config = this.config; const resultList = Array.isArray(result) ? result : [result]; for (const item of resultList) { // check cache if (this.isCached(item.dist, item.content)) { return; } if (item.content) { // create file const dtsContent = `${config_1.dtsComment}\nimport '${config.framework}';\n${item.content}`; utils.writeFileSync(item.dist, dtsContent); this.emit('update', item.dist, file); this.log(`create ${path_1.default.relative(this.config.cwd, item.dist)} (${Date.now() - startTime}ms)`); this.updateDistFiles(item.dist); } else { if (!fs_1.default.existsSync(item.dist)) { return; } // remove file fs_1.default.unlinkSync(item.dist); delete this.cacheDist[item.dist]; this.emit('remove', item.dist, file); this.log(`delete ${path_1.default.relative(this.config.cwd, item.dist)} (${Date.now() - startTime}ms)`); this.updateDistFiles(item.dist, true); } } }; if (typeof result.then === 'function') { return result .then(r => updateTs(r, file)) .catch(e => { this.log(e.message); }); } updateTs(result, file); } updateDistFiles(fileUrl, isRemove) { const index = this.dtsFileList.indexOf(fileUrl); if (index >= 0) { if (isRemove) { this.dtsFileList.splice(index, 1); } } else { this.dtsFileList.push(fileUrl); } } isCached(fileUrl, content) { const cacheItem = this.cacheDist[fileUrl]; if (content && cacheItem === content) { // no need to create file content is not changed. return true; } this.cacheDist[fileUrl] = content; return false; } // support dot prop config formatConfig(config) { const newConfig = {}; Object.keys(config).forEach(key => (0, dot_prop_1.set)(newConfig, key, config[key])); return newConfig; } // merge ts helper options mergeConfig(base, ...args) { args.forEach(opt => { if (!opt) return; const config = this.formatConfig(opt); // compatitable for alias of generatorCofig if (config.watchDirs) config.generatorConfig = config.watchDirs; Object.keys(config).forEach(key => { if (key !== 'generatorConfig') { base[key] = config[key] === undefined ? base[key] : config[key]; return; } const generatorConfig = config.generatorConfig || {}; Object.keys(generatorConfig).forEach(k => { const item = generatorConfig[k]; if (typeof item === 'boolean') { if (base.generatorConfig[k]) base.generatorConfig[k].enabled = item; } else if (item) { // check private generator (0, assert_1.default)(!generator.isPrivateGenerator(item.generator), `${item.generator} is a private generator, can not configure in config file`); // compatible for deprecated fields [ ['path', 'directory'], ].forEach(([oldValue, newValue]) => { if (item[oldValue]) { item[newValue] = item[oldValue]; } }); if (base.generatorConfig[k]) { Object.assign(base.generatorConfig[k], item); } else { base.generatorConfig[k] = item; } } }); }); }); } } exports.default = TsHelper; exports.TsHelper = TsHelper; function createTsHelperInstance(options) { return new TsHelper(options); } exports.createTsHelperInstance = createTsHelperInstance;