git
和 npm
管理多软件包代码仓库的工作流程进行优化首先使用 npm 将 Lerna 安装到全局环境中
npm install -g lerna
接下来,我们将创建一个新的 git 代码仓库
mkdir lerna4 && cd lerna4
现在,我们将上述仓库转变为一个 Lerna 仓库:
lerna init
lerna notice cli v4.0.0
lerna info Initializing Git repository
lerna info Creating package.json
lerna info Creating lerna.json
lerna info Creating packages directory
lerna success Initialized Lerna files
lerna4 / packages / 放置多个软件包(package);
package.json;
lerna.json;
.gitignore
node_modules.idea.vscode;
npm install -g yrm
npm install -g nrm
git clone https://gitee.com/zhufengpeixun/lerna.git --depth=1
.vscode\launch.json
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}\\core\\lerna\\cli.js",
"args": ["ls"]
}
]
}
lerna 入口核心包
@lerna/cli
@lerna/create 创建包命令
@lerna/init 初始化lerna项目
cnpm install verdaccio -g
verdaccio
http://localhost:4873
npm adduser --registry http://localhost:4873/
npm publish --registry http://localhost:4873/
lerna create lerna4 --registry http://localhost:4873
lerna success create New package lerna4 created at ./packages\lerna4
lerna create @lerna4/cli --registry http://localhost:4873
lerna success create New package @lerna4/cli created at ./packages\cli
lerna create @lerna4/create --registry http://localhost:4873
lerna success create New package @lerna4/create created at ./packages\create
lerna create @lerna4/init --registry http://localhost:4873
lerna success create New package @lerna4/init created at ./packages\init
npm install --save-dev jest
//在所有的包下执行test命令
lerna run test
//在lerna4下执行test命令
lerna run test --scope lerna4
//在所有的包下执行shell脚本
lerna exec -- jest
//在lerna4下执行shell脚本
lerna exec --scope lerna4 -- jest
{
"name": "root",
"private": true,
"devDependencies": {
"lerna": "^4.0.0"
},
+ "scripts": {
+ "test":"jest"
+ }
}
module.exports = {
testMatch: ["**/__tests__/**/*.test.js"],
};
packages\lerna4\package.json
{
+ "scripts": {
+ "test": "jest"
+ }
}
packages\lerna4\lib\lerna4.js
module.exports = lerna4;
function lerna4() {
return "lerna4";
}
packages\create__tests__\create.test.js
"use strict";
const create = require("..");
describe("@lerna4/create", () => {
it("create", () => {
expect(create()).toEqual("create");
});
});
cnpm i eslint --save-dev
module.exports = {
parserOptions: { ecmaVersion: 2017, sourceType: "module" },
extends: ["eslint:recommended"],
rules: {
"no-unused-vars": ["off"],
},
env: { node: true, jest: false },
};
__tests__;
"scripts": {
"test": "jest",
+ "lint":"eslint --ext .js packages/**/*.js --no-error-on-unmatched-pattern --fix"
}
cnpm i prettier eslint-plugin-prettier --save-dev
module.exports = {
extends: ['eslint:recommended'],
//让所有可能会与 prettier 规则存在冲突的 eslint rule失效,并使用 prettier 的规则进行代码检查
//相当于用 prettier 的规则,覆盖掉 eslint:recommended 的部分规则
+ plugins: ['prettier'],
rules: {
'no-unused-vars': ['off'],
//不符合prettier规则的代码要进行错误提示
+ 'prettier/prettier': ['error', { endOfLine: 'auto' }],
},
env: { node: true, jest: false },
};
module.exports = {
singleQuote: true,
};
\n
LF(Line Feed)换行 回车
,即\r\n
CR/LF回车
,即\r
CR(Carriage Return)root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
git commit
之前检查代码,保证所有提交到版本库中的代码都是符合规范的git push
之前执行单元测试,保证所有的提交的代码经过的单元测试git hooks
cnpm i husky --save-dev
npm set-script prepare "husky install"
npx husky add .husky/pre-commit "npx lint-staged"
cnpm install commitizen cz-customizable @commitlint/cli @commitlint/config-conventional --save-dev
npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"
npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"
module.exports = {
types: [
{ value: "feat", name: "feat:一个新特性" },
{ value: "fix", name: "fix:修复BUG" },
],
scopes: [{ name: "sale" }, { name: "user" }, { name: "admin" }],
};
module.exports = {
extends: ["@commitlint/config-conventional"],
};
"scripts": {
"test": "jest",
"lint": "eslint --ext .js packages/**/*.js --no-error-on-unmatched-pattern --fix",
"prepare": "husky install",
+ "commit": "cz"
},
npx husky add .husky/pre-push "npm run test"
lerna version
lerna publish
packages\lerna4\cli.js
#!/usr/bin/env node
require(".")(process.argv.slice(2));
packages\lerna4\index.js
module.exports = main;
function main(argv) {
console.log(argv);
}
packages\lerna4\package.json
{
+ "main": "index.js",
+ "bin":{
+ "lerna4":"cli.js"
+ }
}
cd packages\lerna4
npm link
lerna4
const yargs = require("yargs/yargs");
const argv = process.argv.slice(2);
const cli = yargs(argv);
//应用到每一个命令的全局参数
const opts = {
loglevel: {
defaultDescription: "info",
describe: "报告日志的级别",
type: "string",
alias: "L",
},
};
//全局的key
const globalKeys = Object.keys(opts).concat(["help", "version"]);
cli
.options(opts) //配置全局参数
.group(globalKeys, "Global Options:") // 把全局参数分到全局组里
.usage("Usage: $0 <command> [options]") //提示使用说明
.demandCommand(1, "至少需要一个命令,传递--help查看所有的命令和选项") //指定最小命令数量
.recommendCommands() //推荐命令
.strict() //严格命令,不正确 会报错
.fail((msg, err) => {
//自定义错误打印
console.error("lerna", msg, err);
})
.alias("h", "help") //别名
.alias("v", "version") //别名
.wrap(cli.terminalWidth()) //命令行宽度
.epilogue(
//结语
`当1个命令失败了,所有的日志将会写入当前工作目录中的lerna-debug.log`
)
.command({
command: "create <name>",
describe: "创建一个新的lerna管理的包",
builder: (yargs) => {
yargs
.positional("name", {
describe: "包名(包含scope)",
type: "string",
})
.options({
registry: {
group: "Command Options:",
describe: "配置包的发布仓库",
type: "string",
},
});
},
handler: (argv) => {
console.log("执行init命令", argv);
},
})
.parse(argv);
/**
node lerna4.js create project --registry http://localhost:4873
执行init命令 {
'$0': 'lerna4.js',
_: [ 'create' ],
name: 'project'
registry: 'http://localhost:4873',
}
*/
lerna link
lerna bootstrap
packages\cli\package.json
"dependencies": {
"@lerna4/cli":"^0.0.4",
"@lerna4/init":"^0.0.4"
},
+ "main": "index.js",
packages\cli\index.js
const yargs = require("yargs/yargs");
function lernaCLI() {
const cli = yargs();
//应用到每一个命令的全局参数
const opts = {
loglevel: {
defaultDescription: "info",
describe: "报告日志的级别",
type: "string",
alias: "L",
},
};
//全局的key
const globalKeys = Object.keys(opts).concat(["help", "version"]);
return cli
.options(opts) //配置全局参数
.group(globalKeys, "Global Options:") // 把全局参数分到全局组里
.usage("Usage: $0 <command> [options]") //提示使用说明
.demandCommand(1, "至少需要一个命令,传递--help查看所有的命令和选项") //指定最小命令数量
.recommendCommands() //推荐命令
.strict() //严格命令,不正确 会报错
.fail((msg, err) => {
//自定义错误打印
console.error("lerna", msg, err);
})
.alias("h", "help") //别名
.alias("v", "version") //别名
.wrap(cli.terminalWidth()) //命令行宽度
.epilogue(
//结语
`当1个命令失败了,所有的日志将会写入当前工作目录中的lerna-debug.log`
);
}
module.exports = lernaCLI;
packages\init\command.js
exports.command = "init";
exports.describe = "创建一个新的Lerna仓库";
exports.builder = (yargs) => {
console.log("执行init builder");
};
exports.handler = (argv) => {
console.log("执行init命令", argv);
};
packages\lerna4\package.json
+ "main": "index.js"
packages\lerna4\index.js
const cli = require('@lerna4/cli');
const initCmd = require('@lerna4/init/command');
function main(argv) {
return cli().command(initCmd).parse(argv);
}
module.exports = main;
lerna add fs-extra packages/init
lerna add execa packages/init
packages\init\command.js
exports.command = "init";
exports.describe = "创建一个新的Lerna仓库";
exports.builder = () => {
console.log("执行init builder");
};
exports.handler = (argv) => {
console.log("执行init命令", argv);
return require(".")(argv);
};
packages\init\index.js
const path = require("path");
const fs = require("fs-extra");
const execa = require("execa");
class InitCommand {
constructor(argv) {
this.argv = argv;
this.rootPath = path.resolve();
}
async execute() {
await execa("git", ["init"], { stdio: "pipe" });
await this.ensurePackageJSON();
await this.ensureLernaConfig();
await this.ensurePackagesDir();
console.log("Initialized Lerna files");
}
async ensurePackageJSON() {
console.log("创建 package.json");
await fs.writeJson(
path.join(this.rootPath, "package.json"),
{
name: "root",
private: true,
devDependencies: {
lerna: "^4.0.0",
},
},
{ spaces: 2 }
);
}
async ensureLernaConfig() {
console.log("创建 lerna.json");
await fs.writeJson(
path.join(this.rootPath, "lerna.json"),
{
packages: ["packages/*"],
version: "0.0.0",
},
{ spaces: 2 }
);
}
async ensurePackagesDir() {
console.log("创建 packages 目录");
await fs.mkdirp(path.join(this.rootPath, "packages"));
}
}
function factory(argv) {
new InitCommand(argv).execute();
}
module.exports = factory;
lerna add pify packages/crate
lerna add init-package-json packages/crate
lerna add dedent packages/crate
packages\lerna4\index.js
const cli = require('@lerna4/cli');
const initCmd = require('@lerna4/init/command');
const createCmd = require('@lerna4/create/command');
function main(argv) {
return cli()
.command(initCmd)
+ .command(createCmd)
.parse(argv);
}
module.exports = main;
packages\lerna4\package.json
{
"dependencies": {
"@lerna4/cli":"^0.0.4",
"@lerna4/init":"^0.0.4",
+ "@lerna4/create":"^0.0.4"
},
}
packages\create\command.js
exports.command = "create <name>";
exports.describe = "创建一个新的lerna管理的包";
exports.builder = (yargs) => {
console.log("执行init builder");
yargs
.positional("name", {
describe: "包名(包含scope)",
type: "string",
})
.options({
registry: {
group: "Command Options:",
describe: "配置包的发布仓库",
type: "string",
},
});
};
exports.handler = (argv) => {
console.log("执行create命令", argv);
return require(".")(argv);
};
packages\create\index.js
const path = require("path");
const fs = require("fs-extra");
const dedent = require("dedent");
const initPackageJson = require("pify")(require("init-package-json"));
class CreateCommand {
constructor(options) {
this.options = options;
this.rootPath = path.resolve();
console.log("options", options);
}
async execute() {
const { name, registry } = this.options;
this.targetDir = path.join(this.rootPath, "packages/cli");
this.libDir = path.join(this.targetDir, "lib");
this.testDir = path.join(this.targetDir, "__tests__");
this.libFileName = `${name}.js`;
this.testFileName = `${name}.test.js`;
await fs.mkdirp(this.libDir);
await fs.mkdirp(this.testDir);
await this.writeLibFile();
await this.writeTestFile();
await this.writeReadme();
var initFile = path.resolve(process.env.HOME, ".npm-init");
await initPackageJson(this.targetDir, initFile);
}
async writeLibFile() {
const libContent = dedent`
module.exports = ${this.camelName};
function ${this.camelName}() {
// TODO
}
`;
await catFile(this.libDir, this.libFileName, libContent);
}
async writeTestFile() {
const testContent = dedent`
const ${this.camelName} = require('..');
describe('${this.pkgName}', () => {
it('needs tests');
});
`;
await catFile(this.testDir, this.testFileName, testContent);
}
async writeReadme() {
const readmeContent = dedent`## Usage`;
await catFile(this.targetDir, "README.md", readmeContent);
}
}
function catFile(baseDir, fileName, content) {
return fs.writeFile(path.join(baseDir, fileName), `${content}\n`);
}
function factory(argv) {
new CreateCommand(argv).execute();
}
module.exports = factory;
命令 | 说明 |
---|---|
lerna init | 初始化项目 |
命令 | 说明 |
---|---|
lerna create | 创建 package |
lerna add | 安装依赖 |
lerna link | 链接依赖 |
命令 | 说明 |
---|---|
lerna exec | 执行 shell 脚本 |
lerna run | 执行 npm 命令 |
lerna clean | 清空依赖 |
lerna bootstrap | 重新安装依赖 |
命令 | 说明 |
---|---|
lerna version | 修改版本号 |
lerna changed | 查看上个版本以来的所有变更 |
lerna diff | 查看 diff |
lerna publish | 发布项目 |
git commit
可以提高git log
可读性,生成格式良好的changelog
<类型>[可选 范围]: <描述>
[可选 正文]
[可选 脚注]