silki
Version:
Cli tool for build react app, based on generator-react-multipage, create-react-app, roadhog. Support react multiple pages app develop.
416 lines (389 loc) • 15.9 kB
JavaScript
const Generators = require("yeoman-generator");
const fs = require("fs");
const table = require("markdown-table");
const async = require("async");
// 工具函数
const generatePrompts = require("./generatePrompts");
// 默认要填写的内容
const path = require("path");
const mkdirp = require("mkdirp");
const chalk = require("chalk");
// 批量移动文件的方法
const cwd = process.cwd();
// 源文件的路径
const shell = require("shelljs");
const packJsBundle = require("./utils/build");
const indexTestJS = path.join(cwd, "./src/index.test.js");
const MarkdownTable = require("./utils/generateTable");
let NOTICE = ``;
/**
* url是否是git仓库的正则表达式
*/
function matchGitRepository(url) {
return url && /^git@\S*\.git/.test(url);
}
/**
* 获取该仓库所属于的group+repository信息
* "git@gitlab.alibaba-inc.com:qinliang.ql/RelationshipTransformation.git"
*/
function extractGroupAndRepository(url) {
const [type, gitUrl] = url.split("@");
const [gitlabUrl, focusPart] = gitUrl.split(":");
const [group, repository] = focusPart.split("/");
return {
group,
repository: repository.split(".")[0]
};
}
class AppGenerator extends Generators.Base {
constructor(args, options) {
super(args, options);
this.option("skip-welcome-message", {
desc: "Skip the welcome message",
type: Boolean,
defaults: false
});
this.option("skip-install");
// this.sourceRoot(baseRootPath);
// 默认的root为"./templates",通过该方法覆盖
this.config.save();
//Calling `this.config.save()` from a generator for the first time will create the file(yo-rc.json).
}
initializing() {
if (!this.options["skip-welcome-message"]) {
this.log(require("yeoman-welcome"));
this.log(
"Out of the box I include Webpack and some default React components.\n"
);
}
}
prompting() {
this.log("当你运行该命令的时候表示你需要发布你的组件到demo站点中,请知悉......");
const prevPrompts = this.config;
const prompts = generatePrompts(prevPrompts);
return this.prompt(prompts).then(answers => {
//如果用户输入的组件名称和默认的不一致,需要设置为默认的组件名称
this.publish = answers.publish;
// 为yoeman设置默认的全局变量
this.appName = answers.appName;
this.chineseName = answers.chineseName;
this.desc = answers.desc;
this.category = answers.category;
this.developer = answers.developer;
this.email = answers.email;
this.respository = answers.respository;
this.frame = answers.frame;
this.platform = answers.platform;
this.overallDesc = answers.overallDesc;
this.when2Use = answers.when2Use;
this.api = answers.api;
this.demoTitle = answers.demoTitle;
this.config.set("appName", this.appName);
this.config.set("chineseName", this.chineseName);
this.config.set("desc", this.desc);
// demo说明
this.config.set("category", this.category);
this.config.set("frame", this.frame);
this.config.set("platform", this.platform);
this.config.set("overallDesc", this.overallDesc);
// readme的说明
this.config.set("api", this.api);
this.config.set("when2Use", this.when2Use);
// 这些变量用于子级Generator
this.config.set("demoTitle", this.demoTitle);
this.config.set("publish", this.publish);
// 开发者,仓库,email等信息
this.config.set("developer", this.developer);
this.config.set("email", this.email);
this.config.set("respository", this.respository);
this.config.set("readmeGenerated", true);
});
}
configuring() {}
/**
* 产生一个demo文件夹,将我们的文件移动到里
*/
writing() {
const fileContent = fs.readFileSync(path.join(cwd, "./package.json"));
const packageJSON = JSON.parse(fileContent);
const currentBranch = packageJSON.version;
const tnpmPackageName = packageJSON.name;
let dailyAssetsUrlPrefix, group, repository, fullRepositoryUrl;
// 下面我需要把相应的build目录推送到某一个group下的某一个repository。可以在package.json中获取
if (
!packageJSON.repository &&
!matchGitRepository(this.config.get("respository"))
) {
this.log(
"repository非git仓库,程序将退出,你可以手动在package.json中添加repository字段并重新运行该命令......."
);
process.exit(1);
} else {
// package.json或者用户自定义至少一个有配置信息,继续往下执行代码
if (packageJSON.repository) {
if (typeof packageJSON.repository == "object") {
fullRepositoryUrl = packageJSON.repository.url;
const { url } = packageJSON.repository;
const detail = extractGroupAndRepository(url);
group = detail.group;
repository = detail.repository;
// http://g-assets.daily.taobao.net/de/spring-festival-live/0.0.1/page/live/index.js
} else if (matchGitRepository(packageJSON.repository)) {
const detail = extractGroupAndRepository(packageJSON.repository);
group = detail.group;
repository = detail.repository;
fullRepositoryUrl = packageJSON.repository;
}
} else {
const userInputRepository = this.config.get("respository");
const detail = extractGroupAndRepository(userInputRepository);
group = detail.group;
repository = detail.repository;
fullRepositoryUrl = userInputRepository;
}
dailyAssetsUrlPrefix = `http://g-assets.daily.taobao.net/${group}/${repository}/${currentBranch}/`;
}
const demoPath = path.join(cwd, "./demo");
// 直接打包到build目录下就可以了,每次清空就可以了~~~
packJsBundle(
indexTestJS,
(
generatedFiles,
id,
sourceCode,
specifiers,
antdImports,
relativeExports,
componentName,
componentInfoArray
) => {
// console.log("产生的文件列表为:" + JSON.stringify(generatedFiles));
if (generatedFiles) {
let dependencies = [];
// 生成模板dependencies
for (let i = 0, len = generatedFiles.length; i < len; i++) {
dependencies.push(
`- ${dailyAssetsUrlPrefix}${generatedFiles[i]}.js`
);
}
dependencies.push(`- ${dailyAssetsUrlPrefix}main.js`);
// 将antd绑定到全局上,添加到所有资源后面,同时从antd上import的元素全部从antd上直接获取
dependencies.push(
`- https://g-assets.daily.taobao.net/tvadmin/std-website-static-resource/0.0.2/moment-with-locales.js`
);
dependencies.push(
`- https://g-assets.daily.taobao.net/tvadmin/std-website-static-resource/0.0.2/antd-with-locales.js`
);
let antdRawCode = "";
for (let i = 0, len = antdImports.length; i < len; i++) {
antdRawCode += `const ${antdImports[i]} = antd.${antdImports[
i
]};\n`;
}
let relativeRawCode = "";
let relativeRawCodeDescription = ``;
// import {name,sex} from "./2exports.js";
for (let j = 0, length = relativeExports.length; j < length; j++) {
const { source, imports, key } = relativeExports[j];
for (let k = 0; k < imports.length; k++) {
relativeRawCodeDescription += `const ${imports[
k
]} = require("${source}")["${imports[k]}"];\n`;
relativeRawCode += `const ${imports[
k
]} = window["${key}"]["${imports[k]}"];\n`;
}
}
this.config.set("demoJSLists", dependencies.join("\n"));
this.config.set("sourceCode", sourceCode);
this.config.set("id", id);
if (relativeRawCodeDescription) {
NOTICE = `/**
* 出现下面这种window[\d+]['X']的全局变量是因为你使用了import {A,B} from "./C.js";的格式
* 不建议使用这种相对路径引入外部值,你可以直接将变量写在index.test.js中:
* ${relativeRawCodeDescription}
*/`;
}
// console.log('准备产生表格数据为===='+JSON.stringify(componentInfoArray));
const bitTables = MarkdownTable.generateBitMarkdownTable(
componentInfoArray
);
const componentUsageTables = bitTables.reduce((prev, cur) => {
prev += `组件${cur.specifier}接受的API如下:\n\n${cur.bitTable}\n\n\n`;
return prev;
}, "");
// 需要顶格,否则有多余的空格
const markdownReadmeTemplate = `---
category: ${this.config.get("category")}
name: ${this.config.get("appName")}
desc: ${this.config.get("chineseName")}
frame:
- ${this.config.get("frame")}
platform:
- ${this.config.get("platform")}
---
${this.config.get("overallDesc")}
## 何时使用
${this.config.get("when2Use")}
## API
下面是开发者写的该组件支持的参数:
${componentUsageTables}
`;
// 前面必须顶格才能没有空格~~~
const markdownDemoTemplate = `---
title: ${this.config.get("chineseName")}
dependencies:
${dependencies.join("\n")}
---
## ${this.config.get("demoTitle")}
${this.config.get("desc")}
\`\`\`css
\`\`\`
\`\`\`js
/**
* \n${specifiers
.reduce((prev, cur) => {
if (!/^\d*$/.test(cur)) {
prev.push(cur);
}
return prev;
}, [])
.join(",")},antd等变量已经挂载到window上,使用时请添加如下代码引入组件\n:
const ${componentName} = require("${tnpmPackageName}");
*
*/
${antdRawCode}
${NOTICE}
${relativeRawCode}
${sourceCode}
\`\`\`
\`\`\`html
<div id="${id}"></div>
\`\`\`
`;
async.parallel(
[
callback => {
fs.writeFile(
// path.join(cwd, "./readme.md"),
path.join(cwd, "./std-readme.md"),
markdownReadmeTemplate,
() => {
callback(null, true);
this.log(chalk.green("README文件写入完毕!"));
}
);
},
callback => {
mkdirp(demoPath, err => {
fs.writeFile(
demoPath + "/basic.md",
markdownDemoTemplate,
() => {
callback(null, true);
this.log(chalk.green("使用实例文件写入完毕!"));
}
);
});
}
],
(err, callback) => {
/** 1.下面这种方式是调用npm run build,但是无法直接expose特定的变量,导致window上无法挂载
* 2.其实是先发布具体的代码,然后才开始写readme,而不是先写readme再发布~~~
* **/
if (err) {
this.log(chalk.red("创建规范站点发布文件失败,程序将退出......"));
process.exit(1);
} else {
const needPublish = this.config.get("publish");
if (!this.config.get("publish")) {
this.log(
chalk.red(
"规范站点需要的文件已经生成成功,你可以手动为package.json添加std:true要求发布......"
)
);
} else {
let pkg = {};
pkg = JSON.parse(fileContent);
pkg["std"] = true;
// package.json设置repository配置
if (!matchGitRepository(fullRepositoryUrl)) {
this.log(
chalk.red("package.json和你输入的仓库地址都是不合法的,程序将退出......")
);
process.exit(1);
} else {
pkg.repository = {
type: "git",
url: fullRepositoryUrl
};
pkg.author = this.config.get("developer");
pkg.email = this.config.get("email");
pkg.repository = fullRepositoryUrl;
}
// git remote add origin git@gitlab.alibaba-inc.com:silvermine/tv-lottery-test.git
fs.writeFileSync(
path.join(cwd, "./package.json"),
JSON.stringify(pkg, null, 10)
);
// 1.编译build组件并checkout到一个分支,准备发布CDN
if (shell.which("npm")) {
if (shell.which("git")) {
// 首先推送一个master分支到gitlab上并设置为默认的分支,防止报错
const masterPushStatus = shell.exec(
`git init && git remote add origin ${fullRepositoryUrl} && git add -A && git commit -m "发布master分支" && git push origin master`
).code;
// 推送master分支成功
if (!masterPushStatus) {
this.log(
chalk.green("master分支已经推送到gitlab上,准备推送当前分支....")
);
} else {
const masterPushStatus = shell.exec(
`git add -A && git commit -m "发布master分支" && git push origin master`
).code;
if (!masterPushStatus) {
this.log(
chalk.green("master分支已经推送到gitlab上,准备推送当前分支....")
);
} else {
this.log(chalk.red("master分支推送失败,程序将会退出....."));
process.exit(1);
}
}
const stdError = shell.exec(
`git checkout -b daily/${currentBranch} `,
{ silent: true }
).stderr;
// 1.Generator如果能自己写一个disk文件,这样用户只需要输入一次就可以了,我们的this.config.save干的就是这个事情~~~~~~
// 查看你的.yo-rc.json,直接通过this.config.get就可以获取了。不过这都是后续优化的内容了~~~~
// 2.解析index.test.js的内容,直接把里面的东西插入到demo的basic里面
if (/fatal/g.test(stdError)) {
// 该分支已经存在了
} else if (/on/g.test(stdError)) {
// 已经在该分支上了,切换分支成功
}
const pushCode = shell.exec(
`git push origin daily/${currentBranch}`
).stdout;
} else {
this.log(chalk.red("demo发布的依赖的CDN文件发布失败......."));
}
}
}
}
}
);
}
}
);
}
install() {}
end() {
this.log(chalk.green("Generator执行完成,等待其他操作完成后退出....."));
// process.exit(0);
// 异步调用不要随意调用process.exit(0),否则回调还没有触发就直接退出了~~~~~导致异步回调函数根本没有调用~~~~
//
}
}
module.exports = AppGenerator;