[
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n  extends: [require.resolve('@umijs/fabric/dist/eslint')],\n};\n"
  },
  {
    "path": ".gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n**/node_modules\n# roadhog-api-doc ignore\n/src/utils/request-temp.js\n_roadhog-api-doc\n\n# production\n/dist\n/.vscode\n\n# misc\n.DS_Store\nnpm-debug.log*\nyarn-error.log\n\n/coverage\n.idea\nyarn.lock\npackage-lock.json\n*bak\nlib\n.vscode\n\n# visual studio code\n.history\n*.log\nfunctions/*\n.temp/**\n\n# umi\n.umi\n.umi-production\n\n# screenshot\n.firebase"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"printWidth\": 100,\n  \"singleQuote\": true,\n  \"trailingComma\": \"all\",\n  \"proseWrap\": \"never\",\n  \"overrides\": [\n    {\n      \"files\": \".webpackrc\",\n      \"options\": {\n        \"parser\": \"json\"\n      }\n    },\n    {\n      \"files\": \".prettierrc\",\n      \"options\": {\n        \"parser\": \"json\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2017-2018 Alipay\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE."
  },
  {
    "path": "README.md",
    "content": "# Cli for Ant Design Pro\n\n[![NPM version][npm-image]][npm-url] [![NPM downloads][download-image]][download-url]\n\n[npm-image]: https://img.shields.io/npm/v/@ant-design/pro-cli.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@ant-design/pro-cli\n[download-image]: https://img.shields.io/npm/dm/@ant-design/pro-cli.svg?style=flat-square\n[download-url]: https://npmjs.org/package/@ant-design/pro-cli\n\n## Install\n\n```shell\nnpm i @ant-design/pro-cli\n# or\nyarn add @ant-design/pro-cli\n```\n\n## Commands\n\n- screenshot 对区块进行截图\n- i18n-remove 从项目中移除国际化\n- fetch-blocks 下载 pro 所有的官方区块\n- pro-components-codemod 自动更新 @ant-design/pro-components 的 import 方式\n\n## Options for the screenshot command\n\n- --path 区块的路径，可以用于只截图一个\n- --mobile 使用手机大小的屏幕进行截图\n\n## Options for the i18n-remove command\n\n- --locale 设置语言\n- --write 是否写入文件\n\n## Options for the pro-components-codemod command\n\n- --writePkg 是否自动更新 package.json 中的 dependencies 的添加/删除，默认开启\n- --cleanup 是否开启 cleanup 模式，默认开启\n- --path 需要更新文件的目录，默认 src 目录下的文件\n\n## debug\n\n### bash\n\n```bash\nDEBUG=pro-cli pro XXX\n```\n\n### PowerShell\n\n```powershell\n$env:DEBUG=\"pro-cli\"\npro xxx\n```\n\n### CMD\n\n```cmd\nset DEBUG=pro-cli\npro xxx\n```\n\n## Examples\n\n### pro\n\npro -h\n\n### screenshot\n\n- pro screenshot 对所有区块进行截图\n- pro screenshot --path DashboardWorkplace 对单个区块进行截图\n- pro screenshot --mobile 对所有区块进行截图\n- pro screenshot --dumi 使用 dumi 构建的资产，支持手机模式\n\n### i18n-remove\n\n- pro i18n-remove --write\n\n- pro i18n-remove --locale en-US --write\n\n### fetch-blocks\n\n- pro fetch-blocks\n\n### pro-components-codemod\n\n- pro pro-components-codemod  自动更新 package.json 中的 dependencies 的添加/删除，并将导入模块全部合并到一条 import 语句导入\n- pro pro-components-codemod --writePkg 0 不处理 package.json 中的 dependencies 的添加/删除，只进行 import 导入方式的更新\n- pro pro-components-codemod --path packages 处理 packages 目录下的文件\n- pro pro-components-codemod --cleanup 0 只更新子包的名称为 @ant-design/pro-components，保留旧项目的 import 方式\n"
  },
  {
    "path": "cli.js",
    "content": "#!/usr/bin/env node\n\nconst yParser = require('yargs-parser');\nconst semver = require('semver');\nconst { existsSync } = require('fs');\nconst { join } = require('path');\nconst chalk = require('chalk');\n\n// print version and @local\nconst args = yParser(process.argv.slice(2));\n\nif (args.v || args.version) {\n  // eslint-disable-next-line global-require\n  console.log(require('./package').version);\n  if (existsSync(join(__dirname, '.local'))) {\n    console.log(chalk.cyan('@local'));\n  }\n  process.exit(0);\n}\n\nif (!semver.satisfies(process.version, '>= 8.0.0')) {\n  console.error(chalk.red('✘ The generator will only work with Node v8.0.0 and up!'));\n  process.exit(1);\n}\nconst cwd = process.cwd();\n\nconst option = args._[0];\n\nconst screenshot = async (props) => {\n  // eslint-disable-next-line global-require\n  await require('./src/screenshot/index')(props);\n  process.exit(0);\n};\n\nconst screenshotDumi = async (props) => {\n  // eslint-disable-next-line global-require\n  await require('./src/screenshot/dumi')(props);\n  process.exit(0);\n};\n\nswitch (option) {\n  case 'screenshot':\n    if (args.dumi) {\n      screenshotDumi({ cwd, ...args });\n    } else {\n      screenshot({ cwd, ...args });\n    }\n    break;\n  case 'i18n-remove':\n    // eslint-disable-next-line global-require\n    require('./src/i18n/index')({ cwd, ...args });\n    break;\n  case 'fetch-blocks':\n    // eslint-disable-next-line global-require\n    require('./src/fetch-blocks/index')({ cwd, ...args });\n    break;\n  case 'create':\n    const name = args._[1] || '';\n    // eslint-disable-next-line global-require\n    require('./src/create/index')({ cwd, name, ...args });\n    break;\n  case 'pro-components-codemod':\n    // eslint-disable-next-line global-require\n    require('./src/pro-components-codemod/index')({ cwd, ...args });\n    break;\n  default:\n    if (args.h || args.help) {\n      const details = `\n      Commands:\n        ${chalk.cyan('create')}          创建新的 Ant Design Pro 项目\n        ${chalk.cyan('screenshot ')}     对区块进行截图\n        ${chalk.cyan('i18n-remove')}     从项目中移除国际化\n        ${chalk.cyan('fetch-blocks')}    下载 pro 所有的官方区块\n        ${chalk.cyan('pro-components-codemod')}    自动更新 pro-components 的 import 方式\n\n      Options for the ${chalk.cyan('create')} command:\n        ${chalk.green('--list-templates    ')} 列出可用的模板\n        ${chalk.green('--template          ')} 模板类型（非交互式模式）\n        ${chalk.green('--origin            ')} 指定源: github 或 gitee\n\n      Options for the ${chalk.cyan('screenshot')} command:\n        ${chalk.green('--path              ')} 区块的路径，可以用于只截图一个\n        ${chalk.green('--mobile            ')} 使用手机大小的屏幕进行截图\n\n      Options for the ${chalk.cyan('i18n-remove')} command:\n        ${chalk.green('--locale            ')} 设置语言\n        ${chalk.green('--write             ')} 是否写入文件\n\n      Options for the ${chalk.cyan('pro-components-codemod')} command:\n        ${chalk.green('--writePkg          ')} 是否更新 package.json 中的 dependencies\n        ${chalk.green('--path              ')} 更新 pro-components import 的路径\n        ${chalk.green('--cleanup           ')} 是否开启 cleanup 模式，多个 import 合并为 单个 import\n        ${chalk.green('--list-versions     ')} 列出可用的 pro-components 版本\n        ${chalk.green('--version           ')} 指定 pro-components 版本（非交互式模式，可用 latest）\n\n      Examples:\n        ${chalk.gray('pro')}\n        pro -h\n\n        ${chalk.gray('create (list available templates)')}\n        pro create --list-templates\n\n        ${chalk.gray('create (interactive)')}\n        pro create myapp\n\n        ${chalk.gray('create (non-interactive)')}\n        pro create myapp --template simple\n        pro create myapp --template complete\n\n        ${chalk.gray('screenshot')}\n        pro screenshot\n        pro screenshot --path DashboardWorkplace\n\n        ${chalk.gray('i18n-remove')}\n        pro i18n-remove --write\n        pro i18n-remove --locale en-US --write\n\n        ${chalk.gray('fetch-blocks')}\n        pro fetch-blocks\n\n        ${chalk.gray('pro-components-codemod (interactive)')}\n        pro pro-components-codemod --writePkg\n\n        ${chalk.gray('pro-components-codemod (list available versions)')}\n        pro pro-components-codemod --list-versions\n\n        ${chalk.gray('pro-components-codemod (non-interactive)')}\n        pro pro-components-codemod --version latest\n        pro pro-components-codemod --version 2.6.0 --path src --cleanup\n\n        `.trim();\n      console.log(details);\n    }\n    break;\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"@ant-design/pro-cli\",\n  \"version\": \"3.4.0\",\n  \"description\": \"The command tool for ant design pro\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/ant-design/ant-design-pro-cli\"\n  },\n  \"bin\": {\n    \"pro\": \"./cli.js\"\n  },\n  \"files\": [\n    \"src\",\n    \"cli.js\"\n  ],\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"dependencies\": {\n    \"@babel/core\": \"^7.23.7\",\n    \"@babel/generator\": \"^7.23.6\",\n    \"@babel/parser\": \"^7.23.6\",\n    \"@babel/plugin-proposal-class-properties\": \"^7.18.6\",\n    \"@babel/plugin-proposal-decorators\": \"^7.23.7\",\n    \"@babel/plugin-transform-typescript\": \"^7.23.6\",\n    \"@babel/preset-env\": \"^7.23.8\",\n    \"@babel/preset-react\": \"^7.23.3\",\n    \"@babel/preset-typescript\": \"^7.23.3\",\n    \"@babel/traverse\": \"^7.23.7\",\n    \"@babel/types\": \"^7.23.6\",\n    \"@umijs/fabric\": \"^2.14.1\",\n    \"babel-types\": \"^6.26.0\",\n    \"blink-diff\": \"^1.0.13\",\n    \"carlo\": \"^0.9.46\",\n    \"chalk\": \"^4.1.2\",\n    \"cross-port-killer\": \"^1.4.0\",\n    \"execa\": \"^5.1.1\",\n    \"fs-extra\": \"^10.1.0\",\n    \"glob\": \"^7.2.3\",\n    \"import-fresh\": \"^3.3.0\",\n    \"inquirer\": \"^8.2.6\",\n    \"intl-messageformat\": \"^9.13.0\",\n    \"lodash.groupby\": \"^4.6.0\",\n    \"node-fetch\": \"^2.7.0\",\n    \"node-import-ts\": \"^1.0.6\",\n    \"ora\": \"^5.4.1\",\n    \"pngjs-image\": \"^0.11.7\",\n    \"prettier\": \"^2.8.8\",\n    \"recast\": \"^0.21.5\",\n    \"rimraf\": \"^3.0.2\",\n    \"semver\": \"^7.5.4\",\n    \"typescript\": \"^4.9.5\",\n    \"umi-utils\": \"^1.7.3\",\n    \"yargs-parser\": \"^20.2.9\",\n    \"yeoman-environment\": \"^3.19.3\",\n    \"yeoman-generator\": \"^5.10.0\"\n  },\n  \"devDependencies\": {\n    \"eslint\": \"^8.56.0\"\n  }\n}\n"
  },
  {
    "path": "src/create/BasicGenerator.js",
    "content": "const Generator = require('yeoman-generator');\nconst glob = require('glob');\nconst { statSync } = require('fs');\nconst { basename } = require('path');\nconst debug = require('debug')('create-umi:BasicGenerator');\n\nfunction noop() {\n  return true;\n}\n\nclass BasicGenerator extends Generator {\n  constructor(opts) {\n    super(opts);\n    this.opts = opts;\n    this.name = basename(opts.env.cwd);\n  }\n\n  isTsFile(f) {\n    return f.endsWith('.ts') || f.endsWith('.tsx') || !!/(tsconfig\\.json)/g.test(f);\n  }\n\n  writeFiles({ context, filterFiles = noop }) {\n    // We have multiple welcome file, random this\n    const welcomeImages = glob\n      .sync('**/assets/welcomeImgs/*', {\n        cwd: this.templatePath(),\n        dot: true,\n      })\n      .filter(filterFiles);\n\n    if (welcomeImages.length) {\n      const welcomeImg = welcomeImages[Math.floor(Math.random() * welcomeImages.length)];\n      debug(`copy ${welcomeImg}`);\n\n      this.fs.copyTpl(\n        this.templatePath(welcomeImg),\n        this.destinationPath(welcomeImg.replace(/welcomeImgs.*$/, 'yay.jpg')),\n        context,\n      );\n    }\n\n    debug(`context: ${JSON.stringify(context)}`);\n    glob\n      .sync('**/*', {\n        cwd: this.templatePath(),\n        dot: true,\n      })\n      .filter(filterFiles)\n      .filter((file) => !file.includes('welcomeImgs'))\n      .forEach((file) => {\n        debug(`copy ${file}`);\n        const filePath = this.templatePath(file);\n        if (statSync(filePath).isFile()) {\n          this.fs.copyTpl(\n            this.templatePath(filePath),\n            this.destinationPath(file.replace(/^_/, '.')),\n            context,\n          );\n        }\n      });\n  }\n\n  prompt(questions) {\n    process.send && process.send({ type: 'prompt' });\n    process.emit('message', { type: 'prompt' });\n    return super.prompt(questions);\n  }\n}\n\nmodule.exports = BasicGenerator;\n"
  },
  {
    "path": "src/create/generators/ant-design-pro/.babelrc",
    "content": "{\n  \"plugins\": [\n    [\n      \"@babel/plugin-transform-typescript\",\n      {\n        \"isTSX\": true\n      }\n    ]\n  ]\n}\n"
  },
  {
    "path": "src/create/generators/ant-design-pro/README.md",
    "content": "# Ant Design Pro\n\nThis project is initialized with [Ant Design Pro](https://pro.ant.design). Follow is the quick guide for how to use.\n\n## Environment Prepare\n\nInstall `node_modules`:\n\n```bash\nnpm install\n```\n\nor\n\n```bash\nyarn\n```\n\n## Provided Scripts\n\nAnt Design Pro provides some useful script to help you quick start and build with web project, code style check and test.\n\nScripts provided in `package.json`. It's safe to modify or add additional script:\n\n### Start project\n\n```bash\nnpm start\n```\n\n### Build project\n\n```bash\nnpm run build\n```\n\n### Check code style\n\n```bash\nnpm run lint\n```\n\nYou can also use script to auto fix some lint error:\n\n```bash\nnpm run lint:fix\n```\n\n### Test code\n\n```bash\nnpm test\n```\n\n## More\n\nYou can view full document on our [official website](https://pro.ant.design). And welcome any feedback in our [github](https://github.com/ant-design/ant-design-pro).\n"
  },
  {
    "path": "src/create/generators/ant-design-pro/filterPkg.js",
    "content": "const filterPkg = (pkgObject, ignoreList) => {\n  const devObj = {};\n  Object.keys(pkgObject).forEach(key => {\n    const isIgnore = ignoreList.some(reg => {\n      return new RegExp(reg).test(key);\n    });\n    if (isIgnore) {\n      return;\n    }\n    devObj[key] = pkgObject[key];\n  });\n  return devObj;\n};\n\nmodule.exports = filterPkg;\n"
  },
  {
    "path": "src/create/generators/ant-design-pro/index.js",
    "content": "const fs = require('fs-extra');\nconst path = require('path');\nconst chalk = require('chalk');\nconst glob = require('glob');\nconst exec = require('execa');\nconst rimraf = require('rimraf');\nconst BasicGenerator = require('../../BasicGenerator');\nconst filterPkg = require('./filterPkg');\nconst prettier = require('prettier');\nconst sortPackage = require('sort-package-json');\nconst { getFastGithub } = require('umi-utils');\n\nfunction log(...args) {\n  console.log(`${chalk.gray('>')}`, ...args);\n}\n\nfunction globList(patternList, options) {\n  let fileList = [];\n  patternList.forEach((pattern) => {\n    fileList = [...fileList, ...glob.sync(pattern, options)];\n  });\n\n  return fileList;\n}\n\nconst getGithubUrl = async (origin = '') => {\n  const githubUrl = 'https://github.com/ant-design/ant-design-pro';\n  const giteeUrl = 'https://gitee.com/ant-design/ant-design-pro';\n  // 通过 --origin=xxx指定源\n  if (origin === 'github') {\n    return githubUrl;\n  }\n  if (origin === 'gitee') {\n    return giteeUrl;\n  }\n  // 不指定源\n  const fastGithub = await getFastGithub();\n  if (fastGithub === 'gitee.com' || fastGithub === 'github.com.cnpmjs.org') {\n    return giteeUrl;\n  }\n  return githubUrl;\n};\n\nclass AntDesignProGenerator extends BasicGenerator {\n  prompting() {\n    const TEMPLATES = [\n      { name: 'simple', description: '简单脚手架，只包含基础框架' },\n      { name: 'complete', description: '全量版本，包含所有区块和功能' },\n    ];\n\n    // 列出可用模板\n    if (this.opts.args.listTemplates) {\n      console.log(`\\nAvailable templates for ${chalk.green('create')} command:\\n`);\n      TEMPLATES.forEach((t) => {\n        console.log(`  ${chalk.cyan(t.name.padEnd(12))} ${chalk.gray(t.description)}`);\n      });\n      console.log(`\\nUsage: pro create <project-name> --template <template>\\n`);\n      process.exit(0);\n    }\n\n    // 支持非交互式模式：通过 --template 参数指定模板类型\n    const template = this.opts.args.template;\n    if (template) {\n      const validTemplates = TEMPLATES.map((t) => t.name);\n      if (!validTemplates.includes(template)) {\n        console.log(chalk.red(`Invalid template: ${template}.`));\n        console.log(`Valid templates: ${validTemplates.join(', ')}`);\n        console.log(`Run ${chalk.cyan('pro create --list-templates')} to see all options.`);\n        process.exit(1);\n      }\n      this.prompts = { allBlocks: template };\n      return Promise.resolve();\n    }\n\n    const prompts = [\n      {\n        name: 'allBlocks',\n        type: 'list',\n        message: '🚀 要全量的还是一个简单的脚手架?',\n        choices: TEMPLATES.map((t) => t.name),\n        default: 'simple',\n      },\n    ];\n    return this.prompt(prompts).then((props) => {\n      this.prompts = props;\n    });\n  }\n\n  async writing() {\n    const { allBlocks } = this.prompts;\n\n    const projectName = this.opts.name || this.opts.env.cwd;\n    const projectPath = path.resolve(projectName);\n\n    const envOptions = {\n      cwd: projectPath,\n    };\n\n    const githubUrl = await getGithubUrl(this.opts.args.origin);\n    const gitArgs = [`clone`, githubUrl, `--depth=1`];\n\n    // all-blocks 分支上包含了所有的区块\n    if (allBlocks === 'complete') {\n      gitArgs.push('--branch', 'all-blocks');\n    }\n\n    gitArgs.push(projectName);\n\n    // // 没有提供关闭的配置\n    // // https://github.com/yeoman/environment/blob/9880bc7d5b26beff9f0b4d5048c672a85ce4bcaa/lib/util/repository.js#L50\n    const yoConfigPth = path.join(projectPath, '.yo-repository');\n    if (fs.existsSync(yoConfigPth)) {\n      // 删除 .yo-repository\n      rimraf.sync(yoConfigPth);\n    }\n\n    if (\n      fs.existsSync(projectPath) &&\n      fs.statSync(projectPath).isDirectory() &&\n      fs.readdirSync(projectPath).length > 0\n    ) {\n      console.log('\\n');\n      console.log(`🙈 请在空文件夹中使用，或者使用 ${chalk.red('yarn create umi myapp')}`);\n      console.log(`🙈 Please select an empty folder, or use ${chalk.red('yarn create umi myapp')}`);\n      process.exit(1);\n    }\n\n    // Clone remote branch\n    // log(`git ${[`clone`, githubUrl].join(' ')}`);\n    log(`clone repo url: ${githubUrl}`);\n    await exec(\n      `git`,\n      gitArgs,\n      process.env.TEST\n        ? {}\n        : {\n            stdout: process.stdout,\n            stderr: process.stderr,\n            stdin: process.stdin,\n          },\n    );\n\n    log(`🚚 clone success`);\n\n    const packageJsonPath = path.resolve(projectPath, 'package.json');\n    const pkg = require(packageJsonPath);\n\n    // copy readme\n    const babelConfig = path.resolve(__dirname, 'README.md');\n    fs.copySync(babelConfig, path.resolve(projectPath, 'README.md'));\n\n    // gen package.json\n    if (pkg['create-umi']) {\n      const { ignoreScript = [], ignoreDependencies = [] } = pkg['create-umi'];\n      // filter scripts and devDependencies\n      const projectPkg = {\n        ...pkg,\n        scripts: filterPkg(pkg.scripts, ignoreScript),\n        devDependencies: filterPkg(pkg.devDependencies, ignoreDependencies),\n      };\n      // remove create-umi config\n      delete projectPkg['create-umi'];\n      fs.writeFileSync(\n        path.resolve(projectPath, 'package.json'),\n        // 删除一个包之后 json会多了一些空行。sortPackage 可以删除掉并且排序\n        // prettier 会容忍一个空行\n        prettier.format(JSON.stringify(sortPackage(projectPkg)), {\n          parser: 'json',\n        }),\n      );\n    }\n\n    // Clean up useless files\n    if (pkg['create-umi'] && pkg['create-umi'].ignore) {\n      log('Clean up...');\n      const ignoreFiles = pkg['create-umi'].ignore;\n      const fileList = globList(ignoreFiles, envOptions);\n\n      fileList.forEach((filePath) => {\n        const targetPath = path.resolve(projectPath, filePath);\n        fs.removeSync(targetPath);\n      });\n    }\n  }\n}\n\nmodule.exports = AntDesignProGenerator;\n"
  },
  {
    "path": "src/create/generators/ant-design-pro/meta.json",
    "content": "{\n  \"description\": \"Create project with a layout-only ant-design-pro boilerplate, use together with umi block.\"\n}\n"
  },
  {
    "path": "src/create/index.js",
    "content": "const fs = require('fs');\nconst path = require('path');\nconst chalk = require('chalk');\nconst mkdirp = require('mkdirp');\nconst yeoman = require('yeoman-environment');\n\nconst runGenerator = async (generatorPath, { name = '', cwd = process.cwd(), ...args }) => {\n  return new Promise((resolve) => {\n    if (name) {\n      mkdirp.sync(name);\n      // eslint-disable-next-line no-param-reassign\n      cwd = path.join(cwd, name);\n    }\n\n    const Generator = require(generatorPath);\n    const env = yeoman.createEnv([], {\n      cwd,\n    });\n    const generator = new Generator({\n      name,\n      env,\n      resolved: require.resolve(generatorPath),\n      args,\n    });\n\n    return generator.run(() => {\n      console.log('✨ File Generate Done');\n      resolve(true);\n    });\n  });\n};\n\nconst run = async (config) => {\n  try {\n    return runGenerator(`./generators/ant-design-pro`, config);\n  } catch (e) {\n    console.error(chalk.red(`> Generate failed`), e);\n    process.exit(1);\n  }\n};\n\nmodule.exports = run;\n"
  },
  {
    "path": "src/fetch-blocks/blocks.json",
    "content": "[\n  {\n    \"path\": \".editorconfig\",\n    \"mode\": \"100644\",\n    \"type\": \"blob\",\n    \"sha\": \"7e3649acc2c165b62750e2ca02b80f8ee0da6c4d\",\n    \"size\": 245,\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/blobs/7e3649acc2c165b62750e2ca02b80f8ee0da6c4d\"\n  },\n  {\n    \"path\": \".eslintignore\",\n    \"mode\": \"100644\",\n    \"type\": \"blob\",\n    \"sha\": \"a0a4ac0be6b2d8952b74179ccd69698560ef2ca8\",\n    \"size\": 59,\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/blobs/a0a4ac0be6b2d8952b74179ccd69698560ef2ca8\"\n  },\n  {\n    \"path\": \".eslintrc.js\",\n    \"mode\": \"100644\",\n    \"type\": \"blob\",\n    \"sha\": \"d43079dcfda57db55e8d245e5cc0e83b2f2d9d53\",\n    \"size\": 398,\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/blobs/d43079dcfda57db55e8d245e5cc0e83b2f2d9d53\"\n  },\n  {\n    \"path\": \".github\",\n    \"mode\": \"040000\",\n    \"type\": \"tree\",\n    \"sha\": \"c7addc58ce8f79b7256b12c4c82dd2fba5a93f0f\",\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/trees/c7addc58ce8f79b7256b12c4c82dd2fba5a93f0f\"\n  },\n  {\n    \"path\": \".gitignore\",\n    \"mode\": \"100644\",\n    \"type\": \"blob\",\n    \"sha\": \"44e3fe68ed25dd86a0955b66593a58d7981b481b\",\n    \"size\": 484,\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/blobs/44e3fe68ed25dd86a0955b66593a58d7981b481b\"\n  },\n  {\n    \"path\": \".prettierignore\",\n    \"mode\": \"100644\",\n    \"type\": \"blob\",\n    \"sha\": \"87da24412ddc17992e9dbad6e200a58a13f09ab3\",\n    \"size\": 240,\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/blobs/87da24412ddc17992e9dbad6e200a58a13f09ab3\"\n  },\n  {\n    \"path\": \".prettierrc.js\",\n    \"mode\": \"100644\",\n    \"type\": \"blob\",\n    \"sha\": \"7b597d78917c7e33a81bb3e83c6067f6a9a970e6\",\n    \"size\": 86,\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/blobs/7b597d78917c7e33a81bb3e83c6067f6a9a970e6\"\n  },\n  {\n    \"path\": \".script\",\n    \"mode\": \"040000\",\n    \"type\": \"tree\",\n    \"sha\": \"55e7f5c1b3929b407168d8c33dc9ab848fc0dc1c\",\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/trees/55e7f5c1b3929b407168d8c33dc9ab848fc0dc1c\"\n  },\n  {\n    \"path\": \".stylelintrc.js\",\n    \"mode\": \"100644\",\n    \"type\": \"blob\",\n    \"sha\": \"c2030787de72c5cffc44099fbdbae55c32afd482\",\n    \"size\": 87,\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/blobs/c2030787de72c5cffc44099fbdbae55c32afd482\"\n  },\n  {\n    \"path\": \".umirc.js\",\n    \"mode\": \"100644\",\n    \"type\": \"blob\",\n    \"sha\": \"8aff3c30bd999b5c6da80035078fb06d28b996f8\",\n    \"size\": 260,\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/blobs/8aff3c30bd999b5c6da80035078fb06d28b996f8\"\n  },\n  {\n    \"path\": \"AccountCenter\",\n    \"mode\": \"040000\",\n    \"type\": \"tree\",\n    \"sha\": \"9e248eae0ed18fb3da075aa57ddd2a8696a73624\",\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/trees/9e248eae0ed18fb3da075aa57ddd2a8696a73624\"\n  },\n  {\n    \"path\": \"AccountSettings\",\n    \"mode\": \"040000\",\n    \"type\": \"tree\",\n    \"sha\": \"49222a52f5d486efd77f8e11f6b07939fd8041fd\",\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/trees/49222a52f5d486efd77f8e11f6b07939fd8041fd\"\n  },\n  {\n    \"path\": \"CONTRIBUTING.md\",\n    \"mode\": \"100644\",\n    \"type\": \"blob\",\n    \"sha\": \"97a7df34af13089a768f0c9c2dc296cd72f803e8\",\n    \"size\": 358,\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/blobs/97a7df34af13089a768f0c9c2dc296cd72f803e8\"\n  },\n  {\n    \"path\": \"DashboardAnalysis\",\n    \"mode\": \"040000\",\n    \"type\": \"tree\",\n    \"sha\": \"67e69a6253b473355de7e551adbd905c6f9c41d0\",\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/trees/67e69a6253b473355de7e551adbd905c6f9c41d0\"\n  },\n  {\n    \"path\": \"DashboardMonitor\",\n    \"mode\": \"040000\",\n    \"type\": \"tree\",\n    \"sha\": \"823d402bb1bc607fe89eefff23afc8dd0e6d221a\",\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/trees/823d402bb1bc607fe89eefff23afc8dd0e6d221a\"\n  },\n  {\n    \"path\": \"DashboardWorkplace\",\n    \"mode\": \"040000\",\n    \"type\": \"tree\",\n    \"sha\": \"923bcc518b4f8d4e61fa67b7d41180ae36b5d78a\",\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/trees/923bcc518b4f8d4e61fa67b7d41180ae36b5d78a\"\n  },\n  {\n    \"path\": \"EditorFlow\",\n    \"mode\": \"040000\",\n    \"type\": \"tree\",\n    \"sha\": \"f4d2b18c492bebbfb7e57560e70271f1531b069e\",\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/trees/f4d2b18c492bebbfb7e57560e70271f1531b069e\"\n  },\n  {\n    \"path\": \"EditorKoni\",\n    \"mode\": \"040000\",\n    \"type\": \"tree\",\n    \"sha\": \"5dea36d89efa9d8ea0198db4447ec05332e72dc1\",\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/trees/5dea36d89efa9d8ea0198db4447ec05332e72dc1\"\n  },\n  {\n    \"path\": \"EditorMind\",\n    \"mode\": \"040000\",\n    \"type\": \"tree\",\n    \"sha\": \"9b69c0b01a94bda20d0b5e3fb3a6af901eaf2f18\",\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/trees/9b69c0b01a94bda20d0b5e3fb3a6af901eaf2f18\"\n  },\n  {\n    \"path\": \"EmptyPage\",\n    \"mode\": \"040000\",\n    \"type\": \"tree\",\n    \"sha\": \"423fe42c55f75fe29fb0a8d6e2a605499c76d3fa\",\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/trees/423fe42c55f75fe29fb0a8d6e2a605499c76d3fa\"\n  },\n  {\n    \"path\": \"Exception403\",\n    \"mode\": \"040000\",\n    \"type\": \"tree\",\n    \"sha\": \"4d29af5bdc67abca831d07799a6099b8286b550a\",\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/trees/4d29af5bdc67abca831d07799a6099b8286b550a\"\n  },\n  {\n    \"path\": \"Exception404\",\n    \"mode\": \"040000\",\n    \"type\": \"tree\",\n    \"sha\": \"2146fabfd876b34620e51a41d245eac748394764\",\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/trees/2146fabfd876b34620e51a41d245eac748394764\"\n  },\n  {\n    \"path\": \"Exception500\",\n    \"mode\": \"040000\",\n    \"type\": \"tree\",\n    \"sha\": \"02fabc16f9b1c98ce44752217dc5b8e2b28582dd\",\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/trees/02fabc16f9b1c98ce44752217dc5b8e2b28582dd\"\n  },\n  {\n    \"path\": \"FormAdvancedForm\",\n    \"mode\": \"040000\",\n    \"type\": \"tree\",\n    \"sha\": \"1d4013349e87a467651574c259b75d8922212dc9\",\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/trees/1d4013349e87a467651574c259b75d8922212dc9\"\n  },\n  {\n    \"path\": \"FormBasicForm\",\n    \"mode\": \"040000\",\n    \"type\": \"tree\",\n    \"sha\": \"b00be4373ad60661d63a171cc0bcaa3e7eea2bc8\",\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/trees/b00be4373ad60661d63a171cc0bcaa3e7eea2bc8\"\n  },\n  {\n    \"path\": \"FormStepForm\",\n    \"mode\": \"040000\",\n    \"type\": \"tree\",\n    \"sha\": \"cd9bde40fa2d89ae09a91a67ba25b57f8ee1b10e\",\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/trees/cd9bde40fa2d89ae09a91a67ba25b57f8ee1b10e\"\n  },\n  {\n    \"path\": \"LICENSE\",\n    \"mode\": \"100644\",\n    \"type\": \"blob\",\n    \"sha\": \"375ea0ec1c7b089f6a2d5eaee3a44e8727bc5bb5\",\n    \"size\": 1062,\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/blobs/375ea0ec1c7b089f6a2d5eaee3a44e8727bc5bb5\"\n  },\n  {\n    \"path\": \"ListBasicList\",\n    \"mode\": \"040000\",\n    \"type\": \"tree\",\n    \"sha\": \"2eba7855c78fba7952d324c29c7a6f97fb7968e1\",\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/trees/2eba7855c78fba7952d324c29c7a6f97fb7968e1\"\n  },\n  {\n    \"path\": \"ListCardList\",\n    \"mode\": \"040000\",\n    \"type\": \"tree\",\n    \"sha\": \"a609931242ea4c08f1827f069cf7c5b7ab96749e\",\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/trees/a609931242ea4c08f1827f069cf7c5b7ab96749e\"\n  },\n  {\n    \"path\": \"ListSearch\",\n    \"mode\": \"040000\",\n    \"type\": \"tree\",\n    \"sha\": \"848585078667e19a537db9b25e4208874485dddb\",\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/trees/848585078667e19a537db9b25e4208874485dddb\"\n  },\n  {\n    \"path\": \"ListSearchApplications\",\n    \"mode\": \"040000\",\n    \"type\": \"tree\",\n    \"sha\": \"ffebb2570cfb9f5a89b235a0dcef0bb4477c6792\",\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/trees/ffebb2570cfb9f5a89b235a0dcef0bb4477c6792\"\n  },\n  {\n    \"path\": \"ListSearchArticles\",\n    \"mode\": \"040000\",\n    \"type\": \"tree\",\n    \"sha\": \"711dc59e04ef4788a70a9c5056e78dfb5ca38697\",\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/trees/711dc59e04ef4788a70a9c5056e78dfb5ca38697\"\n  },\n  {\n    \"path\": \"ListSearchProjects\",\n    \"mode\": \"040000\",\n    \"type\": \"tree\",\n    \"sha\": \"535637ca58bd5fc6ad004006377290ac1b5d5699\",\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/trees/535637ca58bd5fc6ad004006377290ac1b5d5699\"\n  },\n  {\n    \"path\": \"ListTableList\",\n    \"mode\": \"040000\",\n    \"type\": \"tree\",\n    \"sha\": \"045fca7caea4bf5327e378a3c85ab5d7d75f509d\",\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/trees/045fca7caea4bf5327e378a3c85ab5d7d75f509d\"\n  },\n  {\n    \"path\": \"ProfileAdvanced\",\n    \"mode\": \"040000\",\n    \"type\": \"tree\",\n    \"sha\": \"b657a0e84180c49ab8da87c25b40835904349164\",\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/trees/b657a0e84180c49ab8da87c25b40835904349164\"\n  },\n  {\n    \"path\": \"ProfileBasic\",\n    \"mode\": \"040000\",\n    \"type\": \"tree\",\n    \"sha\": \"2244f5d92c4eaf6a9fef746f7b60da5330ab330d\",\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/trees/2244f5d92c4eaf6a9fef746f7b60da5330ab330d\"\n  },\n  {\n    \"path\": \"ResultFail\",\n    \"mode\": \"040000\",\n    \"type\": \"tree\",\n    \"sha\": \"f663f9d373cf37b25ffc2823cd5d92df9b9639f7\",\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/trees/f663f9d373cf37b25ffc2823cd5d92df9b9639f7\"\n  },\n  {\n    \"path\": \"ResultSuccess\",\n    \"mode\": \"040000\",\n    \"type\": \"tree\",\n    \"sha\": \"cf68b025911d8bd029a2e1ed697e89063a6cc140\",\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/trees/cf68b025911d8bd029a2e1ed697e89063a6cc140\"\n  },\n  {\n    \"path\": \"UserLogin\",\n    \"mode\": \"040000\",\n    \"type\": \"tree\",\n    \"sha\": \"dbd490d688653d46ca01df7349baa433757b240d\",\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/trees/dbd490d688653d46ca01df7349baa433757b240d\"\n  },\n  {\n    \"path\": \"UserRegister\",\n    \"mode\": \"040000\",\n    \"type\": \"tree\",\n    \"sha\": \"5f905d0e80d9b6dea28356ff0b58b9e2f3b6717d\",\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/trees/5f905d0e80d9b6dea28356ff0b58b9e2f3b6717d\"\n  },\n  {\n    \"path\": \"UserRegisterResult\",\n    \"mode\": \"040000\",\n    \"type\": \"tree\",\n    \"sha\": \"f923c8f3124c8c896216f623f1dc942801d4b67e\",\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/trees/f923c8f3124c8c896216f623f1dc942801d4b67e\"\n  },\n  {\n    \"path\": \"umi-block.json\",\n    \"mode\": \"100644\",\n    \"type\": \"blob\",\n    \"sha\": \"730e69df7d2535d5753fd68edda0cf88825dd09f\",\n    \"size\": 17580,\n    \"url\": \"https://api.github.com/repos/ant-design/pro-blocks/git/blobs/730e69df7d2535d5753fd68edda0cf88825dd09f\"\n  }\n]\n"
  },
  {
    "path": "src/fetch-blocks/index.js",
    "content": "const path = require('path');\nconst fs = require('fs');\nconst fetch = require('node-fetch');\nconst execa = require('execa');\nconst rimraf = require('rimraf');\nconst getNpmRegistry = require('../utils/getNpmRegistry');\nconst chalk = require('chalk');\nconst ora = require('ora');\nconst insertCode = require('./insertCode');\nconst getNewRouteCode = require('./replaceRouter');\nconst router = require('./router.config');\nconst blocks = require('./blocks.json');\n\nconst spinner = ora();\n\nlet isJS = false;\n\nconst fetchGithubFiles = async () => {\n  const ignoreFile = ['_scripts', 'tests'];\n  const data = await fetch('https://api.github.com/repos/ant-design/pro-blocks/git/trees/master');\n  if (data.status !== 200) {\n    return blocks.filter((file) => file.type === 'tree' && !ignoreFile.includes(file.path));\n  }\n  const { tree } = await data.json();\n  const files = tree.filter((file) => file.type === 'tree' && !ignoreFile.includes(file.path));\n  return files;\n};\n\nconst findAllInstallRouter = (route) => {\n  let routers = [];\n  route.forEach((item) => {\n    if (item.component && item.path) {\n      if (item.path !== '/user' || item.path !== '/') {\n        routers.push({\n          ...item,\n          routes: !!item.routes,\n        });\n      }\n    }\n    if (item.routes) {\n      routers = routers.concat(findAllInstallRouter(item.routes));\n    }\n  });\n  return routers;\n};\n\nconst filterParentRouter = (route, layout) =>\n  [...route]\n    .map((item) => {\n      if (!item.path && item.component === '404') {\n        return item;\n      }\n      if (item.routes && (!route.component || layout)) {\n        return { ...item, routes: filterParentRouter(item.routes, false) };\n      }\n      if (item.path === '/user/login') {\n        return item;\n      }\n      if (item.redirect) {\n        return item;\n      }\n      return null;\n    })\n    .filter((item) => item);\n\nconst firstUpperCase = (pathString) =>\n  pathString\n    .replace('.', '')\n    .split(/\\/|-/)\n    .map((s) => s.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase()))\n    .filter((s) => s)\n    .join('');\n\nconst execCmd = (shell, cwd, option = {}) => {\n  const debug = process.env.PRO_CLI !== 'NONE';\n  if (option.sync) {\n    return execa.commandSync(shell, {\n      encoding: 'utf8',\n      cwd,\n      env: {\n        ...process.env,\n        PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true,\n      },\n      stderr: debug ? 'inherit' : 'pipe',\n      stdout: debug ? 'inherit' : 'pipe',\n    });\n  }\n\n  return new Promise((resolve) => {\n    const onData = (data) => {\n      if (debug) {\n        process.stdout.write(data);\n      }\n      if (data && `${data}`.toLowerCase().includes('success')) {\n        resolve({\n          exitCode: 0,\n        });\n      }\n    };\n\n    const { stdout, stderr } = execa.command(shell, {\n      encoding: 'utf8',\n      cwd,\n      env: {\n        ...process.env,\n        PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true,\n      },\n      stderr: 'pipe',\n      stdout: 'pipe',\n    });\n\n    stdout.on('data', onData);\n    stderr.on('data', onData);\n\n    setTimeout(() => {\n      resolve({\n        exitCode: 2,\n      });\n    }, 100000);\n  });\n};\n\nconst installBlock = async (cwd, arg) => {\n  let gitFiles = await fetchGithubFiles();\n  const installRouters = findAllInstallRouter(router);\n  const installBlockIteration = async (i) => {\n    const item = installRouters[i];\n\n    if (!item || !item.path) {\n      return Promise.resolve();\n    }\n    const gitPath = firstUpperCase(item.path);\n\n    // 如果这个区块在 git 上存在\n    if (gitFiles.find((file) => file.path === gitPath)) {\n      console.log(`📦  install ${chalk.green(item.name)}  to: ${chalk.yellow(item.path)}`);\n      // 如果文件夹存在，删除他\n      rimraf.sync(path.join(cwd, '/src/pages', item.path));\n\n      // 从路由中删除这个区块\n      gitFiles = gitFiles.filter((file) => file.path !== gitPath);\n\n      const cmd = [\n        `umi block add https://github.com/ant-design/pro-blocks/tree/master/${gitPath}`,\n        `--path=${item.path}`,\n        '--skip-dependencies',\n        '--page',\n        `--branch=${arg.branch || 'master'}`,\n      ];\n\n      // 如果是 routes 就不修改路由\n      if (item.routes) {\n        cmd.push('--skip-modify-routes');\n      }\n\n      // 如果是 config.js 就下载 js 的区块\n      if (isJS) {\n        cmd.push('--js');\n      }\n\n      try {\n        const { exitCode } = await execCmd(cmd.join(' '), cwd);\n        if (exitCode === 1) {\n          process.exit();\n        }\n        console.log(\n          `👌  install ${chalk.green(item.name)}  to: ${chalk.yellow(item.path)} success`,\n        );\n      } catch (error) {\n        console.error(error);\n        process.exit();\n      }\n    }\n    return installBlockIteration(i + 1);\n  };\n  // 安装路由中设置的区块\n  await installBlockIteration(0);\n};\n\nmodule.exports = async ({ cwd, js, ...rest }) => {\n  spinner.start('🧐  find config.ts ...');\n  let relativePath = path.join(cwd, './config/config.ts');\n  isJS = js;\n  // 如果 ts 不存在 去找 js 的\n  if (!fs.existsSync(relativePath)) {\n    spinner.warn();\n    spinner.start('🧐  find config.js ...');\n\n    relativePath = path.join(cwd, './config/config.js');\n    isJS = true;\n  }\n\n  if (!fs.existsSync(relativePath)) {\n    spinner.warn();\n    // 如果 js 还不在报错\n    console.log(chalk.red('🤔  config.js or config.ts not found'));\n    return;\n  }\n  spinner.succeed();\n\n  // replace router config\n  const parentRouter = filterParentRouter(router, true);\n  const { routesPath, code } = getNewRouteCode(relativePath, parentRouter);\n\n  // write ParentRouter\n  fs.writeFileSync(routesPath, code);\n\n  await installBlock(cwd, rest);\n  await insertCode(cwd, rest);\n\n  /**\n   * 安装依赖，因为 pro 的中忽略了依赖安装来增加速度\n   */\n  const useYarn = fs.existsSync(path.join(cwd, 'yarn.lock'));\n  const registryUrl = await getNpmRegistry();\n  console.log(\n    [useYarn ? 'yarn' : 'npm', useYarn ? '' : 'install', `--registry=${registryUrl}`]\n      .filter((n) => n)\n      .join(' '),\n  );\n  spinner.start(`🚚  install dependencies package`);\n  execCmd(\n    [useYarn ? 'yarn' : 'npm', useYarn ? '' : 'install', `--registry=${registryUrl}`].join(' '),\n    undefined,\n    {\n      sync: true,\n    },\n  );\n\n  spinner.succeed();\n\n  process.exit();\n};\n"
  },
  {
    "path": "src/fetch-blocks/insertCode.js",
    "content": "const parser = require('@babel/parser');\nconst traverse = require('@babel/traverse');\nconst generate = require('@babel/generator');\nconst fs = require('fs');\nconst path = require('path');\nconst prettier = require('prettier');\nconst chalk = require('chalk');\nconst ora = require('ora');\n\nconst spinner = ora();\n\nconst parseCode = (code) =>\n  parser.parse(code, {\n    sourceType: 'module',\n    plugins: ['typescript', 'jsx'],\n  }).program.body[0];\n\n/**\n * 生成代码\n * @param {*} ast\n */\nfunction generateCode(ast) {\n  const newCode = generate.default(ast, {}).code;\n  return prettier.format(newCode, {\n    // format same as ant-design-pro\n    singleQuote: true,\n    trailingComma: 'es5',\n    printWidth: 100,\n    parser: 'typescript',\n  });\n}\n\nconst mapAst = (configPath, callBack) => {\n  const ast = parser.parse(fs.readFileSync(configPath, 'utf-8'), {\n    sourceType: 'module',\n    plugins: ['typescript', 'jsx'],\n  });\n  // 查询当前配置文件是否导出 routes 属性\n  traverse.default(ast, {\n    Program({ node }) {\n      const { body } = node;\n      callBack(body);\n    },\n  });\n  return generateCode(ast);\n};\n\nconst insertRightContent = (configPath) =>\n  mapAst(configPath, (body) => {\n    const codeIndex = body.findIndex((item) => item.type !== 'ImportDeclaration');\n    // 从组件中导入 CopyBlock\n    body.splice(codeIndex, 0, parseCode('import NoticeIconView from \"../NoticeIcon\";'));\n\n    body.forEach((item) => {\n      if (item.type === 'VariableDeclaration') {\n        const classBody = item.declarations[0].init.body;\n        if (!classBody || !classBody.body) {\n          return;\n        }\n        classBody.body.forEach((node) => {\n          if (node.type === 'ReturnStatement') {\n            const index = node.argument.children.findIndex((argumentItem) => {\n              if (argumentItem.type === 'JSXElement') {\n                if (argumentItem.openingElement.name.name === 'Avatar') {\n                  return true;\n                }\n              }\n              return undefined;\n            });\n            node.argument.children.splice(index, 1, parseCode('<Avatar menu />').expression);\n            node.argument.children.splice(index, 0, parseCode('<NoticeIconView />').expression);\n          }\n        });\n      }\n    });\n  });\n\nconst getJsxOrTsx = (cwd, fileName) => {\n  let filePath = path.join(cwd, fileName);\n  if (!fs.existsSync(filePath)) {\n    filePath = path.join(cwd, fileName.replace('.tsx', '.jsx'));\n  }\n  return filePath;\n};\n\nmodule.exports = (cwd) => {\n  spinner.start(`🎁  insert ${chalk.hex('#1890ff')('RightContent')} success`);\n  const rightContentPath = getJsxOrTsx(cwd, '/src/components/RightContent/index.tsx');\n  if (fs.existsSync(rightContentPath)) {\n    fs.writeFileSync(rightContentPath, insertRightContent(rightContentPath));\n  }\n  spinner.succeed();\n};\n"
  },
  {
    "path": "src/fetch-blocks/replaceRouter.js",
    "content": "const parser = require('@babel/parser');\nconst traverse = require('@babel/traverse');\nconst generate = require('@babel/generator');\nconst t = require('@babel/types');\nconst fs = require('fs');\nconst prettier = require('prettier');\n\nconst getNewRouteCode = (configPath, newRoute) => {\n  const ast = parser.parse(fs.readFileSync(configPath, 'utf-8'), {\n    sourceType: 'module',\n    plugins: ['typescript'],\n  });\n  let routesNode = null;\n  const importModules = [];\n  // 查询当前配置文件是否导出 routes 属性\n  traverse.default(ast, {\n    Program({ node }) {\n      // find import\n      const { body } = node;\n      body.forEach((item) => {\n        if (t.isImportDeclaration(item)) {\n          const { specifiers } = item;\n          const defaultEpecifier = specifiers.find(\n            (s) => t.isImportDefaultSpecifier(s) && t.isIdentifier(s.local),\n          );\n          if (defaultEpecifier && t.isStringLiteral(item.source)) {\n            importModules.push({\n              identifierName: defaultEpecifier.local.name,\n              modulePath: item.source.value,\n            });\n          }\n        }\n      });\n    },\n    ObjectExpression({ node, parent }) {\n      // find routes on object, like { routes: [] }\n      if (t.isArrayExpression(parent)) {\n        // children routes\n        return;\n      }\n      const { properties } = node;\n      properties.forEach((p) => {\n        const { key, value } = p;\n        if (t.isObjectProperty(p) && t.isIdentifier(key) && key.name === 'routes') {\n          if (value) {\n            // find json file program expression\n            // eslint-disable-next-line no-param-reassign\n            p.value = parser.parse(JSON.stringify(newRoute)).program.body[0].expression;\n            routesNode = value;\n          }\n        }\n      });\n    },\n  });\n  if (routesNode) {\n    // eslint-disable-next-line no-use-before-define\n    const code = generateCode(ast);\n    return { code, routesPath: configPath };\n  }\n  throw new Error('route array config not found.');\n};\n\n/**\n * 生成代码\n * @param {*} ast\n */\nfunction generateCode(ast) {\n  const newCode = generate.default(ast, {}).code;\n  return prettier.format(newCode, {\n    // format same as ant-design-pro\n    singleQuote: true,\n    trailingComma: 'es5',\n    printWidth: 100,\n    parser: 'typescript',\n  });\n}\n\nmodule.exports = getNewRouteCode;\n"
  },
  {
    "path": "src/fetch-blocks/router.config.js",
    "content": "module.exports = [\n  // user\n  {\n    path: '/user',\n    layout: false,\n    routes: [\n      { path: '/user/login', layout: false, name: 'login', component: './user/Login' },\n      {\n        path: '/user/register',\n        name: 'register',\n        layout: false,\n        component: './User/Register',\n      },\n      {\n        path: '/user/register-result',\n        name: 'register.result',\n        layout: false,\n        component: './User/RegisterResult',\n      },\n      { path: '/user', redirect: '/user/login' },\n      {\n        component: '404',\n      },\n    ],\n  },\n  // dashboard\n  {\n    path: '/dashboard',\n    name: 'dashboard',\n    icon: 'dashboard',\n    routes: [\n      {\n        path: '/dashboard/analysis',\n        name: 'analysis',\n        component: './Dashboard/Analysis',\n      },\n      {\n        path: '/dashboard/monitor',\n        name: 'monitor',\n        component: './Dashboard/Monitor',\n      },\n      {\n        path: '/dashboard/workplace',\n        name: 'workplace',\n        component: './Dashboard/Workplace',\n      },\n      { path: '/dashboard', redirect: '/dashboard/analysis' },\n    ],\n  },\n  // forms\n  {\n    path: '/form',\n    icon: 'form',\n    name: 'form',\n    routes: [\n      {\n        path: '/form/basic-form',\n        name: 'basicform',\n        component: './Form/BasicForm',\n      },\n      {\n        path: '/form/step-form',\n        name: 'stepform',\n        component: './Form/StepForm',\n      },\n      {\n        path: '/form/advanced-form',\n        name: 'advancedform',\n        component: './Form/AdvancedForm',\n      },\n      { path: '/form', redirect: '/form/basic-form' },\n    ],\n  },\n  // list\n  {\n    path: '/list',\n    icon: 'table',\n    name: 'list',\n    routes: [\n      {\n        path: '/list/table-list',\n        name: 'searchtable',\n        component: './list/Tablelist',\n      },\n      {\n        path: '/list/basic-list',\n        name: 'basiclist',\n        component: './list/Basiclist',\n      },\n      {\n        path: '/list/card-list',\n        name: 'cardlist',\n        component: './list/Cardlist',\n      },\n      {\n        path: '/list/search',\n        name: 'search-list',\n        component: './list/search',\n        routes: [\n          {\n            path: '/list/search/articles',\n            name: 'articles',\n            component: './list/Articles',\n          },\n          {\n            path: '/list/search/projects',\n            name: 'projects',\n            component: './list/Projects',\n          },\n          {\n            path: '/list/search/applications',\n            name: 'applications',\n            component: './list/Applications',\n          },\n          {\n            path: '/list/search',\n            redirect: '/list/search/articles',\n          },\n        ],\n      },\n      { path: '/list', redirect: '/list/table-list' },\n    ],\n  },\n  {\n    path: '/profile',\n    name: 'profile',\n    icon: 'profile',\n    routes: [\n      // profile\n      {\n        path: '/profile/basic',\n        name: 'basic',\n        component: './Profile/BasicProfile',\n      },\n      {\n        path: '/profile/basic/:id',\n        hideInMenu: true,\n        component: './Profile/BasicProfile',\n      },\n      {\n        path: '/profile/advanced',\n        name: 'advanced',\n        component: './Profile/AdvancedProfile',\n      },\n      { path: '/profile', redirect: '/profile/basic' },\n    ],\n  },\n  {\n    name: 'result',\n    icon: 'CheckCircleOutlined',\n    path: '/result',\n    routes: [\n      // result\n      {\n        path: '/result/success',\n        name: 'success',\n        component: './Result/Success',\n      },\n      {\n        path: '/result/fail',\n        name: 'fail',\n        component: './Result/Error',\n      },\n      { path: '/result', redirect: '/result/success' },\n    ],\n  },\n  {\n    name: 'exception',\n    icon: 'warning',\n    path: '/exception',\n    routes: [\n      // exception\n      {\n        path: '/exception/403',\n        name: 'not-permission',\n        component: './Exception/403',\n      },\n      {\n        path: '/exception/404',\n        name: 'not-find',\n        component: './Exception/404',\n      },\n      {\n        path: '/exception/500',\n        name: 'server-error',\n        component: './Exception/500',\n      },\n      { path: '/exception', redirect: '/exception/403' },\n    ],\n  },\n  {\n    name: 'account',\n    icon: 'user',\n    path: '/account',\n    routes: [\n      {\n        path: '/account/center',\n        name: 'center',\n        component: './Account/Center/Center',\n      },\n      {\n        path: '/account/settings',\n        name: 'settings',\n        component: './Account/Settings/Info',\n      },\n      { path: '/account', redirect: '/account/center' },\n    ],\n  },\n  //  editor\n  {\n    name: 'editor',\n    icon: 'highlight',\n    path: '/editor',\n    routes: [\n      {\n        path: '/editor/flow',\n        name: 'flow',\n        component: './Editor/GGEditor/Flow',\n      },\n      {\n        path: '/editor/mind',\n        name: 'mind',\n        component: './Editor/GGEditor/Mind',\n      },\n      {\n        path: '/editor/koni',\n        name: 'koni',\n        component: './Editor/GGEditor/Koni',\n      },\n      { path: '/editor', redirect: '/editor/flow' },\n    ],\n  },\n  {\n    path: '/',\n    redirect: '/dashboard/analysis',\n  },\n  {\n    component: '404',\n  },\n];\n"
  },
  {
    "path": "src/i18n/formatRoute.js",
    "content": "const fs = require('fs')\nconst path = require('path')\nconst { getFile } = require('./utils')\n\nmodule.exports = (localeMap, prettierCode) => {\n  const base = `${process.cwd()}/config/`\n  const file = getFile({\n    type: 'javascript',\n    fileNameWithoutExt: 'routes',\n    base\n  })\n  if (!file) return\n  const content = fs.readFileSync(file.absolutePath, 'utf-8')\n  const formatContent = content.replace('export default', 'module.exports =')\n  fs.writeFileSync('./routes.js', formatContent)\n  const routes = require(path.join(process.cwd(), '/routes.js'))\n\n  const loopFn = (routes, parentName) => {\n    routes.forEach(route => {\n      let key = parentName\n      if (route.name) {\n        key = `${parentName}.${route.name}`\n        route.name = localeMap[key]\n      }\n      if (route.routes) {\n        loopFn(route.routes, key)\n      }\n    })\n  }\n  loopFn(routes, 'menu')\n  const result = prettierCode(`export default ${JSON.stringify(routes)}`, file.absolutePath)\n  fs.writeFileSync(file.absolutePath, result)\n  fs.unlinkSync(path.join(process.cwd(), '/routes.js'))\n}\n\n"
  },
  {
    "path": "src/i18n/getLocalFileList.js",
    "content": "const groupBy = require('lodash.groupby');\nconst fs = require('fs');\nconst { winPath } = require('umi-utils');\nconst glob = require('glob');\nconst { join, basename } = require('path');\nconst tsImport = require('node-import-ts');\n\nfunction getLocaleFileList(absSrcPath, absPagesPath, singular) {\n  const localeFileMath = /^([a-z]{2})-([A-Z]{2})\\.(js|ts)$/;\n  const localeFolder = singular ? 'locale' : 'locales';\n\n  const localeFiles = glob\n    .sync('*.{ts,js}', {\n      cwd: winPath(join(absSrcPath, localeFolder)),\n    })\n    .map(name => {\n      const fileName = basename(name);\n      const fileInfo = localeFileMath.exec(fileName);\n      return { name: `${fileInfo[1]}-${fileInfo[2]}`, path: join(absSrcPath, localeFolder, name) };\n    })\n    .concat(\n      glob\n        .sync(`**/${localeFolder}/*.{ts,js}`, {\n          cwd: winPath(absPagesPath),\n        })\n        .map(name => join(absPagesPath, name))\n        .filter(p => localeFileMath.test(basename(p)))\n        .map(fullName => {\n          const fileName = basename(fullName);\n          const fileInfo = localeFileMath.exec(fileName);\n          return {\n            name: `${fileInfo[1]}-${fileInfo[2]}`,\n            path: fullName,\n          };\n        }),\n    );\n  const groups = groupBy(localeFiles, 'name');\n  return groups;\n}\n\nmodule.exports = (cwd, locale) => {\n  let absSrcPath = join(cwd, 'src');\n  let absPagesPath = cwd;\n  if (fs.existsSync(join(cwd, 'package.json'))) {\n    absSrcPath = join(cwd, 'src');\n    absPagesPath = join(cwd, 'src/pages');\n  }\n  const arrayList = getLocaleFileList(absSrcPath, absPagesPath)[locale].map(({ path }) =>\n    winPath(path),\n  );\n  const localeMap = arrayList\n    .map(filePath => tsImport(filePath))\n    .reduce(\n      (pre, item) => ({\n        ...pre,\n        ...item,\n      }),\n      {},\n    );\n  return localeMap;\n};\n"
  },
  {
    "path": "src/i18n/index.js",
    "content": "const glob = require('glob');\nconst { join } = require('path');\nconst fs = require('fs');\nconst { winPath } = require('umi-utils');\nconst prettier = require('prettier');\nconst fabric = require('@umijs/fabric');\nconst ora = require('ora');\n\nconst getLocalFileList = require('./getLocalFileList');\nconst removeLocale = require('./removeLocale');\nconst formatRoute = require('./formatRoute')\n\n\nconst spinner = ora();\n\nconst globList = (patternList, options) => {\n  let fileList = [];\n  patternList.forEach(pattern => {\n    fileList = [...fileList, ...glob.sync(pattern, options)];\n  });\n\n  return fileList;\n};\nconst prettierCode = (code, filepath) =>\n  prettier.format(code, {\n    ...fabric.prettier,\n    filepath,\n  });\n\nconst getFileContent = path => fs.readFileSync(winPath(path), 'utf-8');\n\nmodule.exports = ({ cwd, locale = 'zh-CN', write }) => {\n  // 寻找项目下的所有 ts\n  spinner.start('🕵️‍  find js or ts files');\n  const tsFiles = globList(['**/*.tsx', '**/*.ts', '**/*.js', '**/*.jsx'], {\n    cwd,\n    ignore: ['**/*.d.ts', '**/dist/**', '**/public/**', '**/locales/**', '**/node_modules/**'],\n  });\n  spinner.succeed();\n\n  if (!tsFiles || tsFiles.length < 1) {\n    console.log('🎊 No files found');\n    return;\n  }\n\n  spinner.start('📦  load all locale file and build ts ');\n  // 获得 locale 的配置\n  const localeMap = getLocalFileList(cwd, locale);\n  spinner.succeed();\n\n  spinner.start(`✂️  format routes`)\n  formatRoute(localeMap, prettierCode)\n  spinner.succeed();\n\n  tsFiles.forEach(path => {\n    const source = getFileContent(join(cwd, path));\n    if (source.includes('formatMessage') || source.includes('FormattedMessage') || source.includes('SelectLang') || path === 'config/config.ts') {\n      let content = removeLocale(source, localeMap, path);\n      spinner.start(`✂️  remove locale for ${path}.`);\n\n      if (write) {\n        content = prettierCode(content, path);\n        fs.writeFileSync(join(cwd, path), content, 'utf-8');\n        spinner.succeed();\n        return;\n      }\n      spinner.succeed();\n    }\n  });\n};\n"
  },
  {
    "path": "src/i18n/removeLocale.js",
    "content": "const parser = require('@babel/parser');\nconst traverse = require('@babel/traverse');\nconst generate = require('@babel/generator');\nconst t = require('babel-types');\n\n/**\n * 生成代码\n * @param {*} ast\n */\nfunction generateCode(ast) {\n  return generate.default(ast, {}).code;\n}\n\nconst genMessage = ({ id, defaultMessage, values }, localeMap) => {\n  if (id && localeMap[id]) {\n    const message = localeMap[id];\n    if (values) {\n      console.log(`${id} - ${message} 不支持带逻辑的 values`);\n      return defaultMessage || id;\n    }\n    return localeMap[id];\n  }\n  if (defaultMessage) {\n    return defaultMessage;\n  }\n  return id;\n};\n\n/**\n * 替换文件中的 formatMessage\n * @param {*} ast\n * @param {*} localeMap\n */\nconst genAst = (ast, localeMap, filePath) => {\n  traverse.default(ast, {\n    enter(path) {\n      if (filePath === 'config/config.ts') {\n        if (\n          path.isIdentifier({ name: 'locale' }) &&\n          path.container.value.type === 'ObjectExpression'\n        ) {\n          // path.replaceWith(t.Identifier(''))\n          path.parentPath.remove();\n        }\n      }\n\n      if (path.isIdentifier({ name: 'useIntl' })) {\n        if (path.parentPath.parentPath.type === 'VariableDeclarator') {\n          path.parentPath.parentPath.remove();\n        }\n      }\n\n      if (path.isImportDeclaration()) {\n        const { specifiers } = path.node;\n        if (path.node.specifiers) {\n          path.node.specifiers = specifiers.filter(({ imported }) => {\n            if (imported) {\n              return (\n                imported.name !== 'formatMessage' &&\n                imported.name !== 'FormattedMessage' &&\n                imported.name !== 'useIntl' &&\n                imported.name !== 'SelectLang'\n              );\n            }\n            return true;\n          });\n        }\n        if (path.node.source.value === 'umi-plugin-react/locale') {\n          path.remove();\n          return;\n        }\n      }\n      // 替换 formatMessage\n      if (path.isIdentifier({ name: 'formatMessage' })) {\n        const { arguments: formatMessageArguments } = path.container;\n        if (!formatMessageArguments) {\n          if (path.parentPath.parentPath.type === 'JSXAttribute') {\n            path.parentPath.parentPath.remove();\n            return;\n          }\n          if (path.parentPath.type === 'ObjectProperty') {\n            if (path.parentPath.isRemove) {\n              return;\n            }\n            path.parentPath.remove();\n            path.parentPath.isRemove = true;\n            return;\n          }\n          if (\n            path.parentPath.type === 'MemberExpression' &&\n            path.parentPath.container.type === 'CallExpression' &&\n            path.parentPath.container.arguments\n          ) {\n            const { arguments: containerFormatMessageArguments } = path.parentPath.container;\n            const params = {};\n\n            containerFormatMessageArguments.forEach((node) => {\n              node.properties.forEach((property) => {\n                params[property.key.name] = property.value.value;\n              });\n            });\n            const message = genMessage(params, localeMap);\n\n            const container = path.parentPath.parentPath;\n\n            if (message) {\n              container.replaceWith(t.identifier(`'${message}'`));\n            }\n          }\n          return;\n        }\n        const params = {};\n        formatMessageArguments.forEach((node) => {\n          node.properties.forEach((property) => {\n            params[property.key.name] = property.value.value;\n          });\n        });\n\n        const message = genMessage(params, localeMap);\n        let container = path.parentPath;\n\n        // JSXExpressionContainer = {}, 如果是 JSXExpressionContainer 一起删掉\n        if (container.parentPath.type === 'JSXExpressionContainer') {\n          container = path.parentPath.parentPath;\n        }\n        if (message) {\n          // 如果是 <></> 类型不需要加string\n          const isJSXElement = container.parentPath.type === 'JSXElement';\n          if (!isJSXElement) {\n            if (message.includes(\"'\")) {\n              container.replaceWithSourceString(`\"${message}\"`);\n            } else {\n              container.replaceWithSourceString(`'${message}'`);\n            }\n          } else {\n            container.replaceWith(t.identifier(message));\n          }\n        }\n      }\n\n      // 替换 FormattedMessage\n      if (path.isJSXIdentifier({ name: 'FormattedMessage' })) {\n        const { attributes } = path.container;\n        const params = {};\n        attributes.forEach((node) => {\n          if (node.value.value) {\n            params[node.name.name] = node.value.value;\n          } else {\n            params[node.name.name] = node.value.expression;\n          }\n        });\n        const message = genMessage(params, localeMap);\n        let container = path.parentPath.parentPath;\n\n        // 如果是 <></> 类型不需要加string\n        // JSXExpressionContainer = {}\n        if (container.parentPath.type === 'JSXExpressionContainer') {\n          container = container.parentPath;\n        }\n\n        const isJSXElement = container.parentPath.type === 'JSXElement';\n        if (message) {\n          if (isJSXElement) {\n            container.replaceWith(t.identifier(message));\n          } else {\n            container.replaceWithSourceString(`\"${message}\"`);\n          }\n        }\n      }\n      if (path.isJSXIdentifier({ name: 'data-lang' })) {\n        // path.parentPath.parentPath.replaceWith(t.JSXOpeningElement(t.JSXIdentifier('span'), [t.JSXAttribute(t.JSXIdentifier('data-lang-tag'))], true));\n        path.parentPath.parentPath.parentPath.remove();\n      }\n\n      if (path.isJSXIdentifier({ name: 'SelectLang' })) {\n        // path.parentPath.replaceWith(t.JSXOpeningElement(t.JSXIdentifier('span'), [t.JSXAttribute(t.JSXIdentifier('data-lang-tag'))], true));\n        path.parentPath.parentPath.remove();\n      }\n\n      if (path.node.source && path.node.source.value === 'umi' && !path.node.specifiers.length) {\n        path.remove();\n        return;\n      }\n    },\n    CallExpression(p) {\n      if (p.container && p.container.property && p.container.property.name === 'formatMessage') {\n        const parent = p.parentPath;\n        const { arguments: formatMessageArguments } = parent.container;\n        // eslint-disable-next-line prefer-rest-params\n        if (arguments && arguments.length) {\n          const params = {};\n          formatMessageArguments.forEach((node) => {\n            node.properties.forEach((property) => {\n              params[property.key.name] = property.value.value;\n            });\n          });\n          const message = genMessage(params, localeMap);\n          parent.parentPath.replaceWith(t.identifier(`'${message}'`));\n        }\n      }\n    },\n  });\n};\n\nmodule.exports = (code, localeMap, filePath) => {\n  const ast = parser.parse(code, {\n    sourceType: 'module',\n    plugins: ['jsx', 'typescript', 'dynamicImport', 'classProperties', 'decorators-legacy'],\n  });\n  genAst(ast, localeMap, filePath);\n  return generateCode(ast);\n};\n"
  },
  {
    "path": "src/i18n/transform.js",
    "content": "const babel = require('@babel/core');\n\nmodule.exports = filePath => {\n  const { code } = babel.transformFileSync(filePath, {\n    presets: [\n      [\n        require.resolve('@babel/env'),\n        {\n          targets: {\n            node: true,\n          },\n        },\n      ],\n      require.resolve('@babel/preset-typescript'),\n    ],\n  });\n  return code;\n};\n"
  },
  {
    "path": "src/i18n/utils.js",
    "content": "\nconst fs = require('fs')\nconst path = require('path')\n\nfunction winPath (path) {\n  const isExtendedLengthPath = /^\\\\\\\\\\?\\\\/.test(path);\n  if (isExtendedLengthPath) {\n    return path;\n  }\n\n  return path.replace(/\\\\/g, '/');\n}\n  \n/**\n * @description\n * - `'javascript'`: try to match the file with extname `.{ts(x)|js(x)}`\n * - `'css'`: try to match the file with extname `.{less|sass|scss|stylus|css}`\n */\n\nconst extsMap = {\n  javascript: ['.ts', '.tsx', '.js', '.jsx'],\n  css: ['.less', '.sass', '.scss', '.stylus', '.css'],\n}\n\n/**\n * Try to match the exact extname of the file in a specific directory.\n * @returns\n * - matched: `{ path: string; filename: string }`\n * - otherwise: `null`\n */\nfunction getFile(opts) {\n  const exts = extsMap[opts.type];\n  for (const ext of exts) {\n    const filename = `${opts.fileNameWithoutExt}${ext}`;\n    const absolutePath = winPath(path.join(opts.base, filename));\n    if (fs.existsSync(absolutePath)) {\n      return {\n        absolutePath,\n        filename,\n      };\n    }\n  }\n  return null;\n}\n\nmodule.exports = {\n  getFile,\n  winPath\n}\n"
  },
  {
    "path": "src/pro-components-codemod/PACKAGE_CONSTANT.js",
    "content": "const CHILD_PACKAGES = [\n  '@ant-design/pro-card',\n  '@ant-design/pro-descriptions',\n  '@ant-design/pro-field',\n  '@ant-design/pro-form',\n  '@ant-design/pro-layout',\n  '@ant-design/pro-list',\n  '@ant-design/pro-provider',\n  '@ant-design/pro-skeleton',\n  '@ant-design/pro-table',\n  '@ant-design/pro-utils',\n];\n\nconst PRO_PACKAGE = '@ant-design/pro-components';\n\nmodule.exports = {\n  CHILD_PACKAGES,\n  PRO_PACKAGE,\n};\n"
  },
  {
    "path": "src/pro-components-codemod/index.js",
    "content": "const updateDependency = require('./updateDependency');\nconst updateImports = require('./updateImports');\nconst { listVersions } = require('./updateDependency');\n\nmodule.exports = async ({\n  cwd,\n  // 是否自动更新 package.json 中的 dependencies 的添加/删除，默认开启\n  writePkg = true,\n  // 是否开启 cleanup 模式，默认开启\n  cleanup = true,\n  // 默认转换 src 目录下的文件\n  path = 'src',\n  // 指定 pro-components 版本（非交互式模式）\n  version,\n  // 列出可用版本\n  listVersions: shouldListVersions,\n}) => {\n  // 仅列出可用版本\n  if (shouldListVersions) {\n    await listVersions();\n    process.exit(0);\n  }\n\n  // console.log({cwd, writePkg, cleanup, path})\n  // 1. update package.json\n  if (writePkg) {\n    // TODO: 是否需要考虑兼容 monorepo 的 package.json，一般都是安装在根目录下的（要兼容的话，可设置 depth 处理下）\n    await updateDependency({\n      cwd,\n      version,\n    });\n  }\n\n  // 2. update import\n  await updateImports({ cwd, cleanup, path });\n\n  process.exit();\n};\n"
  },
  {
    "path": "src/pro-components-codemod/pkgApi.js",
    "content": "const { join } = require('path');\nconst { existsSync, readFileSync, writeFileSync } = require('fs');\nconst chalk = require('chalk');\nconst execa = require('execa');\nconst ora = require('ora');\n\nconst spinner = ora();\n\nconst LOCKFILES = {\n  'pnpm-lock.yaml': 'pnpm',\n  'yarn.lock': 'yarn',\n  'package-lock.json': 'npm',\n  'npm-shrinkwrap.json': 'npm',\n};\n\n// TODO: 使用 zx 安装依赖\nconst NPM_CLIENT_COMMANDS = {\n  npm: {\n    install: 'npm install',\n    uninstall: 'npm uninstall',\n  },\n  pnpm: {\n    install: 'pnpm add',\n    uninstall: 'pnpm remove',\n  },\n  yarn: {\n    install: 'yarn add',\n    uninstall: 'yarn remove',\n  },\n};\n\n/**\n * 操作 package.json 的工具类\n */\nclass PkgApi {\n  constructor({ cwd } = {}) {\n    this.cwd = cwd;\n    this.pkgPath = join(cwd, 'package.json');\n\n    if (!existsSync(this.pkgPath)) {\n      console.error(chalk.red('🤔 package.json not found'));\n      process.exit(1);\n    }\n\n    this.npmClient = this.getNpmClient();\n    this.commands = NPM_CLIENT_COMMANDS[this.npmClient];\n  }\n\n  execCmd({ command, dependencies = [], isDev }) {\n    execa.commandSync([command, ...dependencies, isDev ? '-D' : null].filter(Boolean).join(' '), {\n      encoding: 'utf8',\n      cwd: this.cwd,\n      env: {\n        ...process.env,\n      },\n      stderr: 'pipe',\n      stdout: 'pipe',\n    });\n  }\n\n  async removeDependency(dependency) {\n    const dependencies = typeof dependency === 'string' ? [dependency] : [...dependency];\n    const pkg = this.getPkg();\n\n    if (pkg.dependencies) {\n      const removeDependencies = {};\n\n      for (const dep in pkg.dependencies) {\n        if (dependencies.includes(dep)) {\n          removeDependencies[dep] = pkg.dependencies[dep];\n        }\n      }\n\n      const removeDependencyNames = Object.keys(removeDependencies);\n\n      if (removeDependencyNames.length) {\n        spinner.start(`🗑  ${this.commands['uninstall']} ${chalk.blueBright('dependencies')} `);\n        this.execCmd({\n          command: this.commands['uninstall'],\n          dependencies: removeDependencyNames,\n        });\n        spinner.succeed();\n\n        console.log(\n          `\\n${removeDependencyNames\n            .map((name) => `${chalk.red('-')} ${name} ${chalk.gray(removeDependencies[name])}`)\n            .join('\\n')}\\n`,\n        );\n      }\n    }\n  }\n\n  async addDependency(name, version) {\n    const command = this.commands['install'];\n\n    spinner.start(`🚚  ${command} ${chalk.blueBright('dependencies')} `);\n    this.execCmd({\n      command,\n      dependencies: [`${name}${version ? '@^' + version : ''}`],\n    });\n    spinner.succeed();\n\n    console.log(`\\n${chalk.green('+')} ${name} ${chalk.gray(version)} \\n`);\n  }\n\n  getPkg() {\n    return JSON.parse(readFileSync(this.pkgPath, 'utf-8'));\n  }\n\n  writePkg(pkg) {\n    writeFileSync(this.pkgPath, JSON.stringify(pkg, null, 2), 'utf-8');\n  }\n\n  getNpmClient() {\n    const pkg = this.getPkg();\n    let client = null;\n\n    // 1. 优先从 packageManager 获取 npm client\n    if (pkg.packageManager) {\n      const [name] = pkg.packageManager.split('@');\n\n      if (name in NPM_CLIENT_COMMANDS) {\n        client = name;\n      }\n\n      if (!client) {\n        console.error(chalk.red(`Unknown packageManager: ${pkg.packageManager}`));\n        process.exit(1);\n      }\n    }\n\n    // 2. 通过 lockfiles 获取 npm client\n    if (!client) {\n      const lockfile = Object.keys(LOCKFILES).find((lockFilePath) =>\n        existsSync(join(this.cwd, lockFilePath)),\n      );\n\n      if (lockfile) {\n        client = LOCKFILES[lockfile];\n      }\n    }\n\n    return client || 'npm';\n  }\n}\n\nmodule.exports = PkgApi;\n"
  },
  {
    "path": "src/pro-components-codemod/transform.js",
    "content": "const { transformFromAstSync, parseSync } = require('@babel/core');\nconst { parse, print } = require('recast');\nconst { CHILD_PACKAGES, PRO_PACKAGE } = require('./PACKAGE_CONSTANT');\n\nconst CommonParserOpts = {\n  sourceType: 'module',\n  plugins: ['jsx', 'typescript'],\n};\n\nconst UpdateImportPlugin = (api, options) => {\n  const { template, types } = api;\n  const { cleanup } = options;\n\n  const generateImportDeclarationAst = (localName) =>\n    template.ast(`import { ${localName} } from '${PRO_PACKAGE}'`);\n\n  return {\n    visitor: {\n      Program: {\n        enter(path) {\n          // 保存 comments ，traverse 处理完再保存到第一个 astNode 上\n          const firstNode = path.node.body[0];\n          const { leadingComments = [], innerComments = [], trailingComments = [] } = firstNode;\n\n          /**\n           * cleanup：提供两种 import 模式\n           *  1. 全部合并到一条 import 语句导入\n           *     import { ProTable, ProList} from '@ant-design/pro-components'\n           *  2. 只更新子包的名称为 @ant-design/pro-components，保留旧项目的 import 方式\n           *    import { ProTable } from '@ant-design/pro-components'\n           *    import { ProList } from '@ant-design/pro-components'\n           */\n          if (cleanup) {\n            const specifierSet = new Set();\n            const namespaceSpecifierSet = new Set();\n            // type import\n            const typeSpecifierSet = new Set();\n            const typeNamespaceSpecifierSet = new Set();\n            path.traverse({\n              ImportDeclaration(path) {\n                const { node } = path;\n\n                if (!node) return;\n\n                const { importKind, source } = node;\n\n                if (CHILD_PACKAGES.includes(source.value)) {\n                  node.specifiers.forEach((spec) => {\n                    // specifiers: ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier\n                    const localName = spec.local.name;\n                    const importType = importKind === 'type' || spec.importKind == 'type';\n\n                    if (types.isImportSpecifier(spec)) {\n                      const importedName = spec.imported.name;\n                      const _localName =\n                        importedName === localName ? localName : `${importedName} as ${localName}`;\n\n                      importType ? typeSpecifierSet.add(_localName) : specifierSet.add(_localName);\n                    } else if (types.isImportDefaultSpecifier(spec)) {\n                      importType ? typeSpecifierSet.add(localName) : specifierSet.add(localName);\n                    } else if (types.isImportNamespaceSpecifier(spec)) {\n                      importType\n                        ? typeNamespaceSpecifierSet.add(localName)\n                        : namespaceSpecifierSet.add(localName);\n                    }\n                  });\n\n                  path.remove();\n                }\n              },\n            });\n\n            if (namespaceSpecifierSet.size) {\n              for (const namespaceSpecifier of namespaceSpecifierSet) {\n                const ast = template.ast(`import * as ${namespaceSpecifier} from '${PRO_PACKAGE}'`);\n                path.node.body.unshift(ast);\n              }\n            }\n\n            if (specifierSet.size) {\n              path.node.body.unshift(\n                template.ast(\n                  `import { ${Array.from(specifierSet).join(', ')} } from '${PRO_PACKAGE}'`,\n                ),\n              );\n            }\n\n            if (typeNamespaceSpecifierSet.size) {\n              for (const namespaceSpecifier of typeNamespaceSpecifierSet) {\n                path.node.body.unshift(\n                  template.ast(\n                    `import type * as ${namespaceSpecifier} from '${PRO_PACKAGE}'`,\n                    CommonParserOpts,\n                  ),\n                );\n              }\n            }\n\n            if (typeSpecifierSet.size) {\n              path.node.body.unshift(\n                template.ast(\n                  `import type { ${Array.from(typeSpecifierSet).join(\n                    ', ',\n                  )} } from '${PRO_PACKAGE}'`,\n                  CommonParserOpts,\n                ),\n              );\n            }\n          } else {\n            path.traverse({\n              ImportDeclaration(path) {\n                const { node } = path;\n                if (!node) return;\n\n                const { source } = node;\n\n                if (CHILD_PACKAGES.includes(source.value)) {\n                  path.node.source.value = PRO_PACKAGE;\n\n                  const { specifiers } = node;\n                  const hasImportNamespaceSpecifier = specifiers.some((spec) =>\n                    types.isImportNamespaceSpecifier(spec),\n                  );\n\n                  if (hasImportNamespaceSpecifier) {\n                    // case: default import with namespace import\n                    if (specifiers.length > 1) {\n                      const localName = specifiers[0].local.name;\n                      path.get(`specifiers.0`).remove();\n                      path.insertAfter(generateImportDeclarationAst(localName));\n                    }\n                  } else {\n                    specifiers.forEach((spec, index) => {\n                      const specPath = path.get(`specifiers.${index}`);\n                      // replace ImportDefaultSpecifier with importSpecifier\n                      if (types.isImportDefaultSpecifier(spec)) {\n                        specPath.replaceWith(types.importSpecifier(spec.local, spec.local));\n                      }\n                    });\n                  }\n                }\n              },\n            });\n          }\n\n          // 保留文件开头 leadingComments 的位置\n          const firstNode2 = path.node.body[0];\n          if (firstNode2 !== firstNode) {\n            firstNode2.comments = [...leadingComments];\n            firstNode.comments = [...innerComments, ...trailingComments];\n          }\n        },\n      },\n    },\n  };\n};\n\nconst transformImportProComponents = (sourceCode, options = {}) => {\n  // 通过 recast 处理 ast，保证输出文件的代码风格和输入前保持一致\n  const ast = parse(sourceCode, {\n    parser: {\n      parse: (source) => {\n        const ast = parseSync(source, {\n          parserOpts: {\n            sourceType: 'module',\n            plugins: ['jsx', 'typescript'],\n            // recast uses this\n            tokens: true,\n          },\n        });\n\n        return ast;\n      },\n    },\n  });\n\n  const { ast: transformedAST } = transformFromAstSync(ast, sourceCode, {\n    plugins: [[UpdateImportPlugin, options]],\n    // allowing it to preserve formatting\n    cloneInputAst: false,\n    code: false,\n    ast: true,\n  });\n\n  const { code } = print(transformedAST);\n\n  return code;\n};\n\nmodule.exports = transformImportProComponents;\n"
  },
  {
    "path": "src/pro-components-codemod/updateDependency.js",
    "content": "const PkgApi = require('./pkgApi');\nconst fetch = require('node-fetch');\nconst { CHILD_PACKAGES, PRO_PACKAGE } = require('./PACKAGE_CONSTANT');\nconst inquirer = require('inquirer');\nconst ora = require('ora');\nconst chalk = require('chalk');\nconst semver = require('semver');\nconst getNpmRegistry = require('../utils/getNpmRegistry');\n\nconst spinner = ora();\n\nconst fetchPkgVersion = async () => {\n  spinner.start(`🚚  Start fetch ${chalk.green(PRO_PACKAGE)} version... `);\n  const registryUrl = await getNpmRegistry();\n  const data = await fetch(`${registryUrl}/${PRO_PACKAGE}`);\n  spinner.succeed();\n\n  if (data.status !== 200) {\n    console.error(chalk.red('🤔 download error'));\n    process.exit(1);\n  }\n\n  const {\n    versions,\n    'dist-tags': { latest },\n  } = await data.json();\n  \n  const versionsList = Object.keys(versions);\n  const maxMajor = semver.parse(latest).major;\n  const uniqueVersions = [];\n\n  for (const major in new Array(maxMajor + 1).fill(null)) {\n    // 只保存每个大版本的最新版本\n    const latestVersion = semver.maxSatisfying(versionsList, major);\n    if (latestVersion) {\n      uniqueVersions.push(latestVersion);\n    }\n  }\n\n  return uniqueVersions;\n};\n\nconst listVersions = async () => {\n  const versions = await fetchPkgVersion();\n  console.log(`\\nAvailable versions for ${chalk.green(PRO_PACKAGE)}:\\n`);\n  versions.forEach((v, i) => {\n    const isLatest = i === versions.length - 1;\n    console.log(`  ${chalk.cyan(v)}${isLatest ? chalk.gray(' (latest)') : ''}`);\n  });\n  console.log(`\\nUsage: pro pro-components-codemod --version <version>\\n`);\n};\n\nconst updateDependency = async ({ cwd, version: specifiedVersion }) => {\n  const pkgApi = new PkgApi({\n    cwd,\n  });\n  // remove child dependencies (e.g., @ant-design/pro-table, @ant-design/pro-layout)\n  await pkgApi.removeDependency(CHILD_PACKAGES);\n\n  // 支持非交互式模式：通过 --version 参数指定版本\n  let version;\n  if (specifiedVersion) {\n    if (specifiedVersion === 'latest') {\n      const versions = await fetchPkgVersion();\n      version = versions[versions.length - 1]; // 最新版本\n    } else {\n      version = specifiedVersion;\n    }\n    console.log(`📦  Using ${chalk.green(PRO_PACKAGE)}@${chalk.cyan(version)}`);\n  } else {\n    // select @ant-design/pro-components version\n    const versions = await fetchPkgVersion();\n    const result = await inquirer.prompt([\n      {\n        name: 'version',\n        type: 'list',\n        message: `🐂  请选择 ${chalk.green(PRO_PACKAGE)} 的版本: `,\n        choices: versions,\n      },\n    ]);\n    version = result.version;\n  }\n\n  await pkgApi.addDependency(PRO_PACKAGE, version);\n};\n\nmodule.exports = updateDependency;\nmodule.exports.listVersions = listVersions;\n"
  },
  {
    "path": "src/pro-components-codemod/updateImports.js",
    "content": "const glob = require('glob');\nconst ora = require('ora');\nconst { winPath } = require('umi-utils');\nconst { join, relative } = require('path');\nconst chalk = require('chalk');\nconst { readFileSync, writeFileSync } = require('fs');\nconst transformImportProComponents = require('./transform');\nconst { CHILD_PACKAGES, PRO_PACKAGE } = require('./PACKAGE_CONSTANT');\n\nconst spinner = ora();\n\nconst getFileList = (cwd, path) => {\n  const _cwd = winPath(join(cwd, path));\n\n  const files = glob.sync('**/*.{ts,tsx,js,jsx}', {\n    cwd: _cwd,\n    ignore: ['**/node_modules/**', '**/dist/**', '**/public/**'],\n  });\n\n  return files.map((filePath) => join(_cwd, filePath));\n};\n\nmodule.exports = async ({ cwd, path, cleanup }) => {\n  if (typeof path !== 'string') {\n    console.log(`🙈 ${chalk.yellow('Please input a string type of path')} `);\n    process.exit(1);\n  }\n\n  spinner.start('🕵️‍  Find files that need updating');\n  const files = getFileList(cwd, path);\n  spinner.succeed();\n\n  if (!files || files.length === 0) {\n    console.log(`🎊 ${chalk.red('No files found')}`);\n    process.exit(1);\n  }\n\n  // 记录转换文件的个数\n  let total = 0;\n\n  spinner.start(`🚀  Start update ${chalk.green(PRO_PACKAGE)} import `);\n  files.forEach((filePath) => {\n    const source = readFileSync(filePath, 'utf-8');\n\n    if (source && CHILD_PACKAGES.some((pkgName) => source.includes(pkgName))) {\n      total += 1;\n      console.log(chalk.green(`${total == 1 ? '\\n' : ''}[${total}] Transform ${relative(cwd, filePath)}`));\n      const code = transformImportProComponents(source, {\n        cleanup,\n      });\n\n      writeFileSync(filePath, code, 'utf-8');\n    }\n  });\n\n  spinner.succeed();\n\n  console.log(`\n    👌  ${chalk.green.bold(total)} files transform success\n  `);\n};\n"
  },
  {
    "path": "src/screenshot/diff.js",
    "content": "const BlinkDiff = require('blink-diff');\n\nmodule.exports = (imageA, imageBPath, imageOutputPath) =>\n  new Promise((resolve, reject) => {\n    const diff = new BlinkDiff({\n      imageA, // Use file-path\n      imageBPath,\n      thresholdType: BlinkDiff.THRESHOLD_PERCENT,\n      threshold: 0.05, // 1% threshold\n      imageOutputPath,\n      hideShift: true,\n    });\n\n    diff.run((error, result) => {\n      if (error) {\n        reject(error);\n      } else {\n        resolve(diff.hasPassed(result.code));\n      }\n    });\n  });\n"
  },
  {
    "path": "src/screenshot/dumi.js",
    "content": "const { spawn } = require('child_process');\nconst { join } = require('path');\nconst fs = require('fs');\nconst chalk = require('chalk');\nconst PNGImage = require('pngjs-image');\nconst { kill } = require('cross-port-killer');\nconst ora = require('ora');\n\nconst getBrowser = require('./getBrowser');\n\nconst portAvailable = require('./portAvailable');\nconst diffPng = require('./diff');\n\nconst spinner = ora();\n\nconst env = Object.create(process.env);\nenv.BROWSER = 'none';\nenv.PORT = process.env.PORT || '8000';\nenv.TEST = true;\nenv.COMPRESS = 'none';\nenv.PROGRESS = 'none';\nenv.BLOCK_PAGES_LAYOUT = 'blankLayout';\n\nlet browser;\n\nlet diffFile = [];\n\n/**\n * 启动区块服务\n */\nconst startServer = async () => {\n  let once = false;\n  return new Promise(resolve => {\n    const server = spawn(/^win/.test(process.platform) ? 'npm.cmd' : 'npm', ['run', 'start'], {\n      env,\n    });\n    server.stdout.on('data', data => {\n      // hack code , wait umi\n      if (!once && data.toString().indexOf('Compiled successfully') >= 0) {\n        once = true;\n        resolve(server);\n      }\n    });\n    server.on('exit', () => {\n      kill(env.PORT || 8000);\n    });\n  });\n};\n\nconst autoScroll = page =>\n  page.evaluate(\n    () =>\n      new Promise(resolve => {\n        let totalHeight = 0;\n        const distance = 100;\n        const timer = setInterval(() => {\n          const { scrollHeight } = document.body;\n          window.scrollBy(0, distance);\n          totalHeight += distance;\n          if (totalHeight >= scrollHeight) {\n            clearInterval(timer);\n            resolve();\n          }\n        }, 100);\n      }),\n  );\n\nconst setFontFamily = page => {\n  page.evaluate(\n    () =>\n      new Promise(resolve => {\n        const link = document.createElement('style');\n        link.href = 'https://fonts.googleapis.com/css?family=Space+Mono&display=swap';\n        link.rel = 'stylesheet';\n        const style = document.createElement('style');\n        const textNode = document.createTextNode(`\n          *{\n            font-family: 'Space Mono', monospace !important; \n          }\n        `);\n        style.appendChild(textNode);\n        link.onload = () => {\n          resolve();\n        };\n        document.head.appendChild(link);\n        document.head.appendChild(style);\n      }),\n  );\n};\n\nconst readPng = path =>\n  new Promise((resolve, reject) => {\n    PNGImage.readImage(path, (error, image) => {\n      if (error) {\n        reject(error);\n      }\n      resolve(image);\n    });\n  });\n\nconst screenshot = async ({ page, routesList, diff, mobile, out }) => {\n  try {\n    const isAvailable = await portAvailable(8000);\n    if (!isAvailable) {\n      kill(env.PORT || 8000);\n    }\n  } catch (error) {\n    console.log(error);\n  }\n  let outPath = out || `${process.cwd()}/screenshot/`;\n\n  if (!fs.existsSync(outPath)) {\n    spinner.start(`🐆  create outPath`);\n    await fs.mkdirSync(outPath);\n    spinner.succeed();\n  }\n  spinner.start(`🚀  start server`);\n  const server = await startServer();\n  spinner.succeed();\n  // /~demos/card-demo\n  const loopGetImage = async index => {\n    try {\n      const route = routesList[index];\n      await page.goto(`http://127.0.0.1:${env.PORT}/~demos/${route}`);\n\n      await page.setViewport({\n        width: mobile ? 375 : 1440,\n        height: mobile ? 667 : 800,\n      });\n\n      spinner.start(`💄  set style (${route})`);\n      await autoScroll(page);\n      await setFontFamily(page);\n      spinner.succeed();\n\n      const imagePath = join(outPath, `${route}.png`);\n      let png = null;\n      // if diff read file\n      if (diff) {\n        try {\n          png = await readPng(imagePath);\n        } catch (error) {\n          diff = false;\n        }\n      }\n\n      spinner.start(`📷  snapshot block image  (${route})`);\n\n      await page.screenshot({\n        path: imagePath,\n        fullPage: true,\n      });\n      spinner.succeed();\n\n      if (diff) {\n        const diffPngPath = join(outPath, `${route}-diff.png`);\n        spinner.start(`👀  diff ${imagePath}`);\n        const isDiff = await diffPng(png, imagePath, diffPngPath);\n        if (!isDiff) {\n          diffFile.push(path);\n        }\n        spinner.succeed();\n      }\n\n      if (routesList.length > index && routesList[index + 1]) {\n        return loopGetImage(index + 1);\n      }\n    } catch (error) {\n      console.log(error);\n    }\n    return Promise.resolve(true);\n  };\n  await loopGetImage(0);\n  server.kill();\n};\n\nconst openBrowser = async () => {\n  browser = await getBrowser();\n  const page = await browser.newPage();\n  return page;\n};\n\n/**\n * 取得所有区块\n */\nconst getAllFile = async (cwd, filePath) => {\n  let assetsJson;\n  if (filePath) {\n    assetsJson = filePath;\n  } else if (fs.existsSync(join(cwd, 'assets.json'))) {\n    assetsJson = join(cwd, 'assets.json')\n  } else {\n    console.error('no find the dumi assets json.');\n    console.log('Please try to execute: dumi assets!');\n    return;\n  }\n  const data = JSON.parse(fs.readFileSync(assetsJson, 'utf-8') || '[]')\n  let examples = [];\n  if (data && data.assets && data.assets.examples) {\n    examples = data.assets.examples;\n  }\n  return (examples).map(demo => demo.identifier);\n};\n\nmodule.exports = async ({ cwd, diff, path, mobile, out }) => {\n  diffFile = [];\n  spinner.start('🔍  Get block');\n  const routesList = await getAllFile(cwd, path);\n  spinner.succeed();\n\n  spinner.start('🌏  Start puppeteer');\n  const page = await openBrowser();\n  spinner.succeed();\n  await screenshot({\n    page,\n    routesList,\n    diff,\n    mobile,\n    out\n  });\n\n  if (diffFile.length > 0) {\n    console.log(`End of diff, ${diffFile.length} failed.`);\n    console.log(chalk.red(diffFile.join('\\n')));\n  }\n\n  browser.close();\n  return diffFile;\n};\n"
  },
  {
    "path": "src/screenshot/getBrowser.js",
    "content": "\n/* eslint-disable global-require */\n/* eslint-disable import/no-extraneous-dependencies */\nconst findChrome = require('carlo/lib/find_chrome');\n\nconst getBrowser = async () => {\n  try {\n    // eslint-disable-next-line import/no-unresolved\n    const puppeteer = require('puppeteer');\n    const browser = await puppeteer.launch({\n      args: [\n        '--disable-gpu',\n        '--disable-dev-shm-usage',\n        '--no-first-run',\n        '--no-zygote',\n        '--no-sandbox',\n      ],\n    });\n    return browser;\n  } catch (error) {\n    // console.log(error)\n  }\n\n  try {\n    // eslint-disable-next-line import/no-unresolved\n    const puppeteer = require('puppeteer-core');\n    const findChromePath = await findChrome({});\n    const { executablePath } = findChromePath;\n    const browser = await puppeteer.launch({\n      executablePath,\n      args: [\n        '--disable-gpu',\n        '--disable-dev-shm-usage',\n        '--no-first-run',\n        '--no-zygote',\n        '--no-sandbox',\n      ],\n    });\n    return browser;\n  } catch (error) {\n    console.log('🧲 no find chrome');\n  }\n  throw new Error('no find puppeteer');\n};\n\nmodule.exports = getBrowser;\n"
  },
  {
    "path": "src/screenshot/index.js",
    "content": "const { spawn } = require('child_process');\nconst { join } = require('path');\nconst fs = require('fs');\nconst chalk = require('chalk');\nconst getNpmRegistry = require('../utils/getNpmRegistry');\nconst execa = require('execa');\nconst PNGImage = require('pngjs-image');\nconst { kill } = require('cross-port-killer');\nconst ora = require('ora');\n\nconst getBrowser = require('./getBrowser');\n\nconst portAvailable = require('./portAvailable');\nconst diffPng = require('./diff');\n\nconst spinner = ora();\n\nconst env = Object.create(process.env);\nenv.BROWSER = 'none';\nenv.PORT = process.env.PORT || '2144';\nenv.TEST = true;\nenv.COMPRESS = 'none';\nenv.PROGRESS = 'none';\nenv.BLOCK_PAGES_LAYOUT = 'blankLayout';\n\nlet browser;\n\nlet diffFile = [];\n\n/**\n * 启动区块服务\n * @param {string} path\n */\nconst startServer = async path => {\n  let once = false;\n  return new Promise(resolve => {\n    env.PAGES_PATH = `${path}/src`;\n\n    const server = spawn(/^win/.test(process.platform) ? 'npm.cmd' : 'npm', ['run', 'start'], {\n      env,\n    });\n    server.stdout.on('data', data => {\n      // hack code , wait umi\n      if (!once && data.toString().indexOf('Compiled successfully') >= 0) {\n        once = true;\n        resolve(server);\n      }\n    });\n    server.on('exit', () => {\n      kill(env.PORT || 8000);\n    });\n  });\n};\n\nconst autoScroll = page =>\n  page.evaluate(\n    () =>\n      new Promise(resolve => {\n        let totalHeight = 0;\n        const distance = 100;\n        const timer = setInterval(() => {\n          const { scrollHeight } = document.body;\n          window.scrollBy(0, distance);\n          totalHeight += distance;\n          if (totalHeight >= scrollHeight) {\n            clearInterval(timer);\n            resolve();\n          }\n        }, 100);\n      }),\n  );\n\nconst setFontFamily = page => {\n  page.evaluate(\n    () =>\n      new Promise(resolve => {\n        const link = document.createElement('style');\n        link.href = 'https://fonts.googleapis.com/css?family=Space+Mono&display=swap';\n        link.rel = 'stylesheet';\n        const style = document.createElement('style');\n        const textNode = document.createTextNode(`\n          *{\n            font-family: 'Space Mono', monospace !important; \n          }\n        `);\n        style.appendChild(textNode);\n        link.onload = () => {\n          resolve();\n        };\n        document.head.appendChild(link);\n        document.head.appendChild(style);\n      }),\n  );\n};\n\nconst readPng = path =>\n  new Promise((resolve, reject) => {\n    PNGImage.readImage(path, (error, image) => {\n      if (error) {\n        reject(error);\n      }\n      resolve(image);\n    });\n  });\n\nconst screenshot = async ({ page, path, diff, index, total, mobile }) => {\n  try {\n    const isAvailable = await portAvailable(8000);\n    if (!isAvailable) {\n      kill(env.PORT || 8000);\n    }\n  } catch (error) {\n    console.log(error);\n  }\n\n  spinner.start(`🚀  start server  (${index + 1}/${total})`);\n  const server = await startServer(path);\n  spinner.succeed();\n\n  await page.goto(`http://127.0.0.1:${env.PORT}`);\n\n  await page.setViewport({\n    width: mobile ? 375 : 1440,\n    height: mobile ? 667 : 800,\n  });\n\n  spinner.start(`💄  set style (${index + 1}/${total})`);\n  await autoScroll(page);\n  await setFontFamily(page);\n  spinner.succeed();\n\n  const imagePath = join(path, 'snapshot.png');\n  let png = null;\n  // if diff read file\n  if (diff) {\n    png = await readPng(imagePath);\n  }\n\n  spinner.start(`📷  snapshot block image  (${index + 1}/${total})`);\n\n  await page.screenshot({\n    path: imagePath,\n    fullPage: true,\n  });\n  spinner.succeed();\n\n  if (diff) {\n    const diffPngPath = join(path, 'diff.png');\n    spinner.start(`👀  diff ${imagePath}`);\n    const isDiff = await diffPng(png, imagePath, diffPngPath);\n    if (!isDiff) {\n      diffFile.push(path);\n    }\n    spinner.succeed();\n  }\n  server.kill();\n};\n\nconst openBrowser = async () => {\n  browser = await getBrowser();\n  const page = await browser.newPage();\n  return page;\n};\n\n/**\n * 取得所有区块\n */\nconst getAllFile = async (cwd, filePath) => {\n  const files = fs.readdirSync(cwd);\n\n  return files.filter(path => {\n    const itemPath = join(cwd, path);\n    const stat = fs.statSync(itemPath);\n    if (path.includes('.') || path.includes('_') || path.includes('node_modules')) {\n      return false;\n    }\n    // 支持单独的 文件夹\n    if (filePath && !filePath.toLowerCase().includes(path.toLowerCase())) {\n      return false;\n    }\n    if (stat.isDirectory()) {\n      const havePackage = fs.existsSync(join(itemPath, 'package.json'));\n\n      if (havePackage) {\n        return true;\n      }\n    }\n    return false;\n  });\n};\n\nmodule.exports = async ({ cwd, diff, path, mobile }) => {\n  diffFile = [];\n  spinner.start('🔍  Get block');\n  const dirList = await getAllFile(cwd, path);\n  spinner.succeed();\n\n  const total = dirList.length;\n  spinner.start('🌏  Start puppeteer');\n  const registry = await getNpmRegistry();\n  const page = await openBrowser();\n  spinner.succeed();\n\n  const loopGetImage = async index => {\n    try {\n      spinner.start(`📦  Install ${dirList[index]} dependencies`);\n      await execa('yarn', ['install', `--registry=${registry}`, '--force'], {\n        cwd: join(cwd, `./${dirList[index]}`),\n      });\n      spinner.succeed();\n\n      await screenshot({\n        page,\n        path: dirList[index],\n        diff,\n        index,\n        total,\n        mobile,\n      });\n\n      if (dirList.length > index && dirList[index + 1]) {\n        return loopGetImage(index + 1);\n      }\n    } catch (error) {\n      console.log(error);\n    }\n    return Promise.resolve(true);\n  };\n  await loopGetImage(0);\n\n  if (diffFile.length > 0) {\n    console.log(`End of diff, ${diffFile.length} failed.`);\n    console.log(chalk.red(diffFile.join('\\n')));\n  }\n\n  browser.close();\n  return diffFile;\n};\n"
  },
  {
    "path": "src/screenshot/portAvailable.js",
    "content": "const net = require('net');\n\n/**\n * 判断端口是否空闲\n * 如果返回true，端口空闲\n * 返回false 端口被占用\n */\nmodule.exports = async port =>\n  new Promise(resolve => {\n    // 创建服务并监听该端口\n    const server = net.createServer().listen(port);\n    server.on('listening', () => {\n      // 执行这块代码说明端口未被占用\n      server.close();\n      resolve(true);\n    });\n\n    server.on('error', err => {\n      if (err.code === 'EADDRINUSE') {\n        // 端口已经被使用\n        resolve(false);\n      }\n    });\n  });\n"
  },
  {
    "path": "src/utils/getNpmRegistry.js",
    "content": "const fetch = require('node-fetch');\n\nconst registryMap = {\n  npmmirror: 'https://registry.npmmirror.com',\n  npm: 'https://registry.npmjs.org',\n};\n\n/**\n * 并发请求多个 registry，返回最快响应的那个\n */\nconst getNpmRegistry = async () => {\n  const timeout = 5000;\n\n  const fetchWithTimeout = (url) =>\n    Promise.race([\n      fetch(url).then(() => url),\n      new Promise((_, reject) =>\n        setTimeout(() => reject(new Error('timeout')), timeout)\n      ),\n    ]);\n\n  try {\n    return await Promise.race(\n      Object.values(registryMap).map((url) => fetchWithTimeout(url))\n    );\n  } catch {\n    // 所有请求都失败或超时，使用 npm 官方\n    return registryMap.npm;\n  }\n};\n\nmodule.exports = getNpmRegistry;\n"
  },
  {
    "path": "test.code.js",
    "content": "const puppeteer = require('puppeteer-core');\nconst childProcess = require('child_process');\nconst execa = require('execa');\nconst fetch = require('node-fetch');\n\nconst openChrome = () =>\n  new Promise(async resolve => {\n    const temp = await execa('mktemp', ['-d', '-t', 'chrome-remote_data_dir']);\n    const tempFile = temp.stdout.toString();\n    childProcess.spawn(\n      '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',\n      [\n        '--remote-debugging-port=9222',\n        '--no-first-run',\n        '--debug-devtools',\n        '--no-default-browser-check',\n        `--user-data-dir=${tempFile}`,\n      ],\n      {\n        stdio: 'inherit',\n      },\n    );\n    setTimeout(async () => {\n      const data = await fetch('http://localhost:9222/json/version').then(req => req.json());\n      const { webSocketDebuggerUrl } = data;\n      resolve(webSocketDebuggerUrl);\n    }, 2000);\n  });\nconst open = async () => {\n  const webSocketDebuggerUrl = await openChrome();\n  console.log(webSocketDebuggerUrl);\n  try {\n    const browser = await puppeteer.connect({\n      browserWSEndpoint: webSocketDebuggerUrl,\n    });\n    const page = await browser.newPage();\n    page.goto('https://www.baidu.com');\n  } catch (error) {\n    console.log(error);\n  }\n};\n\nopen();\n"
  }
]