"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); 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; const tslib_1 = require("tslib"); const fs_1 = tslib_1.__importDefault(require("fs")); const mkdirp_1 = tslib_1.__importDefault(require("mkdirp")); const globby_1 = tslib_1.__importDefault(require("globby")); const path_1 = tslib_1.__importDefault(require("path")); const typescript_1 = tslib_1.__importDefault(require("typescript")); const yn_1 = tslib_1.__importDefault(require("yn")); const config_1 = require("./config"); const child_process_1 = require("child_process"); const json5_1 = tslib_1.__importDefault(require("json5")); exports.JS_CONFIG = { include: ['**/*'], }; exports.TS_CONFIG = { compilerOptions: { target: typescript_1.default.ScriptTarget.ES2017, module: typescript_1.default.ModuleKind.CommonJS, strict: true, noImplicitAny: false, experimentalDecorators: true, emitDecoratorMetadata: true, allowSyntheticDefaultImports: true, charset: 'utf8', allowJs: false, pretty: true, lib: ['es6'], noEmitOnError: false, noUnusedLocals: true, noUnusedParameters: true, allowUnreachableCode: false, allowUnusedLabels: false, strictPropertyInitialization: false, noFallthroughCasesInSwitch: true, skipLibCheck: true, skipDefaultLibCheck: true, inlineSourceMap: true, }, }; const cacheEggInfo = {}; function getEggInfo(option) { const { cacheIndex, cwd, customLoader } = option; const cacheKey = cacheIndex ? `${cacheIndex}${cwd}` : undefined; const caches = cacheKey ? (cacheEggInfo[cacheKey] = cacheEggInfo[cacheKey] || {}) : undefined; const end = (json) => { if (caches) { caches.eggInfo = json; caches.cacheTime = Date.now(); } if (option.callback) { return option.callback(json); } return json; }; // check cache if (caches) { if (caches.cacheTime && (Date.now() - caches.cacheTime) < 1000) { return end(caches.eggInfo); } else if (caches.runningPromise) { return caches.runningPromise; } } // get egg info from customLoader if (customLoader) { return end({ config: customLoader.config, plugins: customLoader.plugins, eggPaths: customLoader.eggPaths, }); } // prepare options const cmd = 'node'; const args = [path_1.default.resolve(__dirname, './scripts/eggInfo')]; const opt = { cwd, 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), }; if (option.async) { // cache promise caches.runningPromise = new Promise((resolve, reject) => { (0, child_process_1.execFile)(cmd, args, opt, err => { caches.runningPromise = null; if (err) reject(err); resolve(end(parseJson(fs_1.default.readFileSync(config_1.eggInfoPath, 'utf-8')))); }); }); return caches.runningPromise; } try { (0, child_process_1.execFileSync)(cmd, args, opt); return end(parseJson(fs_1.default.readFileSync(config_1.eggInfoPath, 'utf-8'))); } catch (e) { return end({}); } } exports.getEggInfo = getEggInfo; // convert string to same type with default value function convertString(val, defaultVal) { if (val === undefined) return defaultVal; switch (typeof defaultVal) { case 'boolean': return (0, yn_1.default)(val, { default: defaultVal }); case 'number': const num = +val; return (isNaN(num) ? defaultVal : num); case 'string': return val; default: return defaultVal; } } exports.convertString = convertString; function isIdentifierName(s) { return /^[$A-Z_][0-9A-Z_$]*$/i.test(s); } exports.isIdentifierName = isIdentifierName; // load ts/js files function loadFiles(cwd, pattern) { pattern = pattern || '**/*.(js|ts)'; pattern = Array.isArray(pattern) ? pattern : [pattern]; const fileList = globby_1.default.sync(pattern.concat(['!**/*.d.ts']), { cwd }); return fileList.filter(f => { // filter same name js/ts return !(f.endsWith('.js') && fileList.includes(f.substring(0, f.length - 2) + 'ts')); }); } exports.loadFiles = loadFiles; // write jsconfig.json to cwd function writeJsConfig(cwd) { const jsconfigUrl = path_1.default.resolve(cwd, './jsconfig.json'); if (!fs_1.default.existsSync(jsconfigUrl)) { fs_1.default.writeFileSync(jsconfigUrl, JSON.stringify(exports.JS_CONFIG, null, 2)); return jsconfigUrl; } } exports.writeJsConfig = writeJsConfig; // write tsconfig.json to cwd function writeTsConfig(cwd) { const tsconfigUrl = path_1.default.resolve(cwd, './tsconfig.json'); if (!fs_1.default.existsSync(tsconfigUrl)) { fs_1.default.writeFileSync(tsconfigUrl, JSON.stringify(exports.TS_CONFIG, null, 2)); return tsconfigUrl; } } exports.writeTsConfig = writeTsConfig; function checkMaybeIsTsProj(cwd, pkgInfo) { pkgInfo = pkgInfo || getPkgInfo(cwd); return (pkgInfo.egg && pkgInfo.egg.typescript) || fs_1.default.existsSync(path_1.default.resolve(cwd, './tsconfig.json')) || fs_1.default.existsSync(path_1.default.resolve(cwd, './config/config.default.ts')) || fs_1.default.existsSync(path_1.default.resolve(cwd, './config/config.ts')); } exports.checkMaybeIsTsProj = checkMaybeIsTsProj; function checkMaybeIsJsProj(cwd, pkgInfo) { pkgInfo = pkgInfo || getPkgInfo(cwd); const isJs = !checkMaybeIsTsProj(cwd, pkgInfo) && (fs_1.default.existsSync(path_1.default.resolve(cwd, './config/config.default.js')) || fs_1.default.existsSync(path_1.default.resolve(cwd, './jsconfig.json'))); return isJs; } exports.checkMaybeIsJsProj = checkMaybeIsJsProj; // load modules to object function loadModules(cwd, loadDefault, preHandle) { const modules = {}; preHandle = preHandle || (fn => fn); fs_1.default .readdirSync(cwd) .filter(f => f.endsWith('.js')) .forEach(f => { const name = f.substring(0, f.lastIndexOf('.')); const obj = require(path_1.default.resolve(cwd, name)); if (loadDefault && obj.default) { modules[name] = preHandle(obj.default); } else { modules[name] = preHandle(obj); } }); return modules; } exports.loadModules = loadModules; // convert string to function function strToFn(fn) { if (typeof fn === 'string') { return (...args) => fn.replace(/{{\s*(\d+)\s*}}/g, (_, index) => args[index]); } return fn; } exports.strToFn = strToFn; // pick fields from object function pickFields(obj, fields) { const newObj = {}; fields.forEach(f => (newObj[f] = obj[f])); return newObj; } exports.pickFields = pickFields; // log function log(msg, prefix = true) { console.info(`${prefix ? '[egg-ts-helper] ' : ''}${msg}`); } exports.log = log; // get import context function getImportStr(from, to, moduleName, importStar) { const extname = path_1.default.extname(to); const toPathWithoutExt = to.substring(0, to.length - extname.length); const importPath = path_1.default.relative(from, toPathWithoutExt).replace(/\/|\\/g, '/'); const isTS = extname === '.ts' || fs_1.default.existsSync(`${toPathWithoutExt}.d.ts`); const importStartStr = isTS && importStar ? '* as ' : ''; const fromStr = isTS ? `from '${importPath}'` : `= require('${importPath}')`; return `import ${importStartStr}${moduleName} ${fromStr};`; } exports.getImportStr = getImportStr; // write file, using fs.writeFileSync to block io that d.ts can create before egg app started. function writeFileSync(fileUrl, content) { mkdirp_1.default.sync(path_1.default.dirname(fileUrl)); fs_1.default.writeFileSync(fileUrl, content); } exports.writeFileSync = writeFileSync; // clean same name js/ts function cleanJs(cwd) { const fileList = []; globby_1.default .sync(['**/*.ts', '**/*.tsx', '!**/*.d.ts', '!**/node_modules'], { cwd }) .forEach(f => { const jf = removeSameNameJs(path_1.default.resolve(cwd, f)); if (jf) fileList.push(path_1.default.relative(cwd, jf)); }); if (fileList.length) { console.info('[egg-ts-helper] These file was deleted because the same name ts file was exist!\n'); console.info(' ' + fileList.join('\n ') + '\n'); } } exports.cleanJs = cleanJs; // get moduleName by file path function getModuleObjByPath(f) { const props = f.substring(0, f.lastIndexOf('.')).split('/'); // composing moduleName const moduleName = props.map(prop => camelProp(prop, 'upper')).join(''); return { props, moduleName, }; } exports.getModuleObjByPath = getModuleObjByPath; // format path sep to / function formatPath(str) { return str.replace(/\/|\\/g, '/'); } exports.formatPath = formatPath; function toArray(pattern) { return pattern ? (Array.isArray(pattern) ? pattern : [pattern]) : []; } exports.toArray = toArray; // remove same name js function removeSameNameJs(f) { if (!f.match(/\.tsx?$/) || f.endsWith('.d.ts')) { return; } const jf = f.replace(/tsx?$/, 'js'); if (fs_1.default.existsSync(jf)) { fs_1.default.unlinkSync(jf); return jf; } } exports.removeSameNameJs = removeSameNameJs; // resolve module function resolveModule(url) { try { return require.resolve(url); } catch (e) { return undefined; } } exports.resolveModule = resolveModule; // check whether module is exist function moduleExist(mod, cwd) { const nodeModulePath = path_1.default.resolve(cwd || process.cwd(), 'node_modules', mod); return fs_1.default.existsSync(nodeModulePath) || resolveModule(mod); } exports.moduleExist = moduleExist; // require modules function requireFile(url) { url = url && resolveModule(url); if (!url) { return undefined; } let exp = require(url); if (exp.__esModule && 'default' in exp) { exp = exp.default; } return exp; } exports.requireFile = requireFile; // extend function extend(obj, ...args) { args.forEach(source => { let descriptor, prop; if (source) { for (prop in source) { descriptor = Object.getOwnPropertyDescriptor(source, prop); Object.defineProperty(obj, prop, descriptor); } } }); return obj; } exports.extend = extend; // parse json function parseJson(jsonStr) { if (jsonStr) { try { return JSON.parse(jsonStr); } catch (e) { return {}; } } else { return {}; } } exports.parseJson = parseJson; // load package.json function getPkgInfo(cwd) { return readJson(path_1.default.resolve(cwd, './package.json')); } exports.getPkgInfo = getPkgInfo; // read json file function readJson(jsonUrl) { if (!fs_1.default.existsSync(jsonUrl)) return {}; return parseJson(fs_1.default.readFileSync(jsonUrl, 'utf-8')); } exports.readJson = readJson; function readJson5(jsonUrl) { if (!fs_1.default.existsSync(jsonUrl)) return {}; return json5_1.default.parse(fs_1.default.readFileSync(jsonUrl, 'utf-8')); } exports.readJson5 = readJson5; // format property function formatProp(prop) { return prop.replace(/[._-][a-z]/gi, s => s.substring(1).toUpperCase()); } exports.formatProp = formatProp; // like egg-core/file-loader function camelProp(property, caseStyle) { if (typeof caseStyle === 'function') { return caseStyle(property); } // camel transfer property = formatProp(property); let first = property[0]; // istanbul ignore next switch (caseStyle) { case 'lower': first = first.toLowerCase(); break; case 'upper': first = first.toUpperCase(); break; case 'camel': break; default: break; } return first + property.substring(1); } exports.camelProp = camelProp; // load tsconfig.json function loadTsConfig(tsconfigPath) { tsconfigPath = path_1.default.extname(tsconfigPath) === '.json' ? tsconfigPath : `${tsconfigPath}.json`; const tsConfig = readJson5(tsconfigPath); if (tsConfig.extends) { const extendPattern = tsConfig.extends; const tsconfigDirName = path_1.default.dirname(tsconfigPath); const maybeRealExtendPath = [ path_1.default.resolve(tsconfigDirName, extendPattern), path_1.default.resolve(tsconfigDirName, `${extendPattern}.json`), ]; if (!path_1.default.extname(tsConfig.extends) && !extendPattern.startsWith('.') && !extendPattern.startsWith('/')) { maybeRealExtendPath.push(path_1.default.resolve(tsconfigDirName, 'node_modules', extendPattern, 'tsconfig.json'), path_1.default.resolve(process.cwd(), 'node_modules', extendPattern, 'tsconfig.json')); } const extendRealPath = maybeRealExtendPath.find(f => fs_1.default.existsSync(f)); if (extendRealPath) { const extendTsConfig = loadTsConfig(extendRealPath); return Object.assign(Object.assign({}, tsConfig.compilerOptions), extendTsConfig); } } return tsConfig.compilerOptions || {}; } exports.loadTsConfig = loadTsConfig; /** * ts ast utils */ // find export node from sourcefile. function findExportNode(code) { const sourceFile = typescript_1.default.createSourceFile('file.ts', code, typescript_1.default.ScriptTarget.ES2017, true); const cache = new Map(); const exportNodeList = []; let exportDefaultNode; sourceFile.statements.forEach(node => { // each node in root scope if (modifierHas(node, typescript_1.default.SyntaxKind.ExportKeyword)) { if (modifierHas(node, typescript_1.default.SyntaxKind.DefaultKeyword)) { // export default exportDefaultNode = node; } else { // export variable if (typescript_1.default.isVariableStatement(node)) { node.declarationList.declarations.forEach(declare => exportNodeList.push(declare)); } else { exportNodeList.push(node); } } } else if (typescript_1.default.isVariableStatement(node)) { // cache variable statement for (const declaration of node.declarationList.declarations) { if (typescript_1.default.isIdentifier(declaration.name) && declaration.initializer) { cache.set(declaration.name.escapedText, declaration.initializer); } } } else if ((typescript_1.default.isFunctionDeclaration(node) || typescript_1.default.isClassDeclaration(node)) && node.name) { // cache function declaration and class declaration cache.set(node.name.escapedText, node); } else if (typescript_1.default.isExportAssignment(node)) { // export default {} exportDefaultNode = node.expression; } else if (typescript_1.default.isExpressionStatement(node) && typescript_1.default.isBinaryExpression(node.expression)) { if (typescript_1.default.isPropertyAccessExpression(node.expression.left)) { const obj = node.expression.left.expression; const prop = node.expression.left.name; if (typescript_1.default.isIdentifier(obj)) { if (obj.escapedText === 'exports') { // exports.xxx = {} exportNodeList.push(node.expression); } else if (obj.escapedText === 'module' && typescript_1.default.isIdentifier(prop) && prop.escapedText === 'exports') { // module.exports = {} exportDefaultNode = node.expression.right; } } } else if (typescript_1.default.isIdentifier(node.expression.left)) { // let exportData; // exportData = {}; // export exportData cache.set(node.expression.left.escapedText, node.expression.right); } } }); while (exportDefaultNode && typescript_1.default.isIdentifier(exportDefaultNode) && cache.size) { const mid = cache.get(exportDefaultNode.escapedText); cache.delete(exportDefaultNode.escapedText); exportDefaultNode = mid; } return { exportDefaultNode, exportNodeList, }; } exports.findExportNode = findExportNode; function isClass(v) { return typeof v === 'function' && /^\s*class\s+/.test(v.toString()); } exports.isClass = isClass; // check kind in node.modifiers. function modifierHas(node, kind) { return node.modifiers && node.modifiers.find(mod => kind === mod.kind); } exports.modifierHas = modifierHas; function cleanEmpty(data) { const clearData = {}; Object.keys(data).forEach(k => { const dataValue = data[k]; if (dataValue !== undefined && dataValue !== null) { clearData[k] = data[k]; } }); return clearData; } exports.cleanEmpty = cleanEmpty;