UNPKG

cis-api-tool

Version:

根据 swagger/yapi/apifox 的接口定义生成 TypeScript/JavaScript 的接口类型及其请求函数代码。

541 lines (525 loc) 26.9 kB
const require_chunk = require('./chunk-nOFOJqeH.js'); const require_ApifoxToYApiServer = require('./ApifoxToYApiServer-3m6lu173.js'); const require_function = require('./function-DmHvaqL5.js'); const require_utils = require('./utils-DlLyHAur.js'); const require_SwaggerToYApiServer = require('./SwaggerToYApiServer-CNQH4pBo.js'); const lodash_isEmpty = require_chunk.__toESM(require("lodash/isEmpty")); const fs_extra = require_chunk.__toESM(require("fs-extra")); const path = require_chunk.__toESM(require("path")); const change_case = require_chunk.__toESM(require("change-case")); const child_process = require_chunk.__toESM(require("child_process")); const lodash_castArray = require_chunk.__toESM(require("lodash/castArray")); const lodash_cloneDeep = require_chunk.__toESM(require("lodash/cloneDeep")); const lodash_groupBy = require_chunk.__toESM(require("lodash/groupBy")); const lodash_isFunction = require_chunk.__toESM(require("lodash/isFunction")); const lodash_last = require_chunk.__toESM(require("lodash/last")); const lodash_memoize = require_chunk.__toESM(require("lodash/memoize")); const lodash_noop = require_chunk.__toESM(require("lodash/noop")); const lodash_omit = require_chunk.__toESM(require("lodash/omit")); const lodash_uniq = require_chunk.__toESM(require("lodash/uniq")); const lodash_values = require_chunk.__toESM(require("lodash/values")); //#region src/Generator.ts var Generator = class { /** 配置 */ config = []; disposes = []; constructor(config, options = { cwd: process.cwd() }) { this.options = options; this.config = (0, lodash_castArray.default)(config); } async prepare() { this.config = await Promise.all(this.config.map(async (item) => { if (item.serverType === "swagger") { const swaggerToYApiServer = new require_SwaggerToYApiServer.SwaggerToYApiServer({ swaggerJsonUrl: item.serverUrl }); item.serverUrl = await swaggerToYApiServer.start(); this.disposes.push(() => swaggerToYApiServer.stop()); } if (item.serverType === "apifox") { const firstProject = item.projects[0]; const firstToken = firstProject ? (0, lodash_castArray.default)(firstProject.token)[0] : ""; const apifoxToYApiServer = new require_ApifoxToYApiServer.ApifoxToYApiServer({ serverUrl: item.serverUrl, token: firstToken, projectId: item.apifoxProjectId || "6720131" }); item.serverUrl = await apifoxToYApiServer.start(); this.disposes.push(() => apifoxToYApiServer.stop()); } if (item.serverUrl) item.serverUrl = item.serverUrl.replace(/\/+$/, ""); return item; })); } /** * 清理输出目录,删除之前生成的文件但保留requestFunctionFilePath指定的文件 */ async cleanOutputDirectory() { try { const config = this.config[0]; if (!config) return; const outputDir = typeof config.outputDir === "string" ? config.outputDir : "src/service"; const requestFunctionFilePath = config.requestFunctionFilePath || "src/service/request.ts"; const fullOutputDir = path.default.resolve(this.options.cwd, outputDir); if (!await fs_extra.default.pathExists(fullOutputDir)) return; const fullRequestFilePath = path.default.resolve(this.options.cwd, requestFunctionFilePath); const isRequestFileInOutputDir = fullRequestFilePath.startsWith(fullOutputDir + path.default.sep) || fullRequestFilePath === fullOutputDir; if (!isRequestFileInOutputDir) { await fs_extra.default.emptyDir(fullOutputDir); return; } let requestFileContent = ""; if (await fs_extra.default.pathExists(fullRequestFilePath)) requestFileContent = await fs_extra.default.readFile(fullRequestFilePath, "utf-8"); await fs_extra.default.emptyDir(fullOutputDir); if (requestFileContent) await fs_extra.default.outputFile(fullRequestFilePath, requestFileContent); } catch (error) { console.warn("清理输出目录时出现警告:", error); } } async generate() { const outputFileList = Object.create(null); await Promise.all(this.config.map(async (serverConfig, serverIndex) => { const projects = serverConfig.projects.reduce((projects$1, project) => { projects$1.push(...(0, lodash_castArray.default)(project.token).map((token) => ({ ...project, token }))); return projects$1; }, []); return Promise.all(projects.map(async (projectConfig, projectIndex) => { const projectInfo = await this.fetchProjectInfo({ ...serverConfig, ...projectConfig }); await Promise.all(projectConfig.categories.map(async (categoryConfig, categoryIndex) => { let categoryIds = (0, lodash_castArray.default)(categoryConfig.id); if (categoryIds.includes(0)) categoryIds.push(...projectInfo.cats.map((cat) => cat._id)); categoryIds = (0, lodash_uniq.default)(categoryIds); const excludedCategoryIds = categoryIds.filter((id) => id < 0).map(Math.abs); categoryIds = categoryIds.filter((id) => !excludedCategoryIds.includes(Math.abs(id))); categoryIds = categoryIds.filter((id) => !!projectInfo.cats.find((cat) => cat._id === id)); categoryIds = categoryIds.sort(); const codes = (await Promise.all(categoryIds.map(async (id, categoryIndex2) => { categoryConfig = { ...categoryConfig, id }; const syntheticalConfig = { ...serverConfig, ...projectConfig, ...categoryConfig }; syntheticalConfig.target = syntheticalConfig.target || "typescript"; let interfaceList = await this.fetchInterfaceList(syntheticalConfig); interfaceList = interfaceList.map((interfaceInfo) => { interfaceInfo._project = (0, lodash_omit.default)(projectInfo, "cats", "getMockUrl", "getDevUrl", "getProdUrl"); let _interfaceInfo = (0, lodash_isFunction.default)(syntheticalConfig.preproccessInterface) ? syntheticalConfig.preproccessInterface?.((0, lodash_cloneDeep.default)(interfaceInfo), change_case, syntheticalConfig) : interfaceInfo; if (_interfaceInfo && syntheticalConfig.pathPrefix) { const pathPrefix = syntheticalConfig.pathPrefix; if (_interfaceInfo.path.startsWith(pathPrefix)) { _interfaceInfo.path = _interfaceInfo.path.substring(pathPrefix.length); if (!_interfaceInfo.path.startsWith("/")) _interfaceInfo.path = "/" + _interfaceInfo.path; } } return _interfaceInfo; }).filter(Boolean); interfaceList.sort((a, b) => a._id - b._id); const interfaceCodes = await Promise.all(interfaceList.map(async (interfaceInfo) => { const _filePath = typeof syntheticalConfig.outputDir === "function" ? syntheticalConfig.outputDir(interfaceInfo, change_case) : require_utils.getOutputFilePath(interfaceInfo, change_case, syntheticalConfig.outputDir || "src/service"); const outputFilePath = path.default.resolve(this.options.cwd, _filePath); syntheticalConfig.fileDirectory = _filePath; const categoryUID = `_${serverIndex}_${projectIndex}_${categoryIndex}_${categoryIndex2}`; const code = await this.generateInterfaceCode(syntheticalConfig, interfaceInfo, categoryUID); const weights = [ serverIndex, projectIndex, categoryIndex, categoryIndex2 ]; return { categoryUID, outputFilePath, weights, code, relativeFilePath: _filePath }; })); const groupedInterfaceCodes = (0, lodash_groupBy.default)(interfaceCodes, (item) => item.outputFilePath); return Object.keys(groupedInterfaceCodes).map((outputFilePath) => { const categoryCode = groupedInterfaceCodes[outputFilePath].map((item) => item.code).filter(Boolean).join("\n\n"); if (!outputFileList[outputFilePath]) outputFileList[outputFilePath] = { syntheticalConfig, content: [], requestFunctionFilePath: syntheticalConfig.requestFunctionFilePath ? path.default.isAbsolute(syntheticalConfig.requestFunctionFilePath) ? path.default.resolve(this.options.cwd, syntheticalConfig.requestFunctionFilePath) : path.default.resolve(this.options.cwd, syntheticalConfig.requestFunctionFilePath) : path.default.join(path.default.dirname(outputFilePath), "request.ts"), requestHookMakerFilePath: syntheticalConfig.reactHooks && syntheticalConfig.reactHooks.enabled ? syntheticalConfig.reactHooks.requestHookMakerFilePath ? path.default.isAbsolute(syntheticalConfig.reactHooks.requestHookMakerFilePath) ? path.default.resolve(this.options.cwd, syntheticalConfig.reactHooks.requestHookMakerFilePath) : path.default.resolve(this.options.cwd, syntheticalConfig.reactHooks.requestHookMakerFilePath) : path.default.join(path.default.dirname(outputFilePath), "makeRequestHook.ts") : "" }; return { outputFilePath, code: categoryCode, weights: (0, lodash_last.default)(require_utils.sortByWeights(groupedInterfaceCodes[outputFilePath])).weights }; }); }))).flat(); for (const groupedCodes of (0, lodash_values.default)((0, lodash_groupBy.default)(codes, (item) => item.outputFilePath))) { require_utils.sortByWeights(groupedCodes); outputFileList[groupedCodes[0].outputFilePath].content.push(...groupedCodes.map((item) => item.code)); } })); })); })); return outputFileList; } /** * 生成index.ts文件,将目录中的所有方法和interface类型导出 * @param directoryPaths 目录路径 * @param outputDir 输出目录,默认为 'src/service' */ async generateIndexFile(directoryPaths, outputDir = "src/service") { const indexPath = path.default.resolve(this.options.cwd, outputDir, "index.ts"); if (!await fs_extra.default.pathExists(indexPath)) await fs_extra.default.writeFile(indexPath, ""); const allDirectories = /* @__PURE__ */ new Set(); for (const dir of directoryPaths) { const rootIndexPath = path.default.resolve(this.options.cwd, dir, "index.ts"); if (await fs_extra.default.pathExists(rootIndexPath)) allDirectories.add(dir); await this.collectSubDirectories(dir, allDirectories); } let content = "/* prettier-ignore-start */\n/* tslint:disable */\n/* eslint-disable */\n\n/* 该文件由 cis-api-tool 自动生成,请勿直接修改!!! */\n\n"; const indexContent = require_utils.transformPaths(Array.from(allDirectories), outputDir).join("\n"); content += indexContent; content += "\n/* prettier-ignore-end */"; await fs_extra.default.writeFile(indexPath, content); } async collectSubDirectories(dirPath, allDirectories) { try { const fullPath = path.default.resolve(this.options.cwd, dirPath); if (await fs_extra.default.pathExists(fullPath)) { const items = await fs_extra.default.readdir(fullPath); for (const item of items) { const itemPath = path.default.join(fullPath, item); const stat = await fs_extra.default.stat(itemPath); if (stat.isDirectory()) { const subDirPath = path.default.join(dirPath, item); const indexFilePath = path.default.join(itemPath, "index.ts"); if (await fs_extra.default.pathExists(indexFilePath)) allDirectories.add(subDirPath); await this.collectSubDirectories(subDirPath, allDirectories); } } } } catch (error) { console.warn(`Warning: Failed to collect subdirectories for ${dirPath}:`, error); } } async write(outputFileList) { const result = await Promise.all(Object.keys(outputFileList).map(async (outputFilePath) => { let { content, requestFunctionFilePath, requestHookMakerFilePath, syntheticalConfig } = outputFileList[outputFilePath]; const rawRequestFunctionFilePath = requestFunctionFilePath; const rawRequestHookMakerFilePath = requestHookMakerFilePath; outputFilePath = outputFilePath.replace(/\.js(x)?$/, ".ts$1"); requestFunctionFilePath = requestFunctionFilePath.replace(/\.js(x)?$/, ".ts$1"); requestHookMakerFilePath = requestHookMakerFilePath.replace(/\.js(x)?$/, ".ts$1"); if (!syntheticalConfig.typesOnly) { if (!await fs_extra.default.pathExists(rawRequestFunctionFilePath)) await fs_extra.default.outputFile(requestFunctionFilePath, require_function.dedent` import type { RequestFunctionParams } from 'cis-api-tool' export interface RequestOptions { /** * 使用的服务器。 * * - \`prod\`: 生产服务器 * - \`dev\`: 测试服务器 * - \`mock\`: 模拟服务器 * * @default prod */ server?: 'prod' | 'dev' | 'mock', } export default function request<TResponseData>( payload: RequestFunctionParams, options: RequestOptions = { server: 'prod', }, ): Promise<TResponseData> { return new Promise<TResponseData>((resolve, reject) => { // 基本地址 const baseUrl = options.server === 'mock' ? payload.mockUrl : options.server === 'dev' ? payload.devUrl : payload.prodUrl // 请求地址 const url = \`\${baseUrl}\${payload.path}\` // 具体请求逻辑 }) } `); if (syntheticalConfig.reactHooks && syntheticalConfig.reactHooks.enabled && !await fs_extra.default.pathExists(rawRequestHookMakerFilePath)) await fs_extra.default.outputFile(requestHookMakerFilePath, require_function.dedent` import { useState, useEffect } from 'react' import type { RequestConfig } from 'cis-api-tool' import type { Request } from ${JSON.stringify(require_utils.getNormalizedPathWithAlias(requestHookMakerFilePath, outputFilePath, typeof syntheticalConfig.outputDir === "string" ? syntheticalConfig.outputDir : "src/service"))} import baseRequest from ${JSON.stringify(require_utils.getNormalizedPathWithAlias(requestHookMakerFilePath, requestFunctionFilePath, typeof syntheticalConfig.outputDir === "string" ? syntheticalConfig.outputDir : "src/service"))} export default function makeRequestHook<TRequestData, TRequestConfig extends RequestConfig, TRequestResult extends ReturnType<typeof baseRequest>>(request: Request<TRequestData, TRequestConfig, TRequestResult>) { type Data = TRequestResult extends Promise<infer R> ? R : TRequestResult return function useRequest(requestData: TRequestData) { // 一个简单的 Hook 实现,实际项目可结合其他库使用,比如: // @umijs/hooks 的 useRequest (https://github.com/umijs/hooks) // swr (https://github.com/zeit/swr) const [loading, setLoading] = useState(true) const [data, setData] = useState<Data>() useEffect(() => { request(requestData).then(data => { setLoading(false) setData(data as any) }) }, [JSON.stringify(requestData)]) return { loading, data, } } } `); } const rawOutputContent = require_function.dedent` /* tslint:disable */ /* eslint-disable */ /* 该文件由 cis-api-tool 自动生成,请勿直接修改!!! */ ${syntheticalConfig.typesOnly ? require_function.dedent` // @ts-ignore type FileData = File ${content.join("\n\n").trim()} ` : require_function.dedent` // @ts-ignore import request from ${JSON.stringify(require_utils.getNormalizedPathWithAlias(outputFilePath, requestFunctionFilePath, typeof syntheticalConfig.outputDir === "string" ? syntheticalConfig.outputDir : "src/service"))} ${content.join("\n\n").trim()} `} `; const prettier = await require_utils.getPrettier(this.options.cwd); const prettyOutputContent = await prettier.format(rawOutputContent, { ...await require_utils.getCachedPrettierOptions(), filepath: outputFilePath }); const outputContent = `${require_function.dedent` /* prettier-ignore-start */ ${prettyOutputContent} /* prettier-ignore-end */ `}\n`; await fs_extra.default.outputFile(outputFilePath, outputContent); if (syntheticalConfig.target === "javascript") { await this.tsc(outputFilePath); await Promise.all([ fs_extra.default.remove(requestFunctionFilePath).catch(lodash_noop.default), fs_extra.default.remove(requestHookMakerFilePath).catch(lodash_noop.default), fs_extra.default.remove(outputFilePath).catch(lodash_noop.default) ]); } return outputFilePath; })); const directories = /* @__PURE__ */ new Set(); result.forEach((outputFilePath) => { const dirPath = path.default.dirname(outputFilePath); directories.add(dirPath); }); const rootDirs = Array.from(directories).filter((dir) => { return !Array.from(directories).some((otherDir) => { return dir !== otherDir && dir.startsWith(otherDir + path.default.sep); }); }); let outputDir = "src/service"; if (this.config[0]?.outputDir) if (typeof this.config[0].outputDir === "string") outputDir = this.config[0].outputDir; else outputDir = "src/service"; await this.generateIndexFile(rootDirs, outputDir); return outputFileList; } async tsc(file) { return new Promise((resolve) => { const command = `${require("os").platform() === "win32" ? "node " : ""}${JSON.stringify(require.resolve(`typescript/bin/tsc`))}`; (0, child_process.exec)(`${command} --target ES2019 --module ESNext --jsx preserve --declaration --esModuleInterop ${JSON.stringify(file)}`, { cwd: this.options.cwd, env: process.env }, () => resolve()); }); } async fetchApi(url, query) { const res = await require_utils.httpGet(url, query); /* istanbul ignore next */ if (res && res.errcode) require_utils.throwError(`${res.errmsg} [请求地址: ${url}] [请求参数: ${new URLSearchParams(query).toString()}]`); return res.data || res; } fetchProject = (0, lodash_memoize.default)(async ({ serverUrl, token }) => { const projectInfo = await this.fetchApi(`${serverUrl}/api/project/get`, { token }); const basePath = `/${projectInfo.basepath || "/"}`.replace(/\/+$/, "").replace(/^\/+/, "/"); projectInfo.basepath = basePath; projectInfo._url = `${serverUrl}/project/${projectInfo._id}/interface/api`; return projectInfo; }); fetchExport = (0, lodash_memoize.default)(async ({ serverUrl, token }) => { const projectInfo = await this.fetchProject({ serverUrl, token }); const categoryList = await this.fetchApi(`${serverUrl}/api/plugin/export`, { type: "json", status: "all", isWiki: "false", token }); return categoryList.map((cat) => { const projectId = cat.list?.[0]?.project_id || 0; const catId = cat.list?.[0]?.catid || 0; cat._url = `${serverUrl}/project/${projectId}/interface/api/cat_${catId}`; cat.list = (cat.list || []).map((item) => { const interfaceId = item._id; item._url = `${serverUrl}/project/${projectId}/interface/api/${interfaceId}`; item.path = `${projectInfo.basepath}${item.path}`; return item; }); return cat; }); }); /** 获取分类的接口列表 */ async fetchInterfaceList({ serverUrl, token, id }) { const category = (await this.fetchExport({ serverUrl, token }) || []).find((cat) => !(0, lodash_isEmpty.default)(cat) && !(0, lodash_isEmpty.default)(cat.list) && cat.list[0].catid === id); if (category) category.list.forEach((interfaceInfo) => { interfaceInfo._category = (0, lodash_omit.default)(category, "list"); }); return category ? category.list : []; } /** 获取项目信息 */ async fetchProjectInfo(syntheticalConfig) { const projectInfo = await this.fetchProject(syntheticalConfig); const projectCats = await this.fetchApi(`${syntheticalConfig.serverUrl}/api/interface/getCatMenu`, { token: syntheticalConfig.token, project_id: projectInfo._id }); return { ...projectInfo, cats: projectCats, getMockUrl: () => `${syntheticalConfig.serverUrl}/mock/${projectInfo._id}`, getDevUrl: (devEnvName) => { const env = projectInfo.env.find((e) => e.name === devEnvName); return env && env.domain || ""; }, getProdUrl: (prodEnvName) => { const env = projectInfo.env.find((e) => e.name === prodEnvName); return env && env.domain || ""; } }; } /** 生成接口代码 */ async generateInterfaceCode(syntheticalConfig, interfaceInfo, categoryUID) { const extendedInterfaceInfo = { ...interfaceInfo, parsedPath: path.default.parse(interfaceInfo.path) }; const requestFunctionName = (0, lodash_isFunction.default)(syntheticalConfig.getRequestFunctionName) ? await syntheticalConfig.getRequestFunctionName?.(extendedInterfaceInfo, change_case) : require_utils.getRequestFunctionName(extendedInterfaceInfo, change_case); const requestDataTypeName = (0, lodash_isFunction.default)(syntheticalConfig.getRequestDataTypeName) ? await syntheticalConfig.getRequestDataTypeName?.(extendedInterfaceInfo, change_case) : require_utils.getRequestDataTypeName(extendedInterfaceInfo, change_case); const responseDataTypeName = (0, lodash_isFunction.default)(syntheticalConfig.getResponseDataTypeName) ? await syntheticalConfig.getResponseDataTypeName?.(extendedInterfaceInfo, change_case) : require_utils.getReponseDataTypeName(extendedInterfaceInfo, change_case); const requestDataJsonSchema = require_utils.getRequestDataJsonSchema(extendedInterfaceInfo, syntheticalConfig.customTypeMapping || {}); const requestDataType = await require_utils.jsonSchemaToType(requestDataJsonSchema, requestDataTypeName); const responseDataJsonSchema = require_utils.getResponseDataJsonSchema(extendedInterfaceInfo, syntheticalConfig.customTypeMapping || {}, syntheticalConfig.dataKey); const responseDataType = await require_utils.jsonSchemaToType(responseDataJsonSchema, responseDataTypeName); /(\{\}|any)$/g.test(requestDataType); syntheticalConfig.reactHooks && syntheticalConfig.reactHooks.enabled ? (0, lodash_isFunction.default)(syntheticalConfig.reactHooks.getRequestHookName) ? await syntheticalConfig.reactHooks.getRequestHookName?.(extendedInterfaceInfo, change_case) : `use${change_case.pascalCase(requestFunctionName)}` : ""; const processPathParams = (path$2) => { const hasPathParams = /\{[^}]+\}/.test(path$2); if (!hasPathParams) return { processedPath: path$2, useTemplate: false, pathParamNames: [], originalPathParamNames: [] }; const pathParamNames$1 = []; const originalPathParamNames$1 = []; const processedPath$1 = path$2.replace(/\{([^}]+)\}/g, (match, paramName) => { originalPathParamNames$1.push(paramName); const validParamName = /^\d+$/.test(paramName) ? `param_${paramName}` : paramName; pathParamNames$1.push(validParamName); return "${params." + validParamName + "}"; }); return { processedPath: processedPath$1, useTemplate: true, pathParamNames: pathParamNames$1, originalPathParamNames: originalPathParamNames$1 }; }; const genComment = (genTitle) => { const { enabled: isEnabled = true, title: hasTitle = true, category: hasCategory = true, tag: hasTag = true, requestHeader: hasRequestHeader = true, updateTime: hasUpdateTime = true, link: hasLink = true, extraTags } = { ...syntheticalConfig.comment, ...syntheticalConfig.serverType === "swagger" ? { tag: false, updateTime: false, link: false } : {} }; if (!isEnabled) return ""; const escapedTitle = String(extendedInterfaceInfo.title).replace(/\//g, "\\/"); const description = escapedTitle; const summary = [ hasCategory && { label: "category", value: extendedInterfaceInfo._category.name }, hasTag && { label: "tags", value: extendedInterfaceInfo.tag.map((tag) => `${tag}`) }, hasRequestHeader && { label: "method", value: `${extendedInterfaceInfo.method.toUpperCase()}` }, hasRequestHeader && { label: "path", value: `${extendedInterfaceInfo.path}` } ]; if (typeof extraTags === "function") { const tags = extraTags(extendedInterfaceInfo); for (const tag of tags) (tag.position === "start" ? summary.unshift : summary.push).call(summary, { label: tag.name, value: tag.value }); } const titleComment = hasTitle ? require_function.dedent` * ${genTitle(description)} * ` : ""; const extraComment = summary.filter((item) => typeof item !== "boolean" && !(0, lodash_isEmpty.default)(item.value)).map((item) => { const _item = item; return `* @${_item.label} ${(0, lodash_castArray.default)(_item.value).join(", ")}`; }).join("\n"); return require_function.dedent` /** ${[titleComment, extraComment].filter(Boolean).join("\n")} */ `; }; const { processedPath, useTemplate, pathParamNames, originalPathParamNames } = processPathParams(extendedInterfaceInfo.path); return require_function.dedent` ${genComment((title) => `@description 接口 ${title} 的 **请求类型**`)} ${requestDataType.trim()} ${genComment((title) => `@description 接口 ${title} 的 **返回类型**`)} ${responseDataType.trim()} ${syntheticalConfig.typesOnly ? "" : require_function.dedent` ${genComment((title) => `@description 接口 ${title} 的 **请求函数**`)} export const ${requestFunctionName || "ErrorRequestFunctionName"} = ( params: ${requestDataTypeName} ) => { return request.${extendedInterfaceInfo.method.toLowerCase()}<${responseDataTypeName}>( ${useTemplate ? `\`${processedPath}\`` : JSON.stringify(processedPath)}${originalPathParamNames.length > 0 ? `` : `, params`} ) } `} `; } async destroy() { return Promise.all(this.disposes.map(async (dispose) => dispose())); } }; //#endregion Object.defineProperty(exports, 'Generator', { enumerable: true, get: function () { return Generator; } });