utils.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.cleanEmpty = exports.modifierHas = exports.isClass = exports.findExportNode = exports.loadTsConfig = exports.camelProp = exports.formatProp = exports.readJson5 = exports.readJson = exports.getPkgInfo = exports.parseJson = exports.extend = exports.requireFile = exports.moduleExist = exports.resolveModule = exports.removeSameNameJs = exports.toArray = exports.formatPath = exports.getModuleObjByPath = exports.cleanJs = exports.writeFileSync = exports.getImportStr = exports.log = exports.pickFields = exports.strToFn = exports.loadModules = exports.checkMaybeIsJsProj = exports.checkMaybeIsTsProj = exports.writeTsConfig = exports.writeJsConfig = exports.loadFiles = exports.isIdentifierName = exports.convertString = exports.getEggInfo = exports.TS_CONFIG = exports.JS_CONFIG = void 0;
  4. const tslib_1 = require("tslib");
  5. const fs_1 = tslib_1.__importDefault(require("fs"));
  6. const mkdirp_1 = tslib_1.__importDefault(require("mkdirp"));
  7. const globby_1 = tslib_1.__importDefault(require("globby"));
  8. const path_1 = tslib_1.__importDefault(require("path"));
  9. const typescript_1 = tslib_1.__importDefault(require("typescript"));
  10. const yn_1 = tslib_1.__importDefault(require("yn"));
  11. const config_1 = require("./config");
  12. const child_process_1 = require("child_process");
  13. const json5_1 = tslib_1.__importDefault(require("json5"));
  14. exports.JS_CONFIG = {
  15. include: ['**/*'],
  16. };
  17. exports.TS_CONFIG = {
  18. compilerOptions: {
  19. target: typescript_1.default.ScriptTarget.ES2017,
  20. module: typescript_1.default.ModuleKind.CommonJS,
  21. strict: true,
  22. noImplicitAny: false,
  23. experimentalDecorators: true,
  24. emitDecoratorMetadata: true,
  25. allowSyntheticDefaultImports: true,
  26. charset: 'utf8',
  27. allowJs: false,
  28. pretty: true,
  29. lib: ['es6'],
  30. noEmitOnError: false,
  31. noUnusedLocals: true,
  32. noUnusedParameters: true,
  33. allowUnreachableCode: false,
  34. allowUnusedLabels: false,
  35. strictPropertyInitialization: false,
  36. noFallthroughCasesInSwitch: true,
  37. skipLibCheck: true,
  38. skipDefaultLibCheck: true,
  39. inlineSourceMap: true,
  40. },
  41. };
  42. const cacheEggInfo = {};
  43. function getEggInfo(option) {
  44. const { cacheIndex, cwd, customLoader } = option;
  45. const cacheKey = cacheIndex ? `${cacheIndex}${cwd}` : undefined;
  46. const caches = cacheKey ? (cacheEggInfo[cacheKey] = cacheEggInfo[cacheKey] || {}) : undefined;
  47. const end = (json) => {
  48. if (caches) {
  49. caches.eggInfo = json;
  50. caches.cacheTime = Date.now();
  51. }
  52. if (option.callback) {
  53. return option.callback(json);
  54. }
  55. return json;
  56. };
  57. // check cache
  58. if (caches) {
  59. if (caches.cacheTime && (Date.now() - caches.cacheTime) < 1000) {
  60. return end(caches.eggInfo);
  61. }
  62. else if (caches.runningPromise) {
  63. return caches.runningPromise;
  64. }
  65. }
  66. // get egg info from customLoader
  67. if (customLoader) {
  68. return end({
  69. config: customLoader.config,
  70. plugins: customLoader.plugins,
  71. eggPaths: customLoader.eggPaths,
  72. });
  73. }
  74. // prepare options
  75. const cmd = 'node';
  76. const args = [path_1.default.resolve(__dirname, './scripts/eggInfo')];
  77. const opt = {
  78. cwd,
  79. env: Object.assign(Object.assign(Object.assign({}, process.env), { TS_NODE_TYPE_CHECK: 'false', TS_NODE_TRANSPILE_ONLY: 'true', TS_NODE_FILES: 'false', EGG_TYPESCRIPT: 'true', CACHE_REQUIRE_PATHS_FILE: path_1.default.resolve(config_1.tmpDir, './requirePaths.json') }), option.env),
  80. };
  81. if (option.async) {
  82. // cache promise
  83. caches.runningPromise = new Promise((resolve, reject) => {
  84. (0, child_process_1.execFile)(cmd, args, opt, err => {
  85. caches.runningPromise = null;
  86. if (err)
  87. reject(err);
  88. resolve(end(parseJson(fs_1.default.readFileSync(config_1.eggInfoPath, 'utf-8'))));
  89. });
  90. });
  91. return caches.runningPromise;
  92. }
  93. try {
  94. (0, child_process_1.execFileSync)(cmd, args, opt);
  95. return end(parseJson(fs_1.default.readFileSync(config_1.eggInfoPath, 'utf-8')));
  96. }
  97. catch (e) {
  98. return end({});
  99. }
  100. }
  101. exports.getEggInfo = getEggInfo;
  102. // convert string to same type with default value
  103. function convertString(val, defaultVal) {
  104. if (val === undefined)
  105. return defaultVal;
  106. switch (typeof defaultVal) {
  107. case 'boolean':
  108. return (0, yn_1.default)(val, { default: defaultVal });
  109. case 'number':
  110. const num = +val;
  111. return (isNaN(num) ? defaultVal : num);
  112. case 'string':
  113. return val;
  114. default:
  115. return defaultVal;
  116. }
  117. }
  118. exports.convertString = convertString;
  119. function isIdentifierName(s) {
  120. return /^[$A-Z_][0-9A-Z_$]*$/i.test(s);
  121. }
  122. exports.isIdentifierName = isIdentifierName;
  123. // load ts/js files
  124. function loadFiles(cwd, pattern) {
  125. pattern = pattern || '**/*.(js|ts)';
  126. pattern = Array.isArray(pattern) ? pattern : [pattern];
  127. const fileList = globby_1.default.sync(pattern.concat(['!**/*.d.ts']), { cwd });
  128. return fileList.filter(f => {
  129. // filter same name js/ts
  130. return !(f.endsWith('.js') &&
  131. fileList.includes(f.substring(0, f.length - 2) + 'ts'));
  132. });
  133. }
  134. exports.loadFiles = loadFiles;
  135. // write jsconfig.json to cwd
  136. function writeJsConfig(cwd) {
  137. const jsconfigUrl = path_1.default.resolve(cwd, './jsconfig.json');
  138. if (!fs_1.default.existsSync(jsconfigUrl)) {
  139. fs_1.default.writeFileSync(jsconfigUrl, JSON.stringify(exports.JS_CONFIG, null, 2));
  140. return jsconfigUrl;
  141. }
  142. }
  143. exports.writeJsConfig = writeJsConfig;
  144. // write tsconfig.json to cwd
  145. function writeTsConfig(cwd) {
  146. const tsconfigUrl = path_1.default.resolve(cwd, './tsconfig.json');
  147. if (!fs_1.default.existsSync(tsconfigUrl)) {
  148. fs_1.default.writeFileSync(tsconfigUrl, JSON.stringify(exports.TS_CONFIG, null, 2));
  149. return tsconfigUrl;
  150. }
  151. }
  152. exports.writeTsConfig = writeTsConfig;
  153. function checkMaybeIsTsProj(cwd, pkgInfo) {
  154. pkgInfo = pkgInfo || getPkgInfo(cwd);
  155. return (pkgInfo.egg && pkgInfo.egg.typescript) ||
  156. fs_1.default.existsSync(path_1.default.resolve(cwd, './tsconfig.json')) ||
  157. fs_1.default.existsSync(path_1.default.resolve(cwd, './config/config.default.ts')) ||
  158. fs_1.default.existsSync(path_1.default.resolve(cwd, './config/config.ts'));
  159. }
  160. exports.checkMaybeIsTsProj = checkMaybeIsTsProj;
  161. function checkMaybeIsJsProj(cwd, pkgInfo) {
  162. pkgInfo = pkgInfo || getPkgInfo(cwd);
  163. const isJs = !checkMaybeIsTsProj(cwd, pkgInfo) &&
  164. (fs_1.default.existsSync(path_1.default.resolve(cwd, './config/config.default.js')) ||
  165. fs_1.default.existsSync(path_1.default.resolve(cwd, './jsconfig.json')));
  166. return isJs;
  167. }
  168. exports.checkMaybeIsJsProj = checkMaybeIsJsProj;
  169. // load modules to object
  170. function loadModules(cwd, loadDefault, preHandle) {
  171. const modules = {};
  172. preHandle = preHandle || (fn => fn);
  173. fs_1.default
  174. .readdirSync(cwd)
  175. .filter(f => f.endsWith('.js'))
  176. .forEach(f => {
  177. const name = f.substring(0, f.lastIndexOf('.'));
  178. const obj = require(path_1.default.resolve(cwd, name));
  179. if (loadDefault && obj.default) {
  180. modules[name] = preHandle(obj.default);
  181. }
  182. else {
  183. modules[name] = preHandle(obj);
  184. }
  185. });
  186. return modules;
  187. }
  188. exports.loadModules = loadModules;
  189. // convert string to function
  190. function strToFn(fn) {
  191. if (typeof fn === 'string') {
  192. return (...args) => fn.replace(/{{\s*(\d+)\s*}}/g, (_, index) => args[index]);
  193. }
  194. return fn;
  195. }
  196. exports.strToFn = strToFn;
  197. // pick fields from object
  198. function pickFields(obj, fields) {
  199. const newObj = {};
  200. fields.forEach(f => (newObj[f] = obj[f]));
  201. return newObj;
  202. }
  203. exports.pickFields = pickFields;
  204. // log
  205. function log(msg, prefix = true) {
  206. console.info(`${prefix ? '[egg-ts-helper] ' : ''}${msg}`);
  207. }
  208. exports.log = log;
  209. // get import context
  210. function getImportStr(from, to, moduleName, importStar) {
  211. const extname = path_1.default.extname(to);
  212. const toPathWithoutExt = to.substring(0, to.length - extname.length);
  213. const importPath = path_1.default.relative(from, toPathWithoutExt).replace(/\/|\\/g, '/');
  214. const isTS = extname === '.ts' || fs_1.default.existsSync(`${toPathWithoutExt}.d.ts`);
  215. const importStartStr = isTS && importStar ? '* as ' : '';
  216. const fromStr = isTS ? `from '${importPath}'` : `= require('${importPath}')`;
  217. return `import ${importStartStr}${moduleName} ${fromStr};`;
  218. }
  219. exports.getImportStr = getImportStr;
  220. // write file, using fs.writeFileSync to block io that d.ts can create before egg app started.
  221. function writeFileSync(fileUrl, content) {
  222. mkdirp_1.default.sync(path_1.default.dirname(fileUrl));
  223. fs_1.default.writeFileSync(fileUrl, content);
  224. }
  225. exports.writeFileSync = writeFileSync;
  226. // clean same name js/ts
  227. function cleanJs(cwd) {
  228. const fileList = [];
  229. globby_1.default
  230. .sync(['**/*.ts', '**/*.tsx', '!**/*.d.ts', '!**/node_modules'], { cwd })
  231. .forEach(f => {
  232. const jf = removeSameNameJs(path_1.default.resolve(cwd, f));
  233. if (jf)
  234. fileList.push(path_1.default.relative(cwd, jf));
  235. });
  236. if (fileList.length) {
  237. console.info('[egg-ts-helper] These file was deleted because the same name ts file was exist!\n');
  238. console.info(' ' + fileList.join('\n ') + '\n');
  239. }
  240. }
  241. exports.cleanJs = cleanJs;
  242. // get moduleName by file path
  243. function getModuleObjByPath(f) {
  244. const props = f.substring(0, f.lastIndexOf('.')).split('/');
  245. // composing moduleName
  246. const moduleName = props.map(prop => camelProp(prop, 'upper')).join('');
  247. return {
  248. props,
  249. moduleName,
  250. };
  251. }
  252. exports.getModuleObjByPath = getModuleObjByPath;
  253. // format path sep to /
  254. function formatPath(str) {
  255. return str.replace(/\/|\\/g, '/');
  256. }
  257. exports.formatPath = formatPath;
  258. function toArray(pattern) {
  259. return pattern ? (Array.isArray(pattern) ? pattern : [pattern]) : [];
  260. }
  261. exports.toArray = toArray;
  262. // remove same name js
  263. function removeSameNameJs(f) {
  264. if (!f.match(/\.tsx?$/) || f.endsWith('.d.ts')) {
  265. return;
  266. }
  267. const jf = f.replace(/tsx?$/, 'js');
  268. if (fs_1.default.existsSync(jf)) {
  269. fs_1.default.unlinkSync(jf);
  270. return jf;
  271. }
  272. }
  273. exports.removeSameNameJs = removeSameNameJs;
  274. // resolve module
  275. function resolveModule(url) {
  276. try {
  277. return require.resolve(url);
  278. }
  279. catch (e) {
  280. return undefined;
  281. }
  282. }
  283. exports.resolveModule = resolveModule;
  284. // check whether module is exist
  285. function moduleExist(mod, cwd) {
  286. const nodeModulePath = path_1.default.resolve(cwd || process.cwd(), 'node_modules', mod);
  287. return fs_1.default.existsSync(nodeModulePath) || resolveModule(mod);
  288. }
  289. exports.moduleExist = moduleExist;
  290. // require modules
  291. function requireFile(url) {
  292. url = url && resolveModule(url);
  293. if (!url) {
  294. return undefined;
  295. }
  296. let exp = require(url);
  297. if (exp.__esModule && 'default' in exp) {
  298. exp = exp.default;
  299. }
  300. return exp;
  301. }
  302. exports.requireFile = requireFile;
  303. // extend
  304. function extend(obj, ...args) {
  305. args.forEach(source => {
  306. let descriptor, prop;
  307. if (source) {
  308. for (prop in source) {
  309. descriptor = Object.getOwnPropertyDescriptor(source, prop);
  310. Object.defineProperty(obj, prop, descriptor);
  311. }
  312. }
  313. });
  314. return obj;
  315. }
  316. exports.extend = extend;
  317. // parse json
  318. function parseJson(jsonStr) {
  319. if (jsonStr) {
  320. try {
  321. return JSON.parse(jsonStr);
  322. }
  323. catch (e) {
  324. return {};
  325. }
  326. }
  327. else {
  328. return {};
  329. }
  330. }
  331. exports.parseJson = parseJson;
  332. // load package.json
  333. function getPkgInfo(cwd) {
  334. return readJson(path_1.default.resolve(cwd, './package.json'));
  335. }
  336. exports.getPkgInfo = getPkgInfo;
  337. // read json file
  338. function readJson(jsonUrl) {
  339. if (!fs_1.default.existsSync(jsonUrl))
  340. return {};
  341. return parseJson(fs_1.default.readFileSync(jsonUrl, 'utf-8'));
  342. }
  343. exports.readJson = readJson;
  344. function readJson5(jsonUrl) {
  345. if (!fs_1.default.existsSync(jsonUrl))
  346. return {};
  347. return json5_1.default.parse(fs_1.default.readFileSync(jsonUrl, 'utf-8'));
  348. }
  349. exports.readJson5 = readJson5;
  350. // format property
  351. function formatProp(prop) {
  352. return prop.replace(/[._-][a-z]/gi, s => s.substring(1).toUpperCase());
  353. }
  354. exports.formatProp = formatProp;
  355. // like egg-core/file-loader
  356. function camelProp(property, caseStyle) {
  357. if (typeof caseStyle === 'function') {
  358. return caseStyle(property);
  359. }
  360. // camel transfer
  361. property = formatProp(property);
  362. let first = property[0];
  363. // istanbul ignore next
  364. switch (caseStyle) {
  365. case 'lower':
  366. first = first.toLowerCase();
  367. break;
  368. case 'upper':
  369. first = first.toUpperCase();
  370. break;
  371. case 'camel':
  372. break;
  373. default:
  374. break;
  375. }
  376. return first + property.substring(1);
  377. }
  378. exports.camelProp = camelProp;
  379. // load tsconfig.json
  380. function loadTsConfig(tsconfigPath) {
  381. tsconfigPath = path_1.default.extname(tsconfigPath) === '.json' ? tsconfigPath : `${tsconfigPath}.json`;
  382. const tsConfig = readJson5(tsconfigPath);
  383. if (tsConfig.extends) {
  384. const extendPattern = tsConfig.extends;
  385. const tsconfigDirName = path_1.default.dirname(tsconfigPath);
  386. const maybeRealExtendPath = [
  387. path_1.default.resolve(tsconfigDirName, extendPattern),
  388. path_1.default.resolve(tsconfigDirName, `${extendPattern}.json`),
  389. ];
  390. if (!path_1.default.extname(tsConfig.extends) && !extendPattern.startsWith('.') && !extendPattern.startsWith('/')) {
  391. maybeRealExtendPath.push(path_1.default.resolve(tsconfigDirName, 'node_modules', extendPattern, 'tsconfig.json'), path_1.default.resolve(process.cwd(), 'node_modules', extendPattern, 'tsconfig.json'));
  392. }
  393. const extendRealPath = maybeRealExtendPath.find(f => fs_1.default.existsSync(f));
  394. if (extendRealPath) {
  395. const extendTsConfig = loadTsConfig(extendRealPath);
  396. return Object.assign(Object.assign({}, tsConfig.compilerOptions), extendTsConfig);
  397. }
  398. }
  399. return tsConfig.compilerOptions || {};
  400. }
  401. exports.loadTsConfig = loadTsConfig;
  402. /**
  403. * ts ast utils
  404. */
  405. // find export node from sourcefile.
  406. function findExportNode(code) {
  407. const sourceFile = typescript_1.default.createSourceFile('file.ts', code, typescript_1.default.ScriptTarget.ES2017, true);
  408. const cache = new Map();
  409. const exportNodeList = [];
  410. let exportDefaultNode;
  411. sourceFile.statements.forEach(node => {
  412. // each node in root scope
  413. if (modifierHas(node, typescript_1.default.SyntaxKind.ExportKeyword)) {
  414. if (modifierHas(node, typescript_1.default.SyntaxKind.DefaultKeyword)) {
  415. // export default
  416. exportDefaultNode = node;
  417. }
  418. else {
  419. // export variable
  420. if (typescript_1.default.isVariableStatement(node)) {
  421. node.declarationList.declarations.forEach(declare => exportNodeList.push(declare));
  422. }
  423. else {
  424. exportNodeList.push(node);
  425. }
  426. }
  427. }
  428. else if (typescript_1.default.isVariableStatement(node)) {
  429. // cache variable statement
  430. for (const declaration of node.declarationList.declarations) {
  431. if (typescript_1.default.isIdentifier(declaration.name) && declaration.initializer) {
  432. cache.set(declaration.name.escapedText, declaration.initializer);
  433. }
  434. }
  435. }
  436. else if ((typescript_1.default.isFunctionDeclaration(node) || typescript_1.default.isClassDeclaration(node)) && node.name) {
  437. // cache function declaration and class declaration
  438. cache.set(node.name.escapedText, node);
  439. }
  440. else if (typescript_1.default.isExportAssignment(node)) {
  441. // export default {}
  442. exportDefaultNode = node.expression;
  443. }
  444. else if (typescript_1.default.isExpressionStatement(node) && typescript_1.default.isBinaryExpression(node.expression)) {
  445. if (typescript_1.default.isPropertyAccessExpression(node.expression.left)) {
  446. const obj = node.expression.left.expression;
  447. const prop = node.expression.left.name;
  448. if (typescript_1.default.isIdentifier(obj)) {
  449. if (obj.escapedText === 'exports') {
  450. // exports.xxx = {}
  451. exportNodeList.push(node.expression);
  452. }
  453. else if (obj.escapedText === 'module' &&
  454. typescript_1.default.isIdentifier(prop) &&
  455. prop.escapedText === 'exports') {
  456. // module.exports = {}
  457. exportDefaultNode = node.expression.right;
  458. }
  459. }
  460. }
  461. else if (typescript_1.default.isIdentifier(node.expression.left)) {
  462. // let exportData;
  463. // exportData = {};
  464. // export exportData
  465. cache.set(node.expression.left.escapedText, node.expression.right);
  466. }
  467. }
  468. });
  469. while (exportDefaultNode && typescript_1.default.isIdentifier(exportDefaultNode) && cache.size) {
  470. const mid = cache.get(exportDefaultNode.escapedText);
  471. cache.delete(exportDefaultNode.escapedText);
  472. exportDefaultNode = mid;
  473. }
  474. return {
  475. exportDefaultNode,
  476. exportNodeList,
  477. };
  478. }
  479. exports.findExportNode = findExportNode;
  480. function isClass(v) {
  481. return typeof v === 'function' && /^\s*class\s+/.test(v.toString());
  482. }
  483. exports.isClass = isClass;
  484. // check kind in node.modifiers.
  485. function modifierHas(node, kind) {
  486. return node.modifiers && node.modifiers.find(mod => kind === mod.kind);
  487. }
  488. exports.modifierHas = modifierHas;
  489. function cleanEmpty(data) {
  490. const clearData = {};
  491. Object.keys(data).forEach(k => {
  492. const dataValue = data[k];
  493. if (dataValue !== undefined && dataValue !== null) {
  494. clearData[k] = data[k];
  495. }
  496. });
  497. return clearData;
  498. }
  499. exports.cleanEmpty = cleanEmpty;