core.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.generator = exports.BaseGenerator = exports.TsHelper = exports.createTsHelperInstance = exports.getDefaultGeneratorConfig = exports.defaultConfig = void 0;
  4. const tslib_1 = require("tslib");
  5. const assert_1 = tslib_1.__importDefault(require("assert"));
  6. const events_1 = require("events");
  7. const fs_1 = tslib_1.__importDefault(require("fs"));
  8. const crypto_1 = tslib_1.__importDefault(require("crypto"));
  9. const chalk_1 = tslib_1.__importDefault(require("chalk"));
  10. const path_1 = tslib_1.__importDefault(require("path"));
  11. const generator = tslib_1.__importStar(require("./generator"));
  12. exports.generator = generator;
  13. const dot_prop_1 = require("dot-prop");
  14. const config_1 = require("./config");
  15. const watcher_1 = tslib_1.__importDefault(require("./watcher"));
  16. const base_1 = require("./generators/base");
  17. Object.defineProperty(exports, "BaseGenerator", { enumerable: true, get: function () { return base_1.BaseGenerator; } });
  18. const utils = tslib_1.__importStar(require("./utils"));
  19. const globby_1 = tslib_1.__importDefault(require("globby"));
  20. const isInUnitTest = process.env.NODE_ENV === 'test';
  21. exports.defaultConfig = {
  22. cwd: utils.convertString(process.env.ETS_CWD, process.cwd()),
  23. framework: utils.convertString(process.env.ETS_FRAMEWORK, 'egg'),
  24. typings: utils.convertString(process.env.ETS_TYPINGS, './typings'),
  25. caseStyle: utils.convertString(process.env.ETS_CASE_STYLE, 'lower'),
  26. autoRemoveJs: utils.convertString(process.env.ETS_AUTO_REMOVE_JS, true),
  27. throttle: utils.convertString(process.env.ETS_THROTTLE, 500),
  28. watch: utils.convertString(process.env.ETS_WATCH, false),
  29. watchOptions: undefined,
  30. execAtInit: utils.convertString(process.env.ETS_EXEC_AT_INIT, false),
  31. silent: utils.convertString(process.env.ETS_SILENT, isInUnitTest),
  32. generatorConfig: {},
  33. configFile: utils.convertString(process.env.ETS_CONFIG_FILE, '') || ['./tshelper', './tsHelper'],
  34. };
  35. // default watch dir
  36. function getDefaultGeneratorConfig(opt) {
  37. const baseConfig = {};
  38. // extend
  39. baseConfig.extend = {
  40. directory: 'app/extend',
  41. generator: 'extend',
  42. };
  43. // controller
  44. baseConfig.controller = {
  45. directory: 'app/controller',
  46. interface: config_1.declMapping.controller,
  47. generator: 'class',
  48. };
  49. // middleware
  50. baseConfig.middleware = {
  51. directory: 'app/middleware',
  52. interface: config_1.declMapping.middleware,
  53. generator: 'object',
  54. };
  55. // proxy
  56. baseConfig.proxy = {
  57. directory: 'app/proxy',
  58. interface: 'IProxy',
  59. generator: 'class',
  60. enabled: false,
  61. };
  62. // model
  63. baseConfig.model = {
  64. directory: 'app/model',
  65. generator: 'function',
  66. interface: 'IModel',
  67. caseStyle: 'upper',
  68. enabled: !(0, dot_prop_1.get)(opt === null || opt === void 0 ? void 0 : opt.eggInfo, 'config.customLoader.model'),
  69. };
  70. // config
  71. baseConfig.config = {
  72. directory: 'config',
  73. generator: 'config',
  74. trigger: ['add', 'unlink', 'change'],
  75. };
  76. // plugin
  77. baseConfig.plugin = {
  78. directory: 'config',
  79. generator: 'plugin',
  80. trigger: ['add', 'unlink', 'change'],
  81. };
  82. // service
  83. baseConfig.service = {
  84. directory: 'app/service',
  85. interface: config_1.declMapping.service,
  86. generator: 'auto',
  87. };
  88. // egg
  89. baseConfig.egg = {
  90. directory: 'app',
  91. generator: 'egg',
  92. watch: false,
  93. };
  94. // custom loader
  95. baseConfig.customLoader = {
  96. generator: 'custom',
  97. trigger: ['add', 'unlink', 'change'],
  98. };
  99. return baseConfig;
  100. }
  101. exports.getDefaultGeneratorConfig = getDefaultGeneratorConfig;
  102. class TsHelper extends events_1.EventEmitter {
  103. constructor(options) {
  104. super();
  105. this.watcherList = [];
  106. this.cacheDist = {};
  107. this.dtsFileList = [];
  108. // utils
  109. this.utils = utils;
  110. // configure ets
  111. this.configure(options);
  112. // init watcher
  113. this.initWatcher();
  114. }
  115. // build all watcher
  116. build() {
  117. // clean old files
  118. this.cleanFiles();
  119. this.watcherList.forEach(watcher => watcher.execute());
  120. return this;
  121. }
  122. // destroy
  123. destroy() {
  124. this.removeAllListeners();
  125. this.watcherList.forEach(item => item.destroy());
  126. this.watcherList.length = 0;
  127. }
  128. // log
  129. log(info, ignoreSilent) {
  130. if (!ignoreSilent && this.config.silent) {
  131. return;
  132. }
  133. utils.log(info);
  134. }
  135. warn(info) {
  136. this.log(chalk_1.default.yellow(info), !isInUnitTest);
  137. }
  138. // create oneForAll file
  139. createOneForAll(dist) {
  140. const config = this.config;
  141. const oneForAllDist = (typeof dist === 'string') ? dist : path_1.default.join(config.typings, './ets.d.ts');
  142. const oneForAllDistDir = path_1.default.dirname(oneForAllDist);
  143. // create d.ts includes all types.
  144. const distContent = config_1.dtsComment + this.dtsFileList
  145. .map(file => {
  146. const importUrl = path_1.default
  147. .relative(oneForAllDistDir, file.replace(/\.d\.ts$/, ''))
  148. .replace(/\/|\\/g, '/');
  149. return `import '${importUrl.startsWith('.') ? importUrl : `./${importUrl}`}';`;
  150. })
  151. .join('\n');
  152. this.emit('update', oneForAllDist);
  153. utils.writeFileSync(oneForAllDist, distContent);
  154. }
  155. // init watcher
  156. initWatcher() {
  157. Object.keys(this.config.generatorConfig).forEach(key => {
  158. this.registerWatcher(key, this.config.generatorConfig[key], false);
  159. });
  160. }
  161. // destroy watcher
  162. destroyWatcher(...refs) {
  163. this.watcherList = this.watcherList.filter(w => {
  164. if (refs.includes(w.ref)) {
  165. w.destroy();
  166. return false;
  167. }
  168. return true;
  169. });
  170. }
  171. // clean old files in startup
  172. cleanFiles() {
  173. const cwd = this.config.typings;
  174. globby_1.default.sync(['**/*.d.ts', '!**/node_modules'], { cwd })
  175. .forEach(file => {
  176. const fileUrl = path_1.default.resolve(cwd, file);
  177. const content = fs_1.default.readFileSync(fileUrl, 'utf-8');
  178. const isGeneratedByEts = content.match(config_1.dtsCommentRE);
  179. if (isGeneratedByEts)
  180. fs_1.default.unlinkSync(fileUrl);
  181. });
  182. }
  183. // register watcher
  184. registerWatcher(name, watchConfig, removeDuplicate = true) {
  185. if (removeDuplicate) {
  186. this.destroyWatcher(name);
  187. }
  188. if (watchConfig.hasOwnProperty('enabled') && !watchConfig.enabled) {
  189. return;
  190. }
  191. const directories = Array.isArray(watchConfig.directory)
  192. ? watchConfig.directory
  193. : [watchConfig.directory];
  194. // support array directory.
  195. return directories.map(dir => {
  196. const options = Object.assign({ name, ref: name, execAtInit: this.config.execAtInit }, watchConfig);
  197. if (dir) {
  198. options.directory = dir;
  199. }
  200. if (!this.config.watch) {
  201. options.watch = false;
  202. }
  203. const watcher = new watcher_1.default(this);
  204. watcher.on('update', this.generateTs.bind(this));
  205. watcher.init(options);
  206. this.watcherList.push(watcher);
  207. return watcher;
  208. });
  209. }
  210. loadWatcherConfig(config, options) {
  211. const configFile = options.configFile || config.configFile;
  212. const eggInfo = config.eggInfo;
  213. const getConfigFromPkg = pkg => (pkg.egg || {}).tsHelper;
  214. // read from enabled plugins
  215. if (eggInfo.plugins) {
  216. Object.keys(eggInfo.plugins)
  217. .forEach(k => {
  218. const pluginInfo = eggInfo.plugins[k];
  219. if (pluginInfo.enable && pluginInfo.path) {
  220. this.mergeConfig(config, getConfigFromPkg(utils.getPkgInfo(pluginInfo.path)));
  221. }
  222. });
  223. }
  224. // read from eggPaths
  225. if (eggInfo.eggPaths) {
  226. eggInfo.eggPaths.forEach(p => {
  227. this.mergeConfig(config, getConfigFromPkg(utils.getPkgInfo(p)));
  228. });
  229. }
  230. // read from package.json
  231. this.mergeConfig(config, getConfigFromPkg(utils.getPkgInfo(config.cwd)));
  232. // read from local file( default to tshelper | tsHelper )
  233. (Array.isArray(configFile) ? configFile : [configFile]).forEach(f => {
  234. this.mergeConfig(config, utils.requireFile(path_1.default.resolve(config.cwd, f)));
  235. });
  236. // merge local config and options to config
  237. this.mergeConfig(config, options);
  238. // create extra config
  239. config.tsConfig = utils.loadTsConfig(path_1.default.resolve(config.cwd, './tsconfig.json'));
  240. }
  241. // configure
  242. // options > configFile > package.json
  243. configure(options) {
  244. if (options.cwd) {
  245. options.cwd = path_1.default.resolve(exports.defaultConfig.cwd, options.cwd);
  246. }
  247. // base config
  248. const config = Object.assign({}, exports.defaultConfig);
  249. config.id = crypto_1.default.randomBytes(16).toString('base64');
  250. config.cwd = options.cwd || config.cwd;
  251. config.customLoader = config.customLoader || options.customLoader;
  252. // load egg info
  253. config.eggInfo = utils.getEggInfo({
  254. cwd: config.cwd,
  255. cacheIndex: config.id,
  256. customLoader: config.customLoader,
  257. });
  258. config.framework = options.framework || exports.defaultConfig.framework;
  259. config.generatorConfig = getDefaultGeneratorConfig(config);
  260. config.typings = path_1.default.resolve(config.cwd, config.typings);
  261. this.config = config;
  262. // load watcher config
  263. this.loadWatcherConfig(this.config, options);
  264. // deprecated framework
  265. if (this.config.framework && this.config.framework !== exports.defaultConfig.framework) {
  266. this.warn(`options.framework are deprecated, using default value(${exports.defaultConfig.framework}) instead`);
  267. }
  268. }
  269. generateTs(result, file, startTime) {
  270. const updateTs = (result, file) => {
  271. const config = this.config;
  272. const resultList = Array.isArray(result) ? result : [result];
  273. for (const item of resultList) {
  274. // check cache
  275. if (this.isCached(item.dist, item.content)) {
  276. return;
  277. }
  278. if (item.content) {
  279. // create file
  280. const dtsContent = `${config_1.dtsComment}\nimport '${config.framework}';\n${item.content}`;
  281. utils.writeFileSync(item.dist, dtsContent);
  282. this.emit('update', item.dist, file);
  283. this.log(`create ${path_1.default.relative(this.config.cwd, item.dist)} (${Date.now() - startTime}ms)`);
  284. this.updateDistFiles(item.dist);
  285. }
  286. else {
  287. if (!fs_1.default.existsSync(item.dist)) {
  288. return;
  289. }
  290. // remove file
  291. fs_1.default.unlinkSync(item.dist);
  292. delete this.cacheDist[item.dist];
  293. this.emit('remove', item.dist, file);
  294. this.log(`delete ${path_1.default.relative(this.config.cwd, item.dist)} (${Date.now() - startTime}ms)`);
  295. this.updateDistFiles(item.dist, true);
  296. }
  297. }
  298. };
  299. if (typeof result.then === 'function') {
  300. return result
  301. .then(r => updateTs(r, file))
  302. .catch(e => { this.log(e.message); });
  303. }
  304. updateTs(result, file);
  305. }
  306. updateDistFiles(fileUrl, isRemove) {
  307. const index = this.dtsFileList.indexOf(fileUrl);
  308. if (index >= 0) {
  309. if (isRemove) {
  310. this.dtsFileList.splice(index, 1);
  311. }
  312. }
  313. else {
  314. this.dtsFileList.push(fileUrl);
  315. }
  316. }
  317. isCached(fileUrl, content) {
  318. const cacheItem = this.cacheDist[fileUrl];
  319. if (content && cacheItem === content) {
  320. // no need to create file content is not changed.
  321. return true;
  322. }
  323. this.cacheDist[fileUrl] = content;
  324. return false;
  325. }
  326. // support dot prop config
  327. formatConfig(config) {
  328. const newConfig = {};
  329. Object.keys(config).forEach(key => (0, dot_prop_1.set)(newConfig, key, config[key]));
  330. return newConfig;
  331. }
  332. // merge ts helper options
  333. mergeConfig(base, ...args) {
  334. args.forEach(opt => {
  335. if (!opt)
  336. return;
  337. const config = this.formatConfig(opt);
  338. // compatitable for alias of generatorCofig
  339. if (config.watchDirs)
  340. config.generatorConfig = config.watchDirs;
  341. Object.keys(config).forEach(key => {
  342. if (key !== 'generatorConfig') {
  343. base[key] = config[key] === undefined ? base[key] : config[key];
  344. return;
  345. }
  346. const generatorConfig = config.generatorConfig || {};
  347. Object.keys(generatorConfig).forEach(k => {
  348. const item = generatorConfig[k];
  349. if (typeof item === 'boolean') {
  350. if (base.generatorConfig[k])
  351. base.generatorConfig[k].enabled = item;
  352. }
  353. else if (item) {
  354. // check private generator
  355. (0, assert_1.default)(!generator.isPrivateGenerator(item.generator), `${item.generator} is a private generator, can not configure in config file`);
  356. // compatible for deprecated fields
  357. [
  358. ['path', 'directory'],
  359. ].forEach(([oldValue, newValue]) => {
  360. if (item[oldValue]) {
  361. item[newValue] = item[oldValue];
  362. }
  363. });
  364. if (base.generatorConfig[k]) {
  365. Object.assign(base.generatorConfig[k], item);
  366. }
  367. else {
  368. base.generatorConfig[k] = item;
  369. }
  370. }
  371. });
  372. });
  373. });
  374. }
  375. }
  376. exports.default = TsHelper;
  377. exports.TsHelper = TsHelper;
  378. function createTsHelperInstance(options) {
  379. return new TsHelper(options);
  380. }
  381. exports.createTsHelperInstance = createTsHelperInstance;