简体中文 | [English](./README-en.md)

[更新日志](./CHANGELOG.md)

<h1 align="center">CR</h1>

<div align="center">

<!-- <a href="#项目大小"><img alt="GitHub repo size" src="https://img.shields.io/github/repo-size/lihao/global-cli"></a> -->
<!-- <a href="#最近更新"><img alt="GitHub last commit" src="https://img.shields.io/github/last-commit/lihao/global-cli"></a> -->

[![NPM version](https://img.shields.io/npm/v/@xiao-edu/cr.svg)](https://www.npmjs.com/package/@xiao-edu/cr)
[![NPM downloads](https://img.shields.io/npm/dm/@xiao-edu/cr.svg)](https://www.npmjs.com/package/@xiao-edu/cr)
[![GitHub](https://img.shields.io/github/license/jerryOnlyZRJ/mobile-events.svg)](https://choosealicense.com/licenses/mit)
[![GitHub language count](https://img.shields.io/badge/language-TypeScript-yellow.svg)](https://www.typescriptlang.org)

[![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme)
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
[![commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier)

[![lerna][lerna]][lerna-url]
[![PR's welcome][pr-welcome]][pr-welcome-url]

[lerna]: https://img.shields.io/badge/maintained%20with-lerna-cc00ff.svg
[lerna-url]: http://www.lernajs.io/
[pr-welcome]: https://img.shields.io/badge/PRs%20-welcome-brightgreen.svg
[pr-welcome-url]: https://gitlab.com/lihao/global-cli/blob/master/doc/CONTRIBUTING.md

一个用于规范化生成模块文件的命令行工具

</div>

# 全局的命令行工具 CR

<!-- 原仓库 [@opq741/global-cli](https://www.npmjs.com/package/@opq741/global-cli)已不再更新，请直接使用[@xiao-edu/cr](https://www.npmjs.com/package/@xiao-edu/cr) -->

## 📦 安装

```shell
npm i -g @xiao-edu/cr

// 或者使用yarn来安装
yarn add -g @xiao-edu/cr
```

## 🦄 使用

```bash
cr --help                -- 查看帮助信息
cr --version             -- 查看版本信息
```

## 1. cr

用于规范化生成任意文件的命令行工具

cr 是一个兼容多平台的文件操作的命令行工具，它可以基于 cr.template.js 来生成任意文件。

可以保证多人开发 vue 或 react 应用时，大家产生的 views、components 下的页面或组件，符合一致的组件规范。

同时解放了双手，避免一个个的去创建文件并编辑一些重复性的代码。

cr 可以保证任意文件的规范，包括 markdown, scss, txt, tsx, vue 等文本类文件，它也同样适用于创建 rar, zip, jpeg, png 等二进制文件。

它也可以用来下载并更新文件，上传文件，批量生成文件夹、甚至是创建整个项目或者小型爬虫。

### 🔋 特性

- 基于 cr.template.js，可以任意的自定义文件模板，来生成任意数量的符合代码规范的文件

- 跨平台兼容 Linux 的 shell 的文件操作的命令：如 ls、touch、mkdir、rm、cp、mv、which 等
  前面加上 cr 即可使用，如 cr ls -a ./bin

### 文件操作

平常也可以用 cr 来作文件操作，cr 是跨平台兼容 shell 的，你可以在 windows 下运行，如：

```shell

cr ls -a ./bin

# 类似于shell中的touch，但它可以递归创建文件夹
cr prettier.config.js .env eslint.config.json README.md src/routes/index.js

# 与touch命令保持一致
cr touch index.js

# 与mkdir命令保持一致
cr mkdir -p src/assets/imgs

# 与rm命令保持一致
cr rm -rf src/temp

# 与cp命令保持一致
cr cp src/temp src/test

# 与which命令保持一致
cr which npm

```

#### 环境要求

- nodejs（7.6+)
- npm

#### 安装

    npm install -g @xiao-edu/cr

如果 npm 官方源下载过慢，你可以尝试淘宝源安装：

    npm install -g @xiao-edu/cr --registry https://registry.npm.taobao.org

如果你确实不想将 cr 命令暴露到全局，只想在项目的路径下使用 cr 命令，你可以只在项目中安装：

    npm install -D @xiao-edu/cr

注意：这样就只能在项目文件夹下使用 cr 命令

#### 使用

除了像上面将命令安装到全局使用外，个人更加推荐的方式是通过 npx 来运行 cr 。npx 会自动查找本机是否有安装相应模块，如果没有的话，自动去远端查找并执行。通过 npx 就不用安装到本地，每次运行都可以使用远端最新的版本。

    npx cr path/to/folderName

#### 升级

每次执行 cr 命令时，都会自动检查更新，你只要根据提示运行即可：

    npm install -g @xiao-edu/cr

### 🎄 教程

在命令行下：

    cr

即可输出帮助信息

在当前目录下，自动创建 cr.template.js

    cr init ./

打开模板文件进行编辑：

    vim cr.template.js

然后来生成文件吧！

    cr ./report/student-count

### ✨ 代码生成

如你在 src 目录下运行：

    cr init ./views

即会在 src/views 中生成一个默认的 cr.template.js, 你可以对其进行编辑

cr.template.js 示例：

```javascript
// 之后的option会与该默认选项合并
const defaultOption = {
  // 是否忽略生成该文件
  ignore: false,
  // 如果文件存在，是否强制覆盖
  isForceOverwrite: false
}

// 导出生成文件的描述数组
const fileList = [
  {
    // 文件的路径
    path: `prettier.config.js`,
    // 写入文件中的内容，可以是模板字符串，也可以是函数，返回值会被写入该文件
    content: `
      // build at ${new Date().toDateString()}
      `
  },
  {
    // 如果没有后缀名，则认为是文件夹
    path: `src`,
    // 可以无限递归生成子目录和子文件
    children: [
      {
        path: 'index.js',
        // 文件内容可以由函数返回，返回的任何内容都会被toString()
        // 可以在函数中作任何同步的操作，包括获取数据，筛选数据，甚至上传文件
        content() {
          // 你可以发起网络请求，
          // 这里的axios是当前文件所在的项目中所能require到的npm包
          const axios = require('axios')
          const data = axios.get('http://example.com/example.json')
          const obj = {
            name: 'Alice',
            data
          }
          return obj
        }
      }
    ]
  }
]

module.exports = {
  fileList,
  defaultOption
}
```

然后当你运行

    cr ./views/report/student-count

时，cr 会从你指定的./views/report/student-count 目录递归向外寻找 cr.template.js，根据其中导出的 fileList 来自动生成对应的文件。

cr.template.js 创建完后，是可以任意共享的，你可以选取别人写好的模板，也可以将模板分享给团队，这里也分享一个项目中可用的 vue 组件模板：

```javascript
// cr.template.js
// 转成大驼峰
function toUpper(str) {
  const camelizeRE = /-(\w)/g
  // 把每个横杆分割的转成大写
  var temp = str.replace(camelizeRE, ($0, $1) => {
    return $1 ? $1.toUpperCase() : ''
  })
  // 再把首字母转成大写
  return temp[0].toUpperCase().concat(temp.slice(1))
}

// 转成横划线
function toCenterLine(str) {
  // \b 和 \B，\b 是单词边界
  const hyphenateRE = /\B([A-Z])/g
  return str.replace(hyphenateRE, '-$1').toLowerCase()
}

// 可以利用axios请求网络上的数据
const axios = require('axios')

// 之后的option会与该默认选项合并
const defaultOption = {
  // 是否忽略生成该文件
  ignore: false,
  // 如果文件存在，是否强制覆盖
  isForceOverwrite: false
}

const fileList = [
  path: `index.vue`,
  content: componentName => {
    const centerName = toCenterLine(componentName)
    const upperName = toUpper(componentName)

    return `
// Generated by @xiao-edu/cr\n\n
<template>
  <!-- ${centerName}的容器 -->
  <div class="${centerName}-wrap">
    {{ $route.meta.title }}
  </div>
</template>
<script>
export default {
  name: '${upperName}',
  data() {
    return {

    }
  },
  methods: {

  }
}

</script>
<style rel="stylesheet/scss" lang="scss" scoped>


</style>
    `
  }

]
```

即可在指定文件夹下生成 index.vue，内容为你返回的 content

这里也分享一个可用的创建 react 页面组件的 cr.template.js:
（基于 typescript 和 css module）

```javascript
// template.js you can generate
// 转成大驼峰
function toUpper(str) {
  const camelizeRE = /-(\w)/g
  // 把每个横杆分割的转成大写
  var temp = str.replace(camelizeRE, ($0, $1) => {
    return $1 ? $1.toUpperCase() : ''
  })
  // 再把首字母转成大写
  return temp[0].toUpperCase().concat(temp.slice(1))
}

// 可以利用axios请求网络上的数据
const axios = require('axios')

// 之后的option会与该默认选项合并
const defaultOption = {
  // 是否忽略生成该文件
  ignore: false,
  // 如果文件存在，是否强制覆盖
  isForceOverwrite: false
}

// 导出生成文件的描述数组
const fileList = [
  {
    // 生成导出组件的js文件
    label: 'jsxTemplate',
    // 生成文件的路径
    filePath: './index.tsx',
    // isForceOverwrite: true,
    // content为函数，返回文件的内容
    content: (componentName) => {
      const name = toUpper(componentName)

      const interfaceName = `I${name}Props`
      const content = `
// Generated by @xiao-edu/cr\n\n
import React, { useState, useEffect } from 'react'

import styles from './index.module.scss'

import classnames from 'classnames'

// import { Button } from 'antd-mobile'

interface ${interfaceName} {

}

const ${name}: React.FC<${interfaceName}> = (props) => {
  // 声明一个叫 'count' 的 state 变量。
  // const [count, setCount] = useState(0)

  useEffect(() => {


    return () => {

    }
  })

  const pageClass = classnames({
    [styles.wrap]: true,
    'with-padding': true,
    'with-bg': false,
    'with-scroll': false,
  })

  return (
    <React.StrictMode>
      <div className={pageClass}>
        {'${name}页面'}
      </div>
    </React.StrictMode>
  )
}

export default ${name}
        `
      return content.trim()
    }
  },
  {
    // 生成组件对应的scss文件
    label: 'scssTemplate',
    // 如果临时不想生成这个文件，可以设置ignore为true
    // ignore: true,
    filePath: './index.module.scss',
    content: (componentName) => {
      const content = `
@import '@/styles/vars.scss';

.wrap{
  width: 100%;
  height: 100%;
  box-sizing: border-box;
}
        `
      return content
    }
  }
]

module.exports = {
  fileList,
  defaultOption
}
```

将其放置在 src/views 下，假设你想生成的页面文件夹名为：student-count，每次运行：

    cr src/views/student-count

cr 会找到最近的祖先的 cr.template.js，根据其中导出的 fileList 来生成文件夹 student-count 及其内部的所有文件。

即可根据模板自动生成对应的页面组件，且页面会遵循一致的组件结构。

### 兼容平台

- Windows
- Mac
- Linux

### 🌎 目标

- 能够简单的生成组件或其他文件，如`cr ./simple-report`
- 跨平台的兼容 linux 的文件操作命令，如`cr ls`,`cr rm`,`cr cp`...
- 一些有用的工具库，如`cr open index.js`, `cr task`, `cr kill 3000`, `cr info`...

### 开发原因

- 在开发的过程中，总是要重复的新建文件夹和文件，填充一些相对固定的文件内容，cr 可以帮助我们去新建这些文件并填充内容。

- 想跨平台兼容一些常用的 linux 命令。

- 其它的一些开发需求，如创建并打开一个文件，杀掉一个进程或者端口，获取系统信息等。

### 如何参与

如果你有任何的建议或者意见，或者你想参与进来，你可以:

- 提一个 issue 来参与讨论
- fork 这个仓库，并提交 pull request

### 代码贡献

1. Fork 代码!
2. 创建自己的分支: `git checkout -b feat/xxxx`
3. 提交你的修改: `git commit -am 'feat(function): add xxxxx'`
4. 推送您的分支: `git push origin feat/xxxx`
5. 提交`pull request`

### 作者

- [lihao](https://gitlab.com/lihao)

### 协议

[MIT](http://opensource.org/licenses/MIT)

### 🔗 链接

## cr 子命令

cr 除了提供生成文件的功能，也提供了一些常用的子命令

```txt
Commands:
  init|i [paths...]                   在指定位置下初始化cr.template.js
  task|t [targetPath]                 运行指定路径下的npm scripts，会递归向上找最近祖先的package.json
  open|o [options] [filePath|url...]  打开对应的文件、url、或可执行文件，如果文件不存在，会递归创建该文件
  serve|s [options] [folderPath]      启动一个静态文件服务器，来预览打包好的页面
  kill [ports...]                     杀死某个端口或者进程
  update|u                            更新cr命令行工具
  dep [options] <filePath>            查看一个模块的依赖树
  $test [paths...]                    (experimental) 仅用于项目开发时做测试用，生产环境请不要使用
  wallpaper [options] [imgPath|url]   获取或者切换系统壁纸
  qrcode <url>                        打印出对应网址的二维码
  random [options] [name...]          生成一个随机数
  $expose [options] [paths...]        暴露本机服务到公网
  hosts [paths...]                    用来管理该主机的HOSTS文件
  config [options]                    查看或设置cr命令的默认配置
  ls [options] [paths...]             显示目录列表，跨平台兼容shell的ls命令
  touch [options] [paths...]          创建文件，跨平台兼容shell的touch命令
  mkdir [options] [paths...]          创建文件夹，跨平台兼容shell的mkdir命令
  cp [options] <source> <dest...>     复制指定的文件或目录，跨平台兼容shell的cp命令
  mv [options] <source> <dest...>     剪切或重命名指定的文件或目录，跨平台兼容shell的mv命令
  rm [options] <paths...>             删除文件或目录，一旦删除，无法恢复。跨平台兼容shell的rm命令
  which [cmds...]                     在环境变量PATH中寻找指定命令的地址，跨平台兼容shell的which命令
  info                                打印相关的环境信息来debug
```

如果你对 `cr` 的某个子命令不熟悉，如 `cr open` 命令
你可以携带`--help`参数来查看对应子命令的帮助信息:

```shell
cr open --help
```

## 附加功能

### node 工具集（可以帮助您创建或执行一些 node 任务）

### utils

utils 方法，详见 [@xiao-edu/cr/task/ts-utils](https://gitlab.com/lihao/global-cli/-/tree/master/task/ts-utils)。

```ts
const utils = require('@xiao-edu/cr/task/utils')

const { log, chalk } = utils.logger
const { execAllShell, shelljs } = utils.exec
const { getEnv } = utils.getEnv
const { axios, fse, unzip, globby } = utils.pkg
```

### 1. `utils.logger`

> 包含常用的日志打印功能，`log` 示例：

```ts
log.info('开始测试logger的功能：')
log.waiting('正在等待任务完成...')
log.warn('这是一条警告信息')
log.error('测试一下打印错误的功能')
log.success('任务测试完成！')
```

- `chalk`: 导出自 [`chalk`](https://www.npmjs.com/package/chalk)，用于命令行终端的日志打印

### 2. `utils.exec`

> 包含执行命令的常用操作，`execAllShell` 示例：

```ts
// 可以任意执行命令行，以'//'开头的是注释，不会执行该行命令
execAllShell(`
  pwd
  ls
  // git status
  which npm
`)
```

- `shelljs` : 导出自 [`shelljs`](https://www.npmjs.com/package/shelljs), 跨平台执行 shell 脚本

### 3. `utils.getEnv`

> 获取环境变量，`getEnv` 基于[`dot-env`](https://www.npmjs.com/package/dot-env)来加载环境变量

示例：

```ts
// 这个函数会自动加载.env等环境文件中的变量到process.env中，并从中筛选TASK_开头的环境变量，作为对象返回
const env = getEnv()
console.log(`env: `, env)

// 比如你的.env.local文件内容：
// TASK_PROJECT_NAME = 'my-project'
// TASK_DO_BUILD_AND_DEPLOY = 'false'

// getEnv()打印结果：
// {
//   TASK_PROJECT_NAME: 'my-project',
//   TASK_DO_BUILD_AND_DEPLOY: 'false'
// }
```

### 4. `utils.pkg`

> 包含常见的 npm 包，导出给外部使用：

- `axios` : 导出自 [`axios`](https://www.npmjs.com/package/axios), 浏览器和 node 端都兼容的请求库
- `fse` : 导出自 [`fs-extra`](https://www.npmjs.com/package/fs-extra), 更好用的 fs 模块
- `unzip` : 导出自 [`unzipper`](https://www.npmjs.com/package/unzipper), 用于压缩或解压文件
- `globby` : 导出自 [`globby`](https://www.npmjs.com/package/globby), 用于获取匹配对应规则的文件
