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