├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .nycrc ├── .travis.yml ├── LICENSE ├── README.md ├── bin ├── omni └── omni-door.js ├── commitlint.config.js ├── docs ├── CHANGELOG.md ├── CHANGELOG.zh-CN.md ├── DEV.md ├── DEV.zh-CN.md ├── OMNI.md ├── OMNI.zh-CN.md ├── README.zh-CN.md └── omni-init.gif ├── mocha.opts ├── package.json ├── scripts ├── branch.sh ├── copy.sh ├── publish.sh └── version.sh ├── src ├── @types │ └── rollup.d.ts ├── commands │ ├── build │ │ ├── __test__ │ │ │ └── index.test.ts │ │ ├── dependencies_build.ts │ │ ├── gulp.ts │ │ ├── index.ts │ │ ├── rollup.ts │ │ └── webpack.ts │ ├── commands.ts │ ├── dev │ │ ├── __test__ │ │ │ └── index.test.ts │ │ ├── index.ts │ │ ├── open.chrome.applescript │ │ ├── open.ts │ │ ├── run.ts │ │ └── server.ts │ ├── index.ts │ ├── initial │ │ ├── __test__ │ │ │ └── index.test.ts │ │ ├── index.ts │ │ └── initial_preset.ts │ ├── new │ │ ├── __test__ │ │ │ └── index.test.ts │ │ └── index.ts │ ├── release │ │ ├── __test__ │ │ │ └── index.test.ts │ │ ├── branch.sh │ │ ├── index.ts │ │ ├── publish.sh │ │ └── version.sh │ ├── servers │ │ ├── __test__ │ │ │ └── index.test.ts │ │ ├── express-webpack.ts │ │ ├── favicon.ico │ │ ├── index.ts │ │ └── koa-next.ts │ └── start │ │ ├── __test__ │ │ └── index.test.ts │ │ └── index.ts ├── index.d.ts └── utils │ ├── __test__ │ └── index.test.ts │ ├── index.ts │ ├── logo.ts │ ├── signal.ts │ └── tackle_plugins.ts ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | lib/ 3 | node_modules/ -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | node: true, 4 | es6: true, 5 | }, 6 | extends: [ 7 | 'eslint:recommended', 8 | 'plugin:@typescript-eslint/eslint-recommended', 9 | ], 10 | globals: { 11 | Atomics: 'readonly', 12 | SharedArrayBuffer: 'readonly', 13 | }, 14 | parser: '@typescript-eslint/parser', 15 | parserOptions: { 16 | ecmaFeatures: { 17 | module: true, 18 | ts: true, 19 | tsx: true, 20 | }, 21 | ecmaVersion: 2018, 22 | sourceType: 'module', 23 | }, 24 | plugins: ['@typescript-eslint'], 25 | rules: { 26 | 'no-console': [ 27 | 'error', 28 | { 29 | allow: ['warn', 'error', 'info'], 30 | }, 31 | ], 32 | '@typescript-eslint/indent': ['warn', 2], 33 | quotes: ['error', 'single'], 34 | semi: ['error', 'always'], 35 | 'no-unused-vars': ['off'], 36 | 'no-useless-escape': ['off'] 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | .nyc_output 4 | .omni_cache 5 | coverage 6 | lib 7 | 8 | omni.config.js 9 | package-lock.json 10 | *.log -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | scripts 3 | src 4 | docs 5 | 6 | .editorconfig 7 | .eslintignore 8 | .eslintrc.js 9 | tsconfig.json 10 | *.config.js 11 | *.conf.js 12 | .gitignore 13 | 14 | test 15 | __test__ 16 | 17 | _config.yml 18 | .nyc_output 19 | .travis.yml 20 | coverage 21 | .nycrc 22 | mocha.opts 23 | 24 | yarn.lock 25 | package-lock.json 26 | *.log -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "extension": [ 3 | ".ts", 4 | ".tsx" 5 | ], 6 | "include": [ 7 | "src/**/*.ts" 8 | ], 9 | "exclude": [ 10 | "**/*.d.ts", 11 | "src/**/*.test.ts", 12 | "src/commands/index.ts", 13 | "src/commands/commands.ts", 14 | "lib/*" 15 | ], 16 | "reporter": [ 17 | "lcovonly" 18 | ], 19 | "all": true 20 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | dist: trusty 5 | sudo: required 6 | addons: 7 | - chrome: stable 8 | before_install: 9 | - npm i -g codecov 10 | install: 11 | - yarn 12 | script: 13 | - npm run lint 14 | - npm run test 15 | - codecov 16 | cache: 17 | yarn: true 18 | directories: 19 | - node_modules 20 | after_success: 21 | - export CODECOV_TOKEN="00ef439d-5d49-4470-970b-ff6a47daea9b" 22 | - bash <(curl -s https://codecov.io/bash) -s coverage/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Bobby Li 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🐸 @omni-door/cli 2 | 3 | https://www.omnidoor.org 4 | 5 | The CLI Tool for set up standard frontend project. 6 | 7 | [![NPM downloads](http://img.shields.io/npm/dm/%40omni-door%2Fcli.svg?style=flat-square)](https://www.npmjs.com/package/@omni-door/cli) 8 | [![npm version](https://badge.fury.io/js/%40omni-door%2Fcli.svg)](https://badge.fury.io/js/%40omni-door%2Fcli) 9 | [![node version](https://img.shields.io/badge/node.js-%3E=_10.13.0-green.svg?style=flat-square)](http://nodejs.org/download/) 10 | [![Build Status](https://travis-ci.com/omni-door/cli.svg?branch=master)](https://travis-ci.com/omni-door/cli) 11 | [![codecov](https://codecov.io/gh/omni-door/cli/branch/master/graph/badge.svg)](https://codecov.io/gh/omni-door/cli) 12 | [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) 13 | [![install size](https://packagephobia.now.sh/badge?p=%40omni-door%2Fcli)](https://packagephobia.now.sh/result?p=%40omni-door%2Fcli) 14 | [![license](http://img.shields.io/npm/l/%40omni-door%2Fcli.svg)](https://github.com/omni-door/cli/blob/master/LICENSE) 15 | 16 | 17 | 18 | 19 | 20 | English | [简体中文](./docs/README.zh-CN.md) 21 | 22 | [ DETAILS](./docs/OMNI.md) 23 | 24 | [CHANGELOG](./docs/CHANGELOG.md) 25 | 26 | ## install 27 | The latest LTS version of Node.js is recommended, or at least ensure node >= 10.13.0 28 | 29 | Several options to get up and running: 30 | 31 | * Clone the repo: `git@github.com:omni-door/cli.git` 32 | 33 | * Install with [npm](https://www.npmjs.com/package/@omni-door/cli): `npm install @omni-door/cli -g` 34 | 35 | * Install with [Yarn](https://yarnpkg.com/en/package/@omni-door/cli): `yarn global add @omni-door/cli` 36 | 37 | * Initial project with [npx](https://www.npmjs.com/package/@omni-door/cli): `npx @omni-door/cli init` 38 | 39 | ## omni --help 40 | ```shell 41 | Usage: index [command] [options] 42 | 43 | Options: 44 | 45 | -v, --version output the version number 46 | -h, --help output usage information 47 | 48 | Commands: 49 | 50 | init [options] [strategy] initialize your project, [strategy] could be stable(default) or latest 51 | dev [options] omni dev [-p ] [-H ] [-P ] 52 | start [options] omni start [-p ] [-H ] [-P ] 53 | new [options] [name] omni new [name] [-f | -c] [-P ] 54 | build [options] build your project according to the [omni.config.js]'s build field 55 | release [options] publish your project according to the [omni.config.js]'s release field 56 | 57 | ``` 58 | 59 | ## omni init 60 | 61 | ### Initial your project by answer several questions 62 | ```shell 63 | omni init 64 | ``` 65 | 66 | ### Initial your project with lastest denpendencies 67 | ```shell 68 | omni init lastest 69 | ``` 70 | 71 | ### Initial your project without install dependencies 72 | ```shell 73 | omni init -n 74 | ``` 75 | 76 | ### Initial your project according to some template 77 | ```shell 78 | omni init -t [projectName] 79 | ``` 80 | or 81 | ```shell 82 | omni init --react_entire [projectName] 83 | ``` 84 | 85 | ### options 86 | ```shell 87 | Usage: omni init [strategy] [options] 88 | 89 | initialize your project, [strategy] could be stable(default) or latest 90 | 91 | Arguments: 92 | 93 | strategy stable or latest 94 | 95 | Options: 96 | -rb, --react_basic [name] create a basic React SPA project 97 | -rs, --react_standard [name] create a standard React SPA project 98 | -re, --react_entire [name] create a most versatile React SPA project 99 | -rp, --react_pc [name] create a React SPA project based on Antd 100 | -vb, --vue_basic [name] create a basic Vue SPA project 101 | -vs, --vue_standard [name] create a standard Vue SPA project 102 | -ve, --vue_entire [name] create a most versatile Vue SPA project 103 | -rS, --react_ssr [name] create a React component library 104 | -rc, --react_components [name] create a React component library 105 | -vc, --vue_components [name] create a Vue component library 106 | -t, --toolkit [name] create a toolkit project 107 | -n, --no-install init project without install dependencies 108 | -P, --path the workpath for init the project 109 | -h, --help output usage information 110 | ``` 111 | 112 | --- 113 | 114 | ## omni dev 115 | 116 | ### options 117 | ```shell 118 | Usage: omni dev [options] 119 | 120 | omni dev [-p ] [-H ] [-P ] 121 | 122 | Options: 123 | -p, --port start the dev-server according to the specified port 124 | -H, --hostname start the dev-server according to the specified hostname 125 | -P, --path the workpath for start the dev-server 126 | -h, --help output usage information 127 | ``` 128 | 129 | --- 130 | 131 | ## omni start 132 | 133 | ### options 134 | ```shell 135 | Usage: omni start [options] 136 | 137 | omni start [-p ] [-H ] [-P ] 138 | 139 | Options: 140 | -p, --port start the prod-server according to the specified port 141 | -H, --hostname start the prod-server according to the specified hostname 142 | -P, --path the workpath for start the prod-server 143 | -h, --help output usage information 144 | ``` 145 | 146 | --- 147 | 148 | ## omni new 149 | 150 | ### options 151 | ```shell 152 | Usage: omni new [name] [options] 153 | 154 | omni new [name] [-f | -c] [-P ] 155 | 156 | Arguments: 157 | 158 | name optional! The name of component. 159 | 160 | Options: 161 | -f, --function create a React-Function-Component 162 | -c, --class create a React-Class-Component 163 | -r, --render create a Vue-Render-Function 164 | -s, --single create a Vue-Single-File-Component 165 | -P, --path the workpath for create component 166 | -h, --help display help for command 167 | ``` 168 | 169 | --- 170 | 171 | ## omni build 172 | 173 | ### options 174 | ```shell 175 | Usage: omni build [options] 176 | 177 | build your project according to the [omni.config.js]'s build field 178 | 179 | Options: 180 | -c, --config specify the path of config file 181 | -n, --no-verify bypass all pre-check before building 182 | -P, --path the workpath for build project 183 | -h, --help output usage information 184 | ``` 185 | 186 | --- 187 | 188 | ## omni release 189 | 190 | ### options 191 | ```shell 192 | Usage: omni release [options] 193 | 194 | publish your project according to the [omni.config.js]'s release field 195 | 196 | Options: 197 | -a, --automatic automatic iteration version 198 | -i, --ignore ignore automatic iteration version 199 | -m, --manual manual iteration version 200 | -t, --tag the tag will add to npm-package 201 | -n, --no-verify bypass unit-test eslint and stylelint check 202 | -P, --path the workpath for release project 203 | -h, --help output usage information 204 | ``` 205 | 206 | --- 207 | 208 | ## API Docs 209 | click [here](./docs/DEV.md) 210 | 211 | ## License 212 | 213 | Copyright (c) 2019 [Bobby.li](https://github.com/BobbyLH) 214 | 215 | Released under the MIT License -------------------------------------------------------------------------------- /bin/omni: -------------------------------------------------------------------------------- 1 | ../lib/node_modules/omni-door/bin/omni-door.js -------------------------------------------------------------------------------- /bin/omni-door.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | require('../lib/commands/index'); -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | const Configuration = { 2 | formatter: '@commitlint/format', 3 | /* 4 | * Any rules defined here will override rules from @commitlint/config-conventional 5 | */ 6 | rules: { 7 | 'type-enum': [2, 'always', [ 8 | '[OMNI-DOOR]', 9 | 'feat', 10 | 'feature', 11 | 'fix', 12 | 'hotfix', 13 | 'docs', 14 | 'style', 15 | 'refactor', 16 | 'test', 17 | 'revert', 18 | 'update', 19 | 'upgrade', 20 | 'modify', 21 | 'merge', 22 | 'chore', 23 | 'optimization', 24 | 'build', 25 | 'perf' 26 | ]] 27 | }, 28 | /* 29 | * Functions that return true if commitlint should ignore the given message. 30 | */ 31 | ignores: [ 32 | commit => { 33 | const regExp = /^Merge branch.+/; 34 | return regExp.test(commit); 35 | } 36 | ], 37 | /* 38 | * Whether commitlint uses the default ignore rules. 39 | */ 40 | defaultIgnores: true 41 | }; 42 | 43 | module.exports = Configuration; -------------------------------------------------------------------------------- /docs/CHANGELOG.zh-CN.md: -------------------------------------------------------------------------------- 1 | # 变更日志 2 | 3 | [English](./CHANGELOG.md) | 简体中文 4 | 5 | # v3 6 | ## v3.1.x 7 | ### v3.1.1 8 | 1. 「fix」`omni build` rollup 配置 9 | 10 | ### v3.1.0 11 | 1. 「update」`omni build` rollup 移除旧的插件支持,并升级配置 12 | 13 | 2. 「update」`omni build` rollup 的 configuration 使用函数作为参数 14 | 15 | ## v3.0.x 16 | ### v3.0.3 17 | 1. 「update」`omni init` husky 初始化 18 | 19 | ### v3.0.2 20 | 1. 「update」`omni build` tsc esm 构建 module 设置为 esnext 21 | 22 | ### v3.0.1 23 | 1. 「update」`omni init` 在 husky init 之前添加 `git init` 命令 24 | 25 | ### v3.0.0 26 | 1. 「update」`omni init` react 组件库移除 docz, styleguidist, bisheng 等demo文档的支持 27 | 28 | 2. 「update」`omni init` react-ssr 移除 koa+next 作为SSR服务的支持 29 | 30 | 3. 「update」支持 react@19, next@15 以及其他依赖的升级 31 | 32 | --- 33 | 34 | # v2 35 | ## v2.9.x 36 | ### v2.9.17 37 | 1. 「update」`omni release` 没有设置 npm 仓库地址也能进行 tag 选择 38 | 39 | ### v2.9.16 40 | 1. 「chore」`omni release` rc 标签名称 41 | 42 | ### v2.9.15 43 | 1. 「fix」`omni build` rollup ESM模块构建参数 44 | 45 | ### v2.9.14 46 | 1. 「fix」`omni build` rollup 多入口文件构建问题 47 | 48 | ### v2.9.13 49 | 1. 「update」`omni dev` webpack 配置支持函数的形式 50 | 51 | ### v2.9.12 52 | 1. 「update」`omni release` 当 `npm` 字段未设置的时候,无法更改版本号 53 | 54 | ### v2.9.11 55 | 1. 「update」`omni build` rollup 配置 56 | 57 | ### v2.9.10 58 | 1. 「fix」`omni dev` dumi 开发服务命令 59 | 60 | ### v2.9.9 61 | 1. 「fix」`omni build` vue 文件没有生成声明文件 62 | 2. 「fix」`omni build` `.vue` 后缀转换成 `.js` 63 | 64 | ### v2.9.8 65 | 1. 「update」`omni build` 用 ts-patch 替代 ttypescript 66 | 67 | ### v2.9.7 68 | 1. 「update」`omni build` gulp 排除 `*.{demo,test,stories}.{vue,ts,tsx,js,jsx}` 文件构建 69 | 70 | ### v2.9.6 71 | 1. 「feat」`omni new` 指定模版的版本 72 | 73 | ### v2.9.5 74 | 1. 「feat」`omni init` 指定模版的版本 75 | 76 | ### v2.9.4 77 | 1. 「chore」`omni init` 添加 init 命令日志 78 | 79 | ### v2.9.3 80 | 1. 「update」`omni dev` storybook 开发命令变更 81 | 82 | ### v2.9.2 83 | 1. 「update」`omni build` gulp-sass 手动引入 sass 84 | 85 | ### v2.9.1 86 | 1. 「update」`omni init` 添加 deprecated 和 recommend 提示 87 | 88 | 89 | ### v2.9.0 90 | 1. 「chore」升级 express 和 node-sass 91 | 92 | ## v2.8.x 93 | ### v2.8.2 94 | 1. 「fix」`omni start` 移除最新版本提示避免网络阻塞 95 | 96 | ### v2.8.1 97 | 1. 「optimization」node 进程异常信号监听 98 | 99 | ### v2.8.0 100 | 1. 「feat」中间件支持 http 方法 101 | 102 | 2. 「chore」升级 @omni-door/utils 103 | 104 | ## v2.7.x 105 | ### v2.7.5 106 | 1. 「fix」`koa-next` 服务中间件 107 | 108 | ### v2.7.4 109 | 1. 「update」移除 `handleKoaApp` 的支持 110 | 111 | 2. 「update」新增 `cors` 的支持 112 | 113 | ### v2.7.3 114 | 1. 「update」`ssr-react` 下的 `koa-next` 服务,支持通过 `handleKoaApp` 回调操作KoaApp 115 | 116 | ### v2.7.2 117 | 1. 「fix」`omni start` 缺失 middleware 和 https 参数 118 | 119 | ### v2.7.1 120 | 1. 「update」`omni init` ssr-react 移除样式选择 121 | 122 | ### v2.7.0 123 | 1. 「optimization」`omni init` 和 `omni release` REPL 交互提升 124 | 125 | 2. 「upgrade」升级 @omni-door/utils 126 | 127 | 3. 「feat」支持 React@18 128 | 129 | ## v2.6.x 130 | ### v2.6.4 131 | 1. 「upgrade」inquirer | mkcert 132 | 133 | ### v2.6.2 134 | 1. 「fix」`omni build` 未能支持 `spa-react-pc` 项目 135 | 136 | ### v2.6.1 137 | 1. 「fix」`omni new` 和 `omni build` 未能支持 `spa-react-pc` 项目 138 | 139 | ### v2.6.0 140 | 1. 「feat」支持 `spa-react-pc` 项目 141 | 142 | ## v2.5.x 143 | ### v2.5.8 144 | 1. 「optimization」`omni build` 和 `omni release` log 输出优化 145 | 146 | ### v2.5.7 147 | 1. 「update」升级 *shelljs* 解决循环依赖的警告 148 | 149 | ### v2.5.6 150 | 1. 「fix」 `omni build` gulp 打包 *.vue* 文件后缀名转换 151 | 152 | 2. 「update」 `omni build` dependencies_build 依赖版本更新 153 | 154 | 3. 「optimization」 `omni release` 从版本中自动获取tag时,`rc` 会强制转换成 `latest` 155 | 156 | ### v2.5.5 157 | 1. 「optimization」 `omni build` gulp 打包自定义暴露完整配置项 158 | 159 | ### v2.5.4 160 | 1. 「optimization」 `omni build` gulp 打包支持自定义配置 161 | 162 | ### v2.5.3 163 | 1. 「fix」 `omni build` gulp 打包替换 vue-SFC 文件路径 164 | 165 | ### v2.5.2 166 | 1. 「optimization」 `omni build` gulp 打包编译 vue-SFC 文件 167 | 168 | ### v2.5.1 169 | 1. 「optimization」 `omni build` gulp 打包支持 vue SFC 170 | 171 | 2. 「feat」 `omni new` *component-vue* 项目支持 SFC 和 Render-Function 172 | 173 | ### v2.5.0 174 | 1. 「feat」 `omni build` gulp 打包支持 css 路径拼接并替换 css-minifier 175 | 176 | ## v2.4.x 177 | ### v2.4.9 178 | 1. 「update」 `omni release` 将插件的处理程序置于 git 和 npm 的操作之前 179 | 180 | ### v2.4.8 181 | 1. 「optimization」 `omni release` tag 和 plugin handler 182 | 183 | 2. 「upgrade」升级 @omni-door/utils 184 | 185 | ### v2.4.7 186 | 1. 「optimization」 `omni init` 在 *component-react* 项目中,选择 *docz* 作为 demo 框架时,检查 node 版本是否 `>= 12` 187 | 188 | ### v2.4.6 189 | 1. 「chore」 `omni new` 名称提示 190 | 191 | 2. 「optimization」 `omni dev` 打开浏览器之前先确保端口已经被占用 192 | 193 | ### v2.4.5 194 | 1. 「optimization」 `omni release` 操作git前先检查状态 195 | 196 | ### v2.4.4 197 | 1. 「update」 `omni dev` 打开浏览器延时 198 | 199 | ### v2.4.3 200 | 1. 「optimization」 `omni release` npm publish 支持两步验证(OTP) 201 | 202 | ### v2.4.2 203 | 1. 「optimization」 `omni build` rollup 自定义配置文件的参数传递 204 | 205 | ### v2.4.1 206 | 1. 「fix」 `omni init` 覆写的工作路径 207 | 208 | ### v2.4.0 209 | 1. 「optimization」 更新 cli 的提示 210 | 211 | 2. 「feat」支持 `component-vue` 项目 212 | 213 | ## v2.3.x 214 | 215 | ### v2.3.11 216 | 1. 「fix」 匹配版本号的正则表达式 217 | 218 | ### v2.3.10 219 | 1. 「optimization」 日志优化 220 | 221 | 2. 「fix」 `omni build` 无法删除在工作路径之外的文件或文件夹 222 | 223 | ### v2.3.9 224 | 1. 「optimization」 日志优化 225 | 226 | ### v2.3.8 227 | 1. 「optimization」 日志优化 228 | 229 | ### v2.3.7 230 | 1. 「optimization」 `omni release` 迭代优化 231 | 232 | ### v2.3.6 233 | 1. 「fix」 `omni build` rollup 中的 typescript插件,影响输出结果的问题 234 | 235 | ### v2.3.5 236 | 1. 「fix」 `omni release` 自动迭代版本号的 tag 不正确 237 | 238 | ### v2.3.4 239 | 1. 「optimization」 `omni release` 自动迭代版本号策略顺序优化 240 | 241 | ### v2.3.3 242 | 1. 「optimization」 `omni init` 单元测试 `spa-vue` 项目默认不选 243 | 244 | 2. 「optimization」 `omni release` 自动迭代版本号优化 245 | 246 | ### v2.3.2 247 | 1. 「optimization」 `omni init` 固定 `@omni-door/cli` 的中版本号 248 | 249 | ### v2.3.1 250 | 1. 「feat」支持 `spa-vue` 项目 251 | 252 | ### v2.3.0 253 | 1. 「optimization」`omni init`、`omni new` 新增脚手架 latest 版本的更新提示 254 | 255 | 2. 「upgrade」升级 @omni-door/utils,并替换 API 256 | 257 | ## v2.2.x 258 | ### v2.2.13 259 | 1. 「fix」`omni dev` favicon.icon 不存在的导致开发服务崩溃 260 | 261 | ### v2.2.12 262 | 1. 「upgrade」升级 @omni-door/utils 263 | 264 | ### v2.2.11 265 | 1. 「fix」`omni dev` 更改 `http-proxy-middleware` 的 API 266 | 267 | ### v2.2.10 268 | 1. 「fix」`omni init` toolkit 项目无法创建 269 | 270 | ### v2.2.9 271 | 1. 「fix」`omni dev` spa 项目 dev-server 通配符路由缺失 272 | 273 | ### v2.2.8 274 | 1. 「optimization」`omni init`、`omni new` 脚手架版本号和模板版本号同步 275 | 276 | 2. 「fix」`omni init` 包管理工具安装时的异常处理 277 | 278 | ### v2.2.7 279 | 1. 「optimization」`omni init` 新增对 REPL(命令行运行的交互式界面) 方式交互的包管理器校验 280 | 281 | 2. 「fix」`omni init` 当未选择样式文件时,正确的展示后续内容 282 | 283 | ### v2.2.6 284 | 1. 「optimization」npm-package latest 版本校验不阻塞程序运行 285 | 286 | ### v2.2.5 287 | 1. 「fix」`omni init` 对 `layout` 的值做转换 288 | 289 | ### v2.2.4 290 | 1. 「feat」`omni init` spa-react 项目支持 `layout` 选项 291 | 292 | ### v2.2.3 293 | 1. 「feat」spa-react 项目支持 webpack5 294 | 295 | 2. 「feat」spa-react 项目开发服务支持自定义 favicon 296 | 297 | ### v2.2.2 298 | 1. 「feat」`omni init` 模板的 pkj-tool 默认使用 pnpm 299 | 300 | ### v2.2.1 301 | 1. 「fix」`omni init` 项目名校验的问题 302 | 303 | ### v2.2.0 304 | 1. 「feat」`omni init` 安装工具新增 pnpm 选项,并移除 cnpm 305 | 306 | 2. 「feat」`omni init` 新增项目名规范校验 307 | 308 | 3. 「feat」新增 *最新版本 cli 安装提示* 309 | 310 | 4. 「feat」新增 *错误命令意图推测* 311 | 312 | ## v2.1.x 313 | ### v2.1.6 314 | 1. 「fix」`omni release` 自动构建参数缺失的问题. 315 | 316 | ### v2.1.5 317 | 1. 「feat」`omni release` 支持 自动构建(autoBuild). 318 | 319 | ### v2.1.4 320 | 1. 「fix」`omni build` toolkit 项目错误的从 'undefined' 或 'null' 中进行解构. 321 | 322 | ### v2.1.3 323 | 1. 「update」`omni build` toolkit 项目构建产物过滤掉忽略的文件夹 324 | 325 | ### v2.1.2 326 | 1. 「update」`omni build` 升级 toolkit 项目的 rollup 配置 327 | 328 | ### v2.1.1 329 | 1. 「fix」`omni release` 手动迭代版本号无法正确匹配 330 | 331 | ### v2.1.0 332 | 1. 「optimization」`omni build` 用 tsc 或 gulp 编译的项目,默认支持 alias 333 | 334 | 2. 「fix」`omni build` 自动安装缺少的构建依赖不全的问题 335 | 336 | ## v2.0.x 337 | ### v2.0.17 338 | 1. 「fix」`omni build` 组件项目gulp配置文件bug 339 | 340 | ### v2.0.16 341 | 1. 「fix」`omni build` 组件项目不兼容同时存在 scss less 文件的问题 342 | 343 | ### v2.0.15 344 | 1. 「optimization」`omni init` 模板的版本自动对齐脚手架的版本 345 | 346 | ### v2.0.14 347 | 1. 「optimization」优化 `omni init/release` 的日志输出 348 | 349 | ### v2.0.13 350 | 1. 「optimization」调用`omni *(commands)` 的日志输出 351 | 352 | ### v2.0.12 353 | 1. 「fix」`omni release` 因为 cache 导致获取当前版本号不正确的问题 354 | 355 | ### v2.0.11 356 | 1. 「fix」`omni release` 在命令行中自定义版本号,自动设置 `tag` 的优先级问题,如在 package.json 中原来的版本号为 `0.0.19`,而后用命令行迭代 `omni release -m 0.0.20-alpha.1`,此时自动判断 `tag` 应为 `alpha` 而非 `latest` 357 | 358 | 2. 「optimization」`omni release` publish 到 npm 仓库的日志优化 359 | 360 | ### v2.0.10 361 | 1. 「optimization」 `release` 新增 `autoTag` 字段,设置为 `true` 时,发布到npm仓库时会自动根据当前版本号设定tag 362 | 363 | ### v2.0.9 364 | 1. 「optimization」`omni release` 自定义版本号会自动根据含带的字母确定默认的 tag 365 | 366 | ### v2.0.8 367 | 1. 「fix」`omni release` 自定义版本号因正则匹配失效的问题 368 | 369 | 2. 「optimization」`omni release` 默认 tag 取自现版本号的字母后缀 370 | 371 | ### v2.0.7 372 | 1. 「fix」`omni build` rollup.config.js namedExports 补全 react 和 react-dom 的 API 373 | 374 | ### v2.0.6 375 | 1. 「fix」`omni start` 依赖引用的问题 376 | 377 | ### v2.0.5 378 | 1. 「fix」`omni dev` 对于 react-ssr 项目的错误判断 379 | 380 | ### v2.0.4 381 | 1. 「feat」`omni *` 所有命令均支持 `-P ` 选项用于自定义工作路径 382 | 383 | ### v2.0.3 384 | 1. 「update」[newTpl、initial] 支持 `tplPkjTag` 选项 385 | 386 | ### v2.0.2 387 | 1. 「fix」修复 express typescript 的问题,[点击详见issue](https://github.com/DefinitelyTyped/DefinitelyTyped/issues/47339#issuecomment-691800846) 388 | 389 | 2. 「docs」变更日志优化 390 | 391 | ### v2.0.1 392 | 1. 「fix」`omni dev` 修复 ssr-react 项目无法启动的问题 393 | 394 | ### v2.0.0 395 | 1. 「feat」`omni init` 支持 ssr-react 项目类型 396 | 397 | 2. 「optimization」`omni init` 优化 398 | 399 | 3. 「feat」新增 `omni start` 命令 400 | 401 | --- 402 | 403 | # v1 404 | ## v1.4.x 405 | ### v1.4.4 406 | 1. 「optimization」 `omni init` 固定 template 的版本号 407 | 408 | ### v1.4.3 409 | 1. 「feat」 `omni init` 支持 `tplPkjTag` 选项 410 | 411 | ### v1.4.2 412 | 1. 「perf」`omni release` 优化了 require package.json 的过程 413 | 414 | 2. 「chore」升级 @omni-door/utils 415 | 416 | ### v1.4.1 417 | 1. 「chore」固定依赖的版本号 418 | 419 | 2. 「fix」`omni build` typescript 被禁用的情况处理 420 | 421 | ### v1.4.0 422 | 1. 「feat」`omni dev` 开发服务支持 `https` 的协议 423 | 424 | ## v1.3.x 425 | ### v1.3.9 426 | 1. 「update」`omni dev` 开发服务 host 默认更变为 `0.0.0.0` 427 | 428 | ### v1.3.8 429 | 1. 「update」`omni build` 的 reserve.assets 字段的类型更变为: `(string | { srcPath: string; relativePath?: string; })[]` 430 | 431 | ### v1.3.7 432 | 1. 「docs」`omni release` 文本调整 433 | 434 | 2. 「optimization」`omni build` gulp 打包优化 435 | 436 | ### v1.3.6 437 | 1. 「fix」升级 @omni-door/utils,解决 logTime 前缀不正确的问题 438 | 439 | ### v1.3.5 440 | 1. 「feat」`omni release` 新增 -a / --automatic 选项,并支持REPL(read-eval-print loop) 441 | 442 | ### v1.3.4 443 | 1. 「feat」`omni dev` storybook 启动开发服务添加 --quiet 选项 444 | 445 | 2. 「feat」`omni dev` 支持 host 配置 446 | 447 | ### v1.3.3 448 | 1. 「chore」`omni dev` 整合 toolkit 和 component-library 项目的开发服务 449 | 450 | ### v1.3.2 451 | 1. 「chore」`omni build` 固定 gulp 构建的 cwd 路径 同时支持 component-libaray 输出全量的css 452 | 453 | ### v1.3.1 454 | 1. 「feat」`omni build` 支持使用 gulp 来打包组件库项目 455 | 456 | 2. 「chore」升级 @omni-door/utils 457 | 458 | 3. 「fix」`omni new [name]` 的 name 可能是 `undefined` 的问题 459 | 460 | ### v1.3.0 461 | 1. 「update」`omni dev -p - port` 改为必填 462 | 463 | 2. 「update」`omni new [name] [option]` 的 name 改为选填,并支持REPL(read-eval-print loop) 464 | 465 | ## v1.2.x 466 | ### v1.2.38 467 | 1. 「fix」修复项目 package.json 可能不存在的问题 468 | 469 | 2. 「feat」`omni init` 新增支持 `pkjFieldName` 字段 470 | 471 | ### v1.2.37 472 | 1. 「feat」`omni build` 新增支持 `pkjFieldName` 字段 473 | 474 | ### v1.2.36 475 | 1. 「chore」升级 @omni-door/utils 476 | 477 | 2. 「feat」在 package.json 中支持 omni 字段,使之能够自定义配置文件(omni.config.js)的路径 478 | 479 | ### v1.2.35 480 | 1. 「feat」`omni new` 新增对传入的模块名做校验: 481 | 482 | - 模块名大于等于2个字符; 483 | 484 | - 第一个字符只能由 下划线_ 或 大小写字母 组成; 485 | 486 | - 后续字符只能由 数字、下划线_、大小写字母 组成! 487 | 488 | ### v1.2.34 489 | 1. 「feat」`omni initial` 支持 custom initPath 490 | 491 | ### v1.2.33 492 | 1. 「fix」`omni release` 删除会导致版本号不正确的缓存 493 | 494 | ### v1.2.32 495 | 1. 「fix」修复构建/发布完成后未退出程序的问题 496 | 497 | ### v1.2.31 498 | 1. 「fix」修复 `omni release` linux环境下版本无法迭代的问题 499 | 500 | ### v1.2.30 501 | 1. 「feat」`omni dev` proxy 支持传入 `function` 类型 502 | 503 | ### v1.2.29 504 | 1. 「feat」`omni dev` middleware 支持传入 `function` 类型 505 | 506 | 2. 「chore」升级 @omni-door/utils 507 | 508 | ### v1.2.28 509 | 1. 「update」 `dev` 字段新增 `devMiddlewareOptions` 属性,对应 `webpack-dev-middleware` [Options](https://github.com/webpack/webpack-dev-middleware#options) 510 | 511 | ### v1.2.27 512 | 1. 「chore」依赖升级 commander@5.1.0 513 | 514 | ### v1.2.26 515 | 1. 「fix」commandar 自动输出 help 信息的逻辑 516 | 517 | ### v1.2.25 518 | 1. 「chore」依赖升级 commander@4.1.0 519 | 520 | 2. 「chore」`omni init` 提升参数 `tplPkjParams` 的权重 521 | 522 | ### v1.2.24 523 | 1. 「fix」依赖固定版本号 524 | 525 | ### v1.2.23 526 | 1. 「fix」修复重名文件夹的检测的问题 527 | 528 | 2. 「feat」`omni init` 项目重名支持重新输入,最大10次 529 | 530 | ### v1.2.22 531 | 1. 「feat」`omni init` 初始化时新增对重名文件夹的检测和覆盖确认提示 532 | 533 | ### v1.2.21 534 | 1. 「fix」初始化时选择 no-install 不会出现安装工具的选择 535 | 536 | ### v1.2.20 537 | 1. 「feat」`omni init` 新增初始化时不安装依赖的选项 538 | 539 | ### v1.2.19 540 | 1. 「chore」升级 @omni-door/utils 541 | 542 | 2. 「feat」plugin-new 回调参数新增 `模板来源(tplSource)` 字段 543 | 544 | 3. 「feat」plugin-build 回调参数新增 `自定义的构建配置(buildConfig)` 字段 545 | 546 | 4. 「feat」plugin-release 回调参数新增 `版本迭代策略(versionIterTactic)` 字段 547 | 548 | ### v1.2.18 549 | 1. 「fix」修复 logo 未被正确替换的问题 550 | 551 | ### v1.2.17 552 | 1. 「feat」`omni new` 移除组件名自动大写第一个字母的逻辑 553 | 554 | 2. 「chore」`omni build` toolkit 打包模板修改 555 | 556 | 3. 「fix」`omni release` 修复commitlint遗漏verify参数的问题 557 | 558 | ### v1.2.16 559 | 1. 「chore」升级 @omni-door/utils 560 | 561 | 2. 「chore」移除错误输出信息中多余的 `JSON.stringify` 562 | 563 | 3. 「update」为 `process` 绑定 `'SIGINT', 'SIGQUIT', 'SIGTERM'` 事件,监听退出程序 564 | 565 | 4. 「fix」`omni dev/build` 修复 `require` 无法获取工作路径的依赖的问题 566 | 567 | ### v1.2.15 568 | 1. 「update」优化异常操作处理 569 | 570 | 2. 「chore」@omni-door/utils 替换 @omni-door/tpl-utils 571 | 572 | 3. 「feat」`omni dev` 采用 create-react-app [复用已打开浏览器的tab策略](https://github.com/facebook/create-react-app/blob/32eebfeb7f5cdf93a790318eb76b81bb2927458e/packages/react-dev-utils/openChrome.applescript) 573 | 574 | ### v1.2.14 575 | 1. 「update」`omni build/release` 支持 prettier 飞行检查 576 | 577 | 2. 「update」`omni build` 新增对于自定义配置文件是否存在的校验 578 | 579 | 3. 「feat」`omni init` component-react项目新增 styleguidist Demo框架的选择 580 | 581 | ### v1.2.13 582 | 1. 「fix」`omni build` 当 catch[try/catch] 到错误时,停止 loading 状态 583 | 584 | 2. 「update」`omni init` 集成 prettier 585 | 586 | ### v1.2.12 587 | 1. 「feat」`omni build` 支持自定义配置文件路径 588 | 589 | 2. 「fix」`omni build` 修复输出资源是否存在的校验的bug 590 | 591 | ### v1.2.11 592 | 1. 「feat」`omni build` 支持 "hash"、"chunkhash"、"contenthash" 593 | 594 | 2. 「update」升级依赖 @omni-door/tpl-utils 595 | 596 | ### v1.2.10 597 | 1. 「feat」`omni initial` before 和 after 支持异步执行 598 | 599 | 2. 「feat」`omni new` 支持 before 和 after 回调 600 | 601 | ### v1.2.9 602 | 1. 「feat」`omni dev` 新增 Signals listener 603 | 604 | 2. 「update」升级依赖 @omni-door/tpl-utils 605 | 606 | 3. 「feat」`omni dev` 修复 history API 路由不正确的问题 607 | 608 | ### v1.2.8 609 | 1. 「update」插件新增 `options` 参数,并更新 type 定义 610 | 611 | ### v1.2.7 612 | 1. 「update」`omni release` 发布过程的异常处理逻辑更改 613 | 614 | 2. 「update」`omni release` 发布到npm仓库显示指定registry 615 | 616 | 3. 「update」`omni release` 发布到git仓库避免修改origin 617 | 618 | 4. 「feat」`omni release` 新增 tag 选项 619 | 620 | 5. 「update」插件运行新增try/catch异常处理 621 | 622 | ### v1.2.6 623 | 1. 「feat」[initial] 支持 `tplPkjParams` 选项 624 | 625 | 2. 「update」`omni build` 移除自动发布的时间日志 626 | 627 | ### v1.2.5 628 | 1. 「fix」`omni release` 修复版本检测的问题 629 | 630 | ### v1.2.4 631 | 1. 「fix」`omni release` 修复 分支检测和npm发布log前缀不正确的问题 632 | 633 | 2. 「update」升级 @omni-door/tpl-utils 依赖 634 | 635 | ### v1.2.3 636 | 1. 「docs」`omni release` 汉化翻译 637 | 638 | 2. 「update」升级 @omni-door/tpl-utils 依赖 639 | 640 | 3. 「feat」`omni initial/build/release)` 新增耗时日志输出 641 | 642 | ### v1.2.2 643 | 1. 「fix」`omni build` rollup 打包新增未正确发现入口文件的提示 644 | 645 | 2. 「feat」[newTpl、initial] 支持 `tplPkj` 选项 646 | 647 | 3. 「update」`omni new` 恢复 plugin 的支持 648 | 649 | ### v1.2.1 650 | 1. 「update」拆分模板 651 | - @omni-door/tpl-spa-react 652 | 653 | - @omni-door/tpl-toolkit 654 | 655 | - @omni-door/tpl-component-react 656 | 657 | ### v1.2.0 658 | 1. 「update」命名规范 659 | 660 | ## v1.1.x 661 | ### v1.1.3 662 | 1. 「update」 修复无法由于循环引用导致webpack配置文件无法获取正确值的问题 663 | 664 | 2. 「update」 新增 html-webpack-plugin 665 | 666 | 3. 「update」 将css、less、scss等样式文件的处理迁移至此 667 | 668 | ### v1.1.2 669 | 1. 「update」 build 新增 hash 字段控制打包资源名是否添加哈希 670 | 671 | 2. 「docs」新增 omni.config.js 详解文档 672 | 673 | ### v1.1.1 674 | 1. 「update」 修复函数的调用栈在转化成字符串后丢失,无法正确获取到变量值的问题 675 | 676 | ### v1.1.0 677 | 1. 「update」 新增 resolutions 字段,解决 (依赖重复导致TS报错)[https://stackoverflow.com/questions/52399839/duplicate-identifier-librarymanagedattributes] 的问题 678 | 679 | 2. 「update」`onmi init` stable 策略版本更新 680 | 681 | 3. 「update」`onmi init` component-react 和 toolkit 默认开启单元测试 682 | 683 | ## v1.0.x 684 | ### v1.0.10 685 | 1. 「fix」[node-version-check] 修复node版本检测问题 686 | 687 | 2. 「feat」`onmi init` 动态更新初始化总步数 688 | 689 | 3. 「fix」`onmi build` 修复错误日志丢失的问题 690 | 691 | 4. 「fix」`onmi release` 修复错误日志丢失的问题 692 | 693 | ### v1.0.9 694 | 1. 「update」 新增 stylelint 检测启动参数 {--allow-empty-input} 695 | 696 | 2. 「update」 新增 jest 单元测试启动参数 {--passWithNoTests} 697 | 698 | 3. 「update」 移除 allowJs、experimentalDecorators 的注释,更新 {exclude} 字段 699 | 700 | 4. 「update」 新增 webpack-bundle-analyzer 插件 701 | 702 | 5. 「update」<.npmignore> 生成基于项目类型 703 | 704 | 6. 「update」`onmi init` 支持样式多选 705 | 706 | ### v1.0.8 707 | 1. 「fix」`omni init` 修复自定义Logo显示不正确问题 708 | 709 | 2. 「chore」`omni init` 支持移除内置的依赖项 710 | 711 | ### v1.0.7 712 | 1. 「update」 模板新增对项目类型判断 713 | 714 | 2. 「update」 注释变更 715 | 716 | 3. 「update」 优化生产环境打包 717 | 718 | 4. 「update」[dev-server] 新增运行时错误捕获 719 | 720 | 5. 「fix」`omni new` 修复无法识别参数的问题 721 | 722 | ### v1.0.6 723 | 1. 「feat」新增对 node 版本做检测,要求 node >= 10.13.0 724 | 725 | ### v1.0.5 726 | 1. 「update」, 新增 `dev.middleware` 字段 727 | 728 | 2. 「update」, 新增 `dev.logLevel` 字段 729 | 730 | 3. 「update」, `dev.webpack_config` 更变为 `dev.webpack` 731 | 732 | ### v1.0.4 733 | 1. 「feat」[dev-server] 启动进程优化 734 | 735 | 2. 「feat」`omni init` 更改 `omni init -s` 和 `omni init --simple` 为 `omni init -b` 和 `omni init -basic` 736 | 737 | ### v1.0.3 738 | 1. 「optimization」`omni build` 自动发布优化,摆脱依赖npm script,直接调用release方法 739 | 740 | ### v1.0.2 741 | 1. 「feat」toolkit 项目支持使用 tsc 打包 742 | 743 | ### v1.0.1 744 | 1. 「chore」优化了log日志的输出 745 | 746 | ### v1.0.0 747 | 1. 「feat」新增 `omni dev` 命令以支持基于 Express + webpack-dev-server 的开发服务 748 | 749 | --- 750 | 751 | # v0 752 | ## v0.2.x 753 | 1. 可用版本,请使用最新版本 754 | 755 | ## v0.1.x 756 | 1. 可用版本,请使用最新版本 757 | 758 | ## v0.0.x 759 | 1. 非正式版本,请使用最新版本 760 | 761 | --- 762 | 763 | **标签的含义**: 764 | - 「xxx」 - 类型 765 | 766 | - \ - 模板 767 | 768 | - [xxx] - 行为、特性、功能 -------------------------------------------------------------------------------- /docs/DEV.md: -------------------------------------------------------------------------------- 1 | # Docs 2 | 3 | English | [简体中文](./DEV.zh-CN.md) 4 | 5 | **@omni-door/cli** provides the ability of secondary development, which is implemented through plug-in or import form。 6 | 7 | --- 8 | 9 | ## Plugin 10 | The Plugin provides the third-party developers with the ability to perform multiple tasks in each lifecycle of the project. Please make sure that the Plugin writing meets the type definition of `type OmniPlugin`. 11 | 12 | ### Write a plugin for gzip when execution release stage 13 | 14 | ```js 15 | import pack from 'pack'; // pseudo code for gzip 16 | 17 | export default function (config, options) { 18 | return { 19 | name: '@scope/my-release-plugin', 20 | stage: 'release', 21 | handler: config => new Promise((resolve, reject) => { 22 | const { build } = config; 23 | const srcPath = build.outDir; 24 | const destPath = path.resolve(process.cwd(), 'dist.zip'); 25 | // gzip 26 | pack(srcPath, destPath, function (res) { 27 | if (res === 'success') { 28 | return resolve(); 29 | } 30 | return reject(); 31 | }); 32 | }) 33 | }); 34 | } 35 | ``` 36 | 37 | ### Type of plugin 38 | ```ts 39 | type PLUGIN_STAGE = 'new' | 'build' | 'release'; 40 | 41 | interface OmniPlugin { 42 | name: string; 43 | stage: T; 44 | handler: PluginHandler; 45 | } 46 | 47 | interface PluginHandler { 48 | ( 49 | config: config: Omit, 50 | options?: T extends 'new' ? OptionTemplate : T extends 'build' ? OptionBuild : OptionRelease 51 | ): Promise; 52 | } 53 | 54 | // stage of "new" 55 | type OptionTemplate = { 56 | componentName: string; 57 | componentType: 'function' | 'class'; 58 | tplSource: string; 59 | }; 60 | 61 | // stage of "build" 62 | type OptionBuild = { 63 | verify?: boolean; 64 | buildConfig?: string; 65 | }; 66 | 67 | // stage of "release" 68 | type OptionRelease = { 69 | version: string; 70 | versionIterTactic: 'ignore' | 'manual' | 'auto'; 71 | verify?: boolean; 72 | tag?: string; 73 | }; 74 | ``` 75 | 76 | ### Type of OmniConfig 77 | ```ts 78 | import type { Configuration } from 'webpack'; 79 | import type { Config } from 'http-proxy-middleware'; 80 | import type { Options as DevMiddlewareOptions } from 'webpack-dev-middleware'; 81 | import type { Request, Response, NextFunction } from 'express'; 82 | import type * as KoaApp from 'koa'; 83 | 84 | type ANY_OBJECT = { [propName: string]: any }; 85 | type ServerType = 'storybook' | 'default'; 86 | type BUILD = 'webpack' | 'rollup' | 'gulp' | 'tsc' | 'next' | ''; 87 | type NPM = 'npm' | 'yarn' | 'pnpm'; 88 | type PROJECT_TYPE = 'spa-react' | 'spa-react-pc' | 'spa-vue' | 'ssr-react' | 'component-react' | 'component-vue' | 'toolkit'; 89 | type STYLE = 'less' | 'scss' | 'css' | 'all' | ''; 90 | type SSR_SERVER = 'next-app' | 'next-pages' | 'nuxt' | ''; 91 | type Method = 'get' | 'GET' | 'post' | 'POST' | 'put' | 'PUT' | 'del' | 'DEL'; 92 | 93 | type OmniServer = { 94 | port?: number; 95 | host?: string; 96 | https?: boolean | { key: string; cert: string; }; 97 | CA?: { 98 | organization?: string; 99 | countryCode?: string; 100 | state?: string; 101 | locality?: string; 102 | validityDays?: number; 103 | }; 104 | proxy?: { 105 | route: PathParams; 106 | config: Config; 107 | }[]; 108 | middleware?: { 109 | route: PathParams; 110 | callback: MiddleWareCallback; 111 | method?: Method; 112 | }[]; 113 | cors?: { 114 | origin?: string | ((ctx: KoaCtx) => string); 115 | allowMethods?: string | string[]; 116 | exposeHeaders?: string | string[]; 117 | allowHeaders?: string | string[]; 118 | maxAge?: string | number; 119 | credentials?: boolean | ((ctx: KoaCtx) => string); 120 | keepHeadersOnError?: boolean; 121 | secureContext?: boolean; 122 | privateNetworkAccess?: boolean; 123 | }; 124 | nextRouter?: NextRouter; 125 | }; 126 | 127 | interface OmniBaseConfig { 128 | type: PROJECT_TYPE; 129 | dev?: OmniServer & { 130 | devMiddlewareOptions?: Partial; 131 | webpack?: Configuration | (() => Configuration); 132 | configuration?: (config: ANY_OBJECT) => ANY_OBJECT; 133 | serverType?: ServerType; 134 | favicon?: string; 135 | }; 136 | server?: OmniServer & { serverType?: SSR_SERVER; }; 137 | build: { 138 | autoRelease?: boolean; 139 | srcDir: string; 140 | outDir: string; 141 | esmDir?: string; 142 | hash?: boolean | HASH; 143 | configuration?: (config: ANY_OBJECT) => ANY_OBJECT; 144 | tool?: Exclude; 145 | preflight?: { 146 | typescript?: boolean; 147 | test?: boolean; 148 | eslint?: boolean; 149 | prettier?: boolean; 150 | stylelint?: boolean; 151 | }; 152 | reserve?: { 153 | style?: boolean; 154 | assets?: (string | { srcPath: string; relativePath?: string; })[]; 155 | }; 156 | }; 157 | release: { 158 | git?: string; 159 | npm?: string | boolean; 160 | autoBuild?: boolean; 161 | autoTag?: boolean; 162 | preflight?: { 163 | test?: boolean; 164 | eslint?: boolean; 165 | prettier?: boolean; 166 | stylelint?: boolean; 167 | commitlint?: boolean; 168 | branch?: string; 169 | }; 170 | }; 171 | template: { 172 | root: string; 173 | test?: boolean; 174 | typescript?: boolean; 175 | stylesheet?: STYLE; 176 | readme?: MARKDOWN | boolean; 177 | }; 178 | plugins?: OmniPlugin[]; 179 | } 180 | 181 | interface OmniRollupConfig extends OmniBaseConfig { 182 | build: OmniBaseConfig['build'] & { 183 | tool: Extract; 184 | configuration?: (getConfig: (bundle: boolean) => ANY_OBJECT) => ANY_OBJECT; 185 | }; 186 | } 187 | 188 | type OmniConfig = OmniBaseConfig | OmniRollupConfig; 189 | ``` 190 | 191 | - `name`: the name of plugin 192 | 193 | - `stage`: the stage of plugin execution 194 | 195 | - `handler`: executed callback function, returned in the form of `promise` 196 | 197 | - through `import { PluginHandler_Release } from '@omni-door/cli/lib/index.d';` to get the type that *handler* should satisfy 198 | - support: `PluginHandler_Dev`, `PluginHandler_Build`, `PluginHandler_Release`, `PluginHandler_New` 199 | --- 200 | 201 | ## The commands by import 202 | - `import { initial } from '@omni-door/cli';`: get the initial instruction, then call with paramter directly: 203 | 204 | ```ts 205 | initial({ 206 | standard: true // initial a standard project 207 | }, { 208 | // before the project initial 209 | before: dir_name => ({ 210 | create_dir: false // avoid create new dir 211 | }), 212 | // after finish the project initial 213 | after: () => { 214 | return { 215 | success: true, 216 | msg: 'build success!' 217 | }; 218 | }, 219 | // custom the installing template 220 | tplPkj: '@omni-door/tpl-toolkit', 221 | // custom the template parameters 222 | tplPkjParams: ['bid=55232', 'test=false'], 223 | // custom the name of omni.config.js file 224 | configFileName: 'custom.config.js' 225 | }); 226 | ``` 227 | 228 | - Other phases commands: `import { dev, new as newTpl, build, release } from '@omni-door/cli';` 229 | 230 | - Support custom logo and brand: 231 | ```ts 232 | import { setLogo, setBrand } from '@omni-door/cli'; 233 | 234 | setLogo('😄'); 235 | setBrand('some_prefix:'); 236 | ``` -------------------------------------------------------------------------------- /docs/DEV.zh-CN.md: -------------------------------------------------------------------------------- 1 | # 接入文档 2 | 3 | [English](./DEV.md) | 简体中文 4 | 5 | @omni-door/cli 提供了二次开发的能力,通过 plugin 或者 import 到项目中实现。 6 | 7 | --- 8 | 9 | ## Plugin 10 | 插件向第三方开发者提供了脚手架在项目各个周期的执行多元化任务的能力,插件的编写请务必满足 `type OmniPlugin` 的类型定义。 11 | 12 | ### 编写一个 release 阶段做压缩打包的插件 13 | 14 | ```js 15 | import pack from 'pack'; // 压缩的伪代码 16 | 17 | export default function (config, options) { 18 | return { 19 | name: '@scope/my-release-plugin', 20 | stage: 'release', 21 | handler: config => new Promise((resolve, reject) => { 22 | const { build } = config; 23 | const srcPath = build.outDir; 24 | const destPath = path.resolve(process.cwd(), 'dist.zip'); 25 | // 压缩打包 26 | pack(srcPath, destPath, function (res) { 27 | if (res === 'success') { 28 | return resolve(); 29 | } 30 | return reject(); 31 | }); 32 | }) 33 | }); 34 | } 35 | ``` 36 | 37 | ### plugin 的类型 38 | ```ts 39 | type PLUGIN_STAGE = 'new' | 'build' | 'release'; 40 | 41 | interface OmniPlugin { 42 | name: string; 43 | stage: T; 44 | handler: PluginHandler; 45 | } 46 | 47 | interface PluginHandler { 48 | ( 49 | config: config: Omit, 50 | options?: T extends 'new' ? OptionTemplate : T extends 'build' ? OptionBuild : OptionRelease 51 | ): Promise; 52 | } 53 | 54 | // "new" 阶段 55 | type OptionTemplate = { 56 | componentName: string; 57 | componentType: 'function' | 'class'; 58 | tplSource: string; 59 | }; 60 | 61 | // "build" 阶段 62 | type OptionBuild = { 63 | verify?: boolean; 64 | buildConfig?: string; 65 | }; 66 | 67 | // "release" 阶段 68 | type OptionRelease = { 69 | version: string; 70 | versionIterTactic: 'ignore' | 'manual' | 'auto'; 71 | verify?: boolean; 72 | tag?: string; 73 | }; 74 | ``` 75 | 76 | ### OmniConfig 的类型 77 | ```ts 78 | import type { Configuration } from 'webpack'; 79 | import type { Config } from 'http-proxy-middleware'; 80 | import type { Options as DevMiddlewareOptions } from 'webpack-dev-middleware'; 81 | import type { Request, Response, NextFunction } from 'express'; 82 | import type * as KoaApp from 'koa'; 83 | 84 | type ANY_OBJECT = { [propName: string]: any }; 85 | type ServerType = 'storybook' | 'dumi' | 'default'; 86 | type BUILD = 'webpack' | 'rollup' | 'gulp' | 'tsc' | 'next' | ''; 87 | type NPM = 'npm' | 'yarn' | 'pnpm'; 88 | type PROJECT_TYPE = 'spa-react' | 'spa-react-pc' | 'spa-vue' | 'ssr-react' | 'component-react' | 'component-vue' | 'toolkit'; 89 | type STYLE = 'less' | 'scss' | 'css' | 'all' | ''; 90 | type SSR_SERVER = 'next-app' | 'next-pages' | 'nuxt' | ''; 91 | type Method = 'get' | 'GET' | 'post' | 'POST' | 'put' | 'PUT' | 'del' | 'DEL'; 92 | 93 | type OmniServer = { 94 | port?: number; 95 | host?: string; 96 | https?: boolean | { key: string; cert: string; }; 97 | CA?: { 98 | organization?: string; 99 | countryCode?: string; 100 | state?: string; 101 | locality?: string; 102 | validityDays?: number; 103 | }; 104 | proxy?: { 105 | route: PathParams; 106 | config: Config; 107 | }[]; 108 | middleware?: { 109 | route: PathParams; 110 | callback: MiddleWareCallback; 111 | method?: Method; 112 | }[]; 113 | cors?: { 114 | origin?: string | ((ctx: KoaCtx) => string); 115 | allowMethods?: string | string[]; 116 | exposeHeaders?: string | string[]; 117 | allowHeaders?: string | string[]; 118 | maxAge?: string | number; 119 | credentials?: boolean | ((ctx: KoaCtx) => string); 120 | keepHeadersOnError?: boolean; 121 | secureContext?: boolean; 122 | privateNetworkAccess?: boolean; 123 | }; 124 | nextRouter?: NextRouter; 125 | }; 126 | 127 | interface OmniBaseConfig { 128 | type: PROJECT_TYPE; 129 | dev?: OmniServer & { 130 | devMiddlewareOptions?: Partial; 131 | webpack?: Configuration | (() => Configuration); 132 | configuration?: (config: ANY_OBJECT) => ANY_OBJECT; 133 | serverType?: ServerType; 134 | favicon?: string; 135 | }; 136 | server?: OmniServer & { serverType?: SSR_SERVER; }; 137 | build: { 138 | autoRelease?: boolean; 139 | srcDir: string; 140 | outDir: string; 141 | esmDir?: string; 142 | hash?: boolean | HASH; 143 | configuration?: (config: ANY_OBJECT) => ANY_OBJECT; 144 | tool?: Exclude; 145 | preflight?: { 146 | typescript?: boolean; 147 | test?: boolean; 148 | eslint?: boolean; 149 | prettier?: boolean; 150 | stylelint?: boolean; 151 | }; 152 | reserve?: { 153 | style?: boolean; 154 | assets?: (string | { srcPath: string; relativePath?: string; })[]; 155 | }; 156 | }; 157 | release: { 158 | git?: string; 159 | npm?: string | boolean; 160 | autoBuild?: boolean; 161 | autoTag?: boolean; 162 | preflight?: { 163 | test?: boolean; 164 | eslint?: boolean; 165 | prettier?: boolean; 166 | stylelint?: boolean; 167 | commitlint?: boolean; 168 | branch?: string; 169 | }; 170 | }; 171 | template: { 172 | root: string; 173 | test?: boolean; 174 | typescript?: boolean; 175 | stylesheet?: STYLE; 176 | readme?: MARKDOWN | boolean; 177 | }; 178 | plugins?: OmniPlugin[]; 179 | } 180 | 181 | interface OmniRollupConfig extends OmniBaseConfig { 182 | build: OmniBaseConfig['build'] & { 183 | tool: Extract; 184 | configuration?: (getConfig: (bundle: boolean) => ANY_OBJECT) => ANY_OBJECT; 185 | }; 186 | } 187 | 188 | type OmniConfig = OmniBaseConfig | OmniRollupConfig; 189 | ``` 190 | 191 | - `name`:插件的名称 192 | 193 | - `stage`:插件执行的阶段 194 | 195 | - `handler`:执行的回调函数,以 `promise` 的形式返回 196 | 197 | - 通过 `import { PluginHandler_Release } from '@omni-door/cli/lib/index.d';` 获取 handle 应满足的类型 198 | - 支持: `PluginHandler_Dev`、`PluginHandler_Build`、`PluginHandler_Release`、`PluginHandler_New` 199 | --- 200 | 201 | ## import 引入 command 命令 202 | - `import { initial } from '@omni-door/cli';`:获取 initial 指令,传入参数直接调用: 203 | 204 | ```ts 205 | initial({ 206 | standard: true // 构建一个标准项目 207 | }, { 208 | // 项目初始化开始前 209 | before: dir_name => ({ 210 | create_dir: false // 避免新创建文件夹 211 | }), 212 | // 项目初始化完成后 213 | after: () => { 214 | return { 215 | success: true, 216 | msg: '完成项目初始化构建' 217 | }; 218 | }, 219 | // 自定义安装的模板 220 | tplPkj: '@omni-door/tpl-toolkit', 221 | // 自定义模板需要传入的参数 222 | tplPkjParams: ['bid=55232', 'test=false'], 223 | // 自定义 omni.config.js 文件名称 224 | configFileName: 'custom.config.js' 225 | }); 226 | ``` 227 | 228 | - 其他阶段的命令同样支持:`import { dev, new as newTpl, build, release } from '@omni-door/cli';` 229 | 230 | - 支持自定义 logo、brand 前缀: 231 | ```ts 232 | import { setLogo, setBrand } from '@omni-door/cli'; 233 | 234 | setLogo('😄'); 235 | setBrand('自定义的前缀:'); 236 | ``` -------------------------------------------------------------------------------- /docs/OMNI.md: -------------------------------------------------------------------------------- 1 | # omni.config.js Detail 2 | 3 | English | [简体中文](./OMNI.zh-CN.md) 4 | 5 | ## type 6 | OMNI will process of initialization, construction and template creation according to different project types 7 | 8 | The project types: 9 | 10 | - spa-react - React single-page-application 11 | 12 | - spa-react-pc - React single-page-application based on [Antd](https://ant.design/) 13 | 14 | - spa-vue - Vue single-page-application 15 | 16 | - ssr-react - React sever-side-render application 17 | 18 | - component-react - React Component Library 19 | 20 | - component-vue - Vue Component Library 21 | 22 | - toolkit - SDK Library 23 | 24 | ## dev 25 | The dev-server based on express, realizing hot-update, api-proxy and other common functions. Provide personalized customization schemes such as middleware customization, port number, log output level and webpack configuration. 26 | 27 | - middleware - middleware configuration: 28 | 29 | ```ts 30 | { 31 | route: string; 32 | callback: (req: any, res: any) => Promise; 33 | } 34 | ``` 35 | 36 | or 37 | 38 | ```ts 39 | (params: { 40 | ip: string; 41 | port: number; 42 | host?: string; 43 | proxyConfig?: (ProxyItem | ProxyFn)[]; 44 | }) => { 45 | route: string; 46 | callback: (req: any, res: any) => Promise; 47 | } 48 | ``` 49 | 50 | - webpack - dev-server webpack configuration 51 | 52 | - proxy - dev-server proxy configuration 53 | 54 | ```ts 55 | { 56 | route: '/api', // Address of the local service for the proxy API 57 | config: { 58 | target: 'http://www.api.com/api', // The actual address of the proxy API 59 | changeOrigin: true // whether change the host 60 | } 61 | } 62 | ``` 63 | 64 | or 65 | 66 | ```ts 67 | (params: { 68 | ip: string; 69 | port: number; 70 | host?: string; 71 | middlewareConfig?: (MiddlewareItem | MiddlewareFn)[]; 72 | }) => { 73 | route: string; 74 | config: Config; 75 | } 76 | ``` 77 | 78 | For more configuration, see [http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware) 79 | 80 | - port - dev-server port 81 | 82 | - host - dev-server host 83 | 84 | - https - start dev-server with https protocol which could custom `key` and `cert` 85 | 86 | - serverType - dev-server type 87 | 88 | - favicon - favicon path for dev-server 89 | 90 | ## build 91 | 92 | - autoRelease - auto release project after build success 93 | 94 | - srcDir - the build source directory 95 | 96 | - outDir - the directory for compiled project 97 | 98 | - esmDir - es6 module compiled directory 99 | 100 | - hash - whether the hash tag add to building result, optional 'contenthash', 'chunkhash' and 'hash'(true equal 'contenthash') 101 | 102 | - configuration - The callback will be call in the build-process, you can return your custom build configuration 103 | 104 | - reserve - Configure resources that are not packaged but need to be kept in the build result 105 | - style - whether or not reserve the stylesheet files 106 | 107 | - assets - reserve other asset paths 108 | 109 | - preflight - the flight check before build 110 | - typescript - whether or not process the ts or tsx files 111 | 112 | - test - whether or not process unit-test 113 | 114 | - eslint - whether or not process eslint check 115 | 116 | - prettier - whether or not process prettier check 117 | 118 | - stylelint - whether or not process stylelint check 119 | 120 | ## release 121 | - autoBuild - auto build project before release process 122 | 123 | - autoTag - npm publish will auto set tag according to the current version 124 | 125 | - git - project git repo url 126 | 127 | - npm - npm depository url 128 | 129 | - preflight - the flight check before release 130 | - test - whether or not process unit-test 131 | 132 | - eslint - whether or not process eslint check 133 | 134 | - prettier - whether or not process prettier check 135 | 136 | - stylelint - whether or not process stylelint check 137 | 138 | - commitlint - whether or not process commitlint check 139 | 140 | - branch - only can release in this branch, set empty string to ignore this check 141 | 142 | ## template 143 | - root - the root directory for generate template 144 | 145 | - typescript - whether or not apply typescript 146 | 147 | - test - whether or not generate unit-test file 148 | 149 | - stylesheet - stylesheet type 150 | 151 | - readme - [true, 'mdx'] ([whether or not README.md, generate mdx or md file]) 152 | 153 | ## plugins 154 | plugin must meet following types: 155 | 156 | ```ts 157 | type OmniPlugin = { 158 | name: string; 159 | stage: PLUGIN_STAGE; 160 | handler: PluginHandler; 161 | }; 162 | 163 | type PLUGIN_STAGE = 'new' | 'build' | 'release'; 164 | interface PluginHandler { 165 | (config: Omit): Promise; 166 | } 167 | ``` -------------------------------------------------------------------------------- /docs/OMNI.zh-CN.md: -------------------------------------------------------------------------------- 1 | # omni.config.js 详解 2 | 3 | [English](./OMNI.md) | 简体中文 4 | 5 | ## type 项目类型 6 | OMNI 会根据不同的项目类型决定整个初始化、构建、创建模板的过程 7 | 8 | 目前支持的项目类型有: 9 | 10 | - spa-react - React单页应用 11 | 12 | - spa-react-pc - 基于 [Antd](https://ant.design/) 的React中后台单页应用 13 | 14 | - spa-vue - Vue单页应用 15 | 16 | - ssr-react - React服务端渲染应用 17 | 18 | - component-react - React组件库 19 | 20 | - component-vue - Vue组件库 21 | 22 | - toolkit - SDK工具包 23 | 24 | ## dev 开发服务 25 | 开发服务基于express,搭配 webpack-dev-middleware、webpack-hot-middleware、http-proxy-middleware 等中间件,实现了热更新、接口代理等常用功能,并提供了中间件的自定义、端口号、log日志输出级别、webpack配置等个性化定制方案。 26 | 27 | - middleware - 中间件配置,参考下面👇的类型: 28 | 29 | ```ts 30 | { 31 | route: string; 32 | callback: (req: any, res: any) => Promise; 33 | } 34 | ``` 35 | 36 | or 37 | 38 | ```ts 39 | (params: { 40 | ip: string; 41 | port: number; 42 | host?: string; 43 | proxyConfig?: (ProxyItem | ProxyFn)[]; 44 | }) => { 45 | route: string; 46 | callback: (req: any, res: any) => Promise; 47 | } 48 | ``` 49 | 50 | - webpack - 开发服务端webpack配置 51 | 52 | - proxy - 开发服务代理配置 53 | 54 | ```ts 55 | { 56 | route: '/api', // 代理API的本地服务的地址 57 | config: { 58 | target: 'http://www.api.com/api', // 代理API的实际地址 59 | changeOrigin: true // 是否改变host 60 | } 61 | } 62 | ``` 63 | 64 | or 65 | 66 | ```ts 67 | (params: { 68 | ip: string; 69 | port: number; 70 | host?: string; 71 | middlewareConfig?: (MiddlewareItem | MiddlewareFn)[]; 72 | }) => { 73 | route: string; 74 | config: Config; 75 | } 76 | ``` 77 | 78 | 更多配置详见 [http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware) 79 | 80 | - port - 开发服务启动的端口号 81 | 82 | - host - 开发服务启动的host 83 | 84 | - https - 开发服务以https协议启动,可自定义 `key` 和 `cert` 85 | 86 | - serverType - 开发服务的类型 87 | 88 | - favicon - 开发服务的 favicon 路径 89 | 90 | ## build 构建配置 91 | 92 | - autoRelease - 构建完成后是否自动发布 93 | 94 | - srcDir - 构建资源输入路径 95 | 96 | - outDir - 构建结果输出路径 97 | 98 | - esmDir - 构建结果输出路径(符合es6 module规范) 99 | 100 | - hash - 构建的资源是否加上hash,可选 'contenthash'、'chunkhash'、'hash'(传入true则是contenthash) 101 | 102 | - configuration - 构建阶段的自定义配置回调,返回自定义的配置 103 | 104 | - reserve - 配置未经过打包,但需要保留进构建结果的资源 105 | - style - 构建结果是否保留样式文件 106 | 107 | - assets - 构建结果保留其他资源的路径 108 | 109 | - preflight - 构建前的飞行检查 110 | - typescript - 是否处理ts或tsx文件 111 | 112 | - test - 是否进行单元测试 113 | 114 | - eslint - 是否进行eslint检测 115 | 116 | - prettier - 是否进行prettier检测 117 | 118 | - stylelint - 是否进行stylelint检测 119 | 120 | ## release 121 | - autoBuild - 发布之前是否自动构建项目 122 | 123 | - autoTag - 发布到npm仓库时会自动根据当前版本号设定tag 124 | 125 | - git - 发布的git仓库地址 126 | 127 | - npm - 发布的npm仓库地址 128 | 129 | - preflight - 发布前的飞行检查 130 | - test - 发布前是否进行单元测试 131 | 132 | - eslint - 发布前是否进行eslint检测 133 | 134 | - prettier - 发布前是否进行prettier检测 135 | 136 | - stylelint - 发布前是否进行stylelint检测 137 | 138 | - commitlint - 发布前是否进行commitlint检测 139 | 140 | - branch - 发布前进行分支检测,设置为空字符串则不会检测 141 | 142 | ## template 新建模板配置 143 | - root - 生成模板的根路径 144 | 145 | - typescript - 是否创建ts文件 146 | 147 | - test - 是否创建单元测试文件 148 | 149 | - stylesheet - 样式文件类型 150 | 151 | - readme - [true, 'mdx'] ([是否生成ReadMe文件, 创建md 或 mdx文件]) 152 | 153 | ## plugins 154 | 插件集合,插件需满足下面的类型: 155 | 156 | ```ts 157 | type OmniPlugin = { 158 | name: string; 159 | stage: PLUGIN_STAGE; 160 | handler: PluginHandler; 161 | }; 162 | 163 | type PLUGIN_STAGE = 'new' | 'build' | 'release'; 164 | interface PluginHandler { 165 | (config: Omit): Promise; 166 | } 167 | ``` -------------------------------------------------------------------------------- /docs/README.zh-CN.md: -------------------------------------------------------------------------------- 1 | # 🐸 @omni-door/cli 2 | 3 | https://www.omnidoor.org 4 | 5 | 一个能创建标准的前端项目的脚手架。 6 | 7 | [![NPM downloads](http://img.shields.io/npm/dm/%40omni-door%2Fcli.svg?style=flat-square)](https://www.npmjs.com/package/@omni-door/cli) 8 | [![npm version](https://badge.fury.io/js/%40omni-door%2Fcli.svg)](https://badge.fury.io/js/%40omni-door%2Fcli) 9 | [![node version](https://img.shields.io/badge/node.js-%3E=_10.13.0-green.svg?style=flat-square)](http://nodejs.org/download/) 10 | [![Build Status](https://travis-ci.com/omni-door/cli.svg?branch=master)](https://travis-ci.com/omni-door/cli) 11 | [![codecov](https://codecov.io/gh/omni-door/cli/branch/master/graph/badge.svg)](https://codecov.io/gh/omni-door/cli) 12 | [![install size](https://packagephobia.now.sh/badge?p=%40omni-door%2Fcli)](https://packagephobia.now.sh/result?p=%40omni-door%2Fcli) 13 | [![license](http://img.shields.io/npm/l/%40omni-door%2Fcli.svg)](https://github.com/omni-door/cli/blob/master/LICENSE) 14 | 15 | [English](../README.md) | 简体中文 16 | 17 | [ 详解](./OMNI.zh-CN.md) 18 | 19 | [变更日志](./CHANGELOG.zh-CN.md) 20 | 21 | ## 安装 22 | 推荐使用 node 最新的 LTS 版本,或至少保证 node >= 10.13.0 23 | 24 | 几个选项: 25 | 26 | * 克隆仓库: `git@github.com:omni-door/cli.git` 27 | 28 | * 用 [npm](https://www.npmjs.com/package/@omni-door/cli) 安装:`npm install @omni-door/cli -g` 29 | 30 | * 用 [Yarn](https://yarnpkg.com/en/package/@omni-door/cli) 安装:`yarn global add @omni-door/cli` 31 | 32 | * 直接用 [npx](https://www.npmjs.com/package/@omni-door/cli) 初始化你的项目:`npx @omni-door/cli init` 33 | 34 | ## omni --help 35 | ```shell 36 | 使用: omni [command] [options] 37 | 38 | Options: 39 | 40 | -v, --version 输出版本号 41 | -h, --help 输出使用帮助 42 | 43 | Commands: 44 | 45 | init [strategy] [options] 初始化你的项目,[strategy(策略)] 可用是stable(默认) 或 lastst 46 | dev [options] omni dev -p [port] 47 | new [options] omni new [module] [-f | -c] 48 | build 根据 [omni.config.js] 打包构建你的项目 49 | release [options] 根据 [omni.config.js] 发布你的项目 50 | 51 | ``` 52 | 53 | ## omni init 54 | 55 | ### 初始化一个项目 56 | ```shell 57 | omni init 58 | ``` 59 | 60 | ### 用最新的依赖@lastest初始化项目 61 | ```shell 62 | omni init lastest 63 | ``` 64 | 65 | ### 初始化项目但不安装依赖 66 | ```shell 67 | omni init -n 68 | ``` 69 | 70 | ### 套用模板一键初始化项目 71 | ```shell 72 | omni init -t [projectName] 73 | ``` 74 | or 75 | ```shell 76 | omni init --entire [projectName] 77 | ``` 78 | 79 | ### 选项 80 | ```shell 81 | 使用: omni init [strategy] [options] 82 | 83 | initialize your project, [strategy] could be stable(default) or latest 84 | 85 | Arguments: 86 | 87 | strategy stable or latest 88 | 89 | Options: 90 | -rb, --react_basic [name] 创建一个最基本的 React 单页应用 91 | -rs, --react_standard [name] 创建一个标准的 React 单页应用 92 | -re, --react_entire [name] 创建一个全量的 React 单页应用 93 | -rp, --react_pc [name] 创建一个基于 Antd 的 React 中后台单页应用 94 | -vb, --vue_basic [name] 创建一个最基本的 Vue 单页应用 95 | -vs, --vue_standard [name] 创建一个标准的 Vue 单页应用 96 | -ve, --vue_entire [name] 创建一个全量的 Vue 单页应用 97 | -rS, --react_ssr [name] 创建一个 React SSR 应用 98 | -rc, --react_components [name] 创建一个 React 组件库 99 | -vc, --vue_components [name] 创建一个 Vue 组件库 100 | -t, --toolkit [name] 创建一个工具库 101 | -n, --no-install 初始化项目不安装任何依赖 102 | -P, --path 创建项目的工作路径 103 | -h, --help 输出帮助信息 104 | ``` 105 | 106 | --- 107 | 108 | ## omni dev 109 | 110 | ### 选项 111 | ```shell 112 | 使用: omni dev [options] 113 | 114 | omni dev [-p ] [-H ] [-P ] 115 | 116 | Options: 117 | -p, --port 根据指定的端口号启动开发服务 118 | -H, --hostname 根据指定的hostname启动开发服务 119 | -P, --path 启动开发服务的工作路径 120 | -h, --help 输出帮助信息 121 | ``` 122 | 123 | --- 124 | 125 | ## omni start 126 | 127 | ### 选项 128 | ```shell 129 | 使用: omni start [options] 130 | 131 | omni start [-p ] [-H ] [-P ] 132 | 133 | Options: 134 | -p, --port 根据指定的端口号启动生产服务 135 | -H, --hostname 根据指定的hostname启动生产服务 136 | -P, --path 启动生产服务的工作路径 137 | -h, --help 输出帮助信息 138 | ``` 139 | 140 | --- 141 | 142 | ## omni new 143 | 144 | ### 选项 145 | ```shell 146 | 使用: omni new [name] [options] 147 | 148 | omni new [name] [-f | -c] [-P ] 149 | 150 | Arguments: 151 | 152 | module 可选!组件名称。 153 | 154 | Options: 155 | -f, --function 创建一个React函数组件 156 | -c, --class 创建一个React类组件 157 | -r, --render 创建一个Vue渲染函数组件 158 | -s, --single 创建一个Vue模板组件 159 | -P, --path 创建组件的工作路径 160 | -h, --help 输出帮助信息 161 | ``` 162 | 163 | --- 164 | 165 | ## omni build 166 | 167 | ### 选项 168 | ```shell 169 | 使用: omni build [options] 170 | 171 | 根据 [omni.config.js] 的 build 字段构建项目 172 | 173 | Options: 174 | -c, --config 指定构建的配置文件路径 175 | -n, --no-verify 绕过所有预检直接构建 176 | -P, --path 构建的工作路径 177 | -h, --help 输出帮助信息 178 | ``` 179 | 180 | --- 181 | 182 | ## omni release 183 | 184 | ### 选项 185 | ```shell 186 | 使用: omni release [options] 187 | 188 | 根据 [omni.config.js] 的 release 字段发布项目 189 | 190 | Options: 191 | -a, --automatic 发布并自动迭代版本号 192 | -i, --ignore 发布并忽视版本号的迭代 193 | -m, --manual 发布并手动指定版本号 194 | -t, --tag 发布时指定tag 195 | -n, --no-verify 绕过所有的预检直接发布 196 | -P, --path 发布的工作路径 197 | -h, --help 输出帮助信息 198 | ``` 199 | 200 | --- 201 | 202 | ## API文档 203 | 点 [这里](./DEV.zh-CN.md) 204 | 205 | ## License 206 | 207 | Copyright (c) 2019 [Bobby.li](https://github.com/BobbyLH) 208 | 209 | Released under the MIT License -------------------------------------------------------------------------------- /docs/omni-init.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omni-door/cli/7349af22a058d59d20b4194609dc2e9d4ae35010/docs/omni-init.gif -------------------------------------------------------------------------------- /mocha.opts: -------------------------------------------------------------------------------- 1 | # mocha.opts 2 | --require ts-node/register src/**/__test__/*.ts 3 | --reporter spec -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@omni-door/cli", 3 | "version": "3.1.1", 4 | "description": "A tool set for set up the standard JS project", 5 | "bin": { 6 | "omni": "./bin/omni-door.js" 7 | }, 8 | "main": "lib/commands/commands.js", 9 | "typings": "lib/commands/commands.d.ts", 10 | "scripts": { 11 | "test": "nyc mocha --opts mocha.opts", 12 | "lint": "eslint src/ --ext .ts --ext .tsx", 13 | "lint:fix": "eslint src/ --ext .ts --ext .tsx --fix", 14 | "build": "npm run build:rm && npm run build:tsc && npm run build:copy", 15 | "build:rm": "rm -rf lib/*", 16 | "build:tsc": "tsc --build", 17 | "build:branch": "./scripts/branch.sh", 18 | "build:version": "./scripts/version.sh", 19 | "build:copy": "./scripts/copy.sh", 20 | "release": "npm run build:branch master && npm run build && npm run build:version" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/omni-door/cli.git" 25 | }, 26 | "keywords": [ 27 | "omni", 28 | "omni-door" 29 | ], 30 | "author": "bobby.li", 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com/omni-door/cli/issues", 34 | "email": "omni.door.official@gmail.com" 35 | }, 36 | "husky": { 37 | "hooks": { 38 | "pre-commit": "lint-staged", 39 | "pre-push": "npm run lint && npm run test", 40 | "commit-msg": "commitlint -e $HUSKY_GIT_PARAMS" 41 | } 42 | }, 43 | "lint-staged": { 44 | "src/**/*.{ts,tsx}": [ 45 | "npm run lint:fix", 46 | "git add" 47 | ] 48 | }, 49 | "homepage": "https://github.com/omni-door/cli#readme", 50 | "devDependencies": { 51 | "@babel/core": "^7.6.4", 52 | "@types/chai": "^4.2.3", 53 | "@types/clui": "^0.3.0", 54 | "@types/detect-port": "^1.1.0", 55 | "@types/figlet": "^1.2.0", 56 | "@types/fs-extra": "^8.0.1", 57 | "@types/inquirer": "^6.5.0", 58 | "@types/ip": "^1.1.0", 59 | "@types/koa-router": "^7.4.1", 60 | "@types/mkcert": "^1.2.0", 61 | "@types/mocha": "^5.2.7", 62 | "@types/next-server": "^9.0.0", 63 | "@types/node": "^12.11.1", 64 | "@types/rollup": "^0.54.0", 65 | "@types/semver": "^7.3.4", 66 | "@types/shelljs": "^0.8.5", 67 | "@types/webpack-env": "^1.15.0", 68 | "@types/webpack-hot-middleware": "^2.25.0", 69 | "@typescript-eslint/eslint-plugin": "^3.8.0", 70 | "@typescript-eslint/parser": "^3.8.0", 71 | "babel-loader": "^8.0.6", 72 | "chai": "^4.2.0", 73 | "commitizen": "^4.1.2", 74 | "commitlint": "^8.2.0", 75 | "css-loader": "^3.2.0", 76 | "cz-conventional-changelog": "^3.2.0", 77 | "detect-port": "^1.3.0", 78 | "eslint": "^7.6.0", 79 | "express": "4.17.3", 80 | "http-proxy-middleware": "^0.20.0", 81 | "husky": "^3.0.9", 82 | "ip": "^1.1.5", 83 | "less": "^3.10.3", 84 | "less-loader": "^5.0.0", 85 | "lint-staged": "^9.4.2", 86 | "mocha": "^6.2.2", 87 | "node-sass": "9.0.0", 88 | "nyc": "^14.1.1", 89 | "open": "^7.0.0", 90 | "rollup": "^1.26.0", 91 | "rollup-plugin-babel": "^4.3.3", 92 | "rollup-plugin-commonjs": "^10.1.0", 93 | "rollup-plugin-node-resolve": "^5.2.0", 94 | "rollup-plugin-typescript": "^1.0.1", 95 | "rollup-plugin-typescript2": "^0.24.3", 96 | "sass-loader": "^8.0.0", 97 | "style-loader": "^1.0.0", 98 | "ts-loader": "^6.2.1", 99 | "ts-node": "^8.4.1", 100 | "typescript": "3.9.7", 101 | "webpack": "^4.41.5", 102 | "webpack-cli": "^3.3.9", 103 | "webpack-dev-middleware": "^3.7.2", 104 | "webpack-hot-middleware": "^2.25.0", 105 | "webpack-merge": "^4.2.2" 106 | }, 107 | "dependencies": { 108 | "@omni-door/utils": "~1.4.0", 109 | "@types/express": "4.17.8", 110 | "@types/http-proxy-middleware": "0.19.3", 111 | "@types/koa": "2.11.3", 112 | "@types/webpack": "4.41.26", 113 | "@types/webpack-dev-middleware": "4.0.0", 114 | "chalk": "3.0.0", 115 | "commander": "5.1.0", 116 | "del": "5.1.0", 117 | "figlet": "1.2.4", 118 | "fs-extra": "8.1.0", 119 | "inquirer": "8.2.4", 120 | "leven": "3.1.0", 121 | "mkcert": "1.5.0", 122 | "shelljs": "0.8.5" 123 | }, 124 | "resolutions": { 125 | "@types/express/@types/express-serve-static-core": "4.17.12" 126 | }, 127 | "config": { 128 | "commitizen": { 129 | "path": "./node_modules/cz-conventional-changelog" 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /scripts/branch.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | branch=$1 4 | name="🐸 [OMNI-DOOR/CLI]" 5 | 6 | checkBranch () { 7 | if [ -z "$branch" ]; then 8 | echo -e "\033[31m \n ${name}: The branch parameter cannot be empty\n \033[0m" 9 | exit 1 10 | fi 11 | 12 | currentBranch=$(git branch | grep \* | cut -d " " -f2) 13 | 14 | if [ "$currentBranch" != "$branch" ] 15 | then 16 | echo -e "\033[31m \n ${name}: Please switch to \033[43;30m ${branch} \033[0m \033[31mbranch first\n \033[0m" 17 | exit 1 18 | fi 19 | 20 | echo -e "\033[36m \n ${name}: The current branch is ${branch}\n \033[0m" 21 | } 22 | 23 | checkBranch -------------------------------------------------------------------------------- /scripts/copy.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | cp -rf src/index.d.ts lib/index.d.ts 4 | cp -rf src/commands/release/branch.sh lib/commands/release/branch.sh 5 | cp -rf src/commands/release/version.sh lib/commands/release/version.sh 6 | cp -rf src/commands/release/publish.sh lib/commands/release/publish.sh 7 | cp -rf src/commands/dev/open.chrome.applescript lib/commands/dev/open.chrome.applescript 8 | cp -rf src/commands/servers/favicon.ico lib/commands/servers/favicon.ico -------------------------------------------------------------------------------- /scripts/publish.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | name="🐸 [OMNI-DOOR/CLI]" 4 | 5 | if [ $? -eq 0 ] 6 | then 7 | pkjV=$(grep \"version\" package.json) 8 | version=$(echo ${pkjV} | tr -cd "[0-9].") 9 | git add -A 10 | git commit -m "${name}: ${version}" 11 | git push 12 | npm publish --registry='https://registry.npmjs.org' 13 | echo -e "\033[32m \n${name}: The npm-package publish success - ${version}\n \033[0m" 14 | else 15 | echo -e "\033[31m \n${name}: The npm-package publish failed!\n \033[0m" 16 | fi -------------------------------------------------------------------------------- /scripts/version.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | iterate=$1 4 | name="🐸 [OMNI-DOOR/CLI]" 5 | dot="." 6 | OS=`uname` 7 | 8 | replaceVersion () { 9 | if [ "$OS" = "Darwin" ]; then 10 | sed -i "" "s/$1/$2/g" "package.json" 11 | else 12 | sed -i"" "s/$1/$2/g" "package.json" 13 | fi 14 | } 15 | 16 | updateVersion () { 17 | versionLine=$(grep \"version\" package.json) 18 | version=$(echo ${versionLine} | tr -cd "[0-9-a-zA-Z]." | sed -ne "s/[^0-9]*\(\([0-9a-zA-Z]\.\)\)/\1/p") 19 | prevSubVersion=$(echo ${version#*.}) 20 | subVersion=$(echo ${prevSubVersion%.*}) 21 | subSubVersion=$(echo ${version##*.}) 22 | manualVersion=$(echo "$iterate" | grep "[0-9].[0-9]*.[0-9]") 23 | if [ "$iterate" = "i" -o "$iterate" = "ignore" ] 24 | then 25 | echo -e "\033[33m${name}: Ignoring the version of iteration\033[0m" 26 | elif [ -z "$iterate" ] 27 | then 28 | newSubSubVersion=`expr $subSubVersion + 1` 29 | newVersion=$(echo ${version/${dot}${subVersion}${dot}${subSubVersion}/${dot}${subVersion}${dot}${newSubSubVersion}}) 30 | newVersionLine=$(echo "${versionLine/${version}/${newVersion}}") 31 | echo -e "\033[36m${name}: Auto-increase the version of iteration to ${newVersion}\033[0m" 32 | replaceVersion "$versionLine" "$newVersionLine" 33 | elif [ -n "$manualVersion" ] 34 | then 35 | newVersion=$(echo ${version/${version}/${manualVersion}}) 36 | newVersionLine=$(echo "${versionLine/${version}/${newVersion}}") 37 | echo -e "\033[35m${name}: Manual specify the version of iteration to ${manualVersion}\033[0m" 38 | replaceVersion "$versionLine" "$newVersionLine" 39 | else 40 | echo -e "\033[41;37m${name}: Please input correct version number\033[0m" 41 | exit 1 42 | fi 43 | } 44 | 45 | updateVersion 46 | 47 | exec "./scripts/publish.sh" -------------------------------------------------------------------------------- /src/@types/rollup.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'rollup-plugin-babel'; 2 | declare module 'rollup-plugin-typescript'; -------------------------------------------------------------------------------- /src/commands/build/__test__/index.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha'; 2 | import { expect } from 'chai'; 3 | import build from '../index'; 4 | import rollupConfig from '../rollup'; 5 | import webpackConfig from '../webpack'; 6 | import dependencies from '../dependencies_build'; 7 | 8 | describe('build command test', function () { 9 | it('type checking', function () { 10 | expect(build).to.be.a('function'); 11 | expect(rollupConfig).to.be.a('function'); 12 | expect(webpackConfig).to.be.a('function'); 13 | expect(dependencies).to.be.a('function'); 14 | }); 15 | 16 | it('call rollupConfig', function () { 17 | const config1 = rollupConfig({ 18 | ts: true, 19 | multiOutput: false, 20 | srcDir: 'src', 21 | outDir: 'lib', 22 | esmDir: 'es' 23 | }); 24 | expect(config1).to.be.a('string'); 25 | 26 | const config2 = rollupConfig({ 27 | ts: false, 28 | multiOutput: true, 29 | srcDir: 'src', 30 | outDir: 'dist', 31 | esmDir: 'es', 32 | configFileName: 'custom.config.js' 33 | }); 34 | expect(config2).to.be.a('string'); 35 | }); 36 | 37 | it('call webpackConfig', function () { 38 | const config1 = webpackConfig({ 39 | ts: true, 40 | multiOutput: false, 41 | srcDir: 'src', 42 | outDir: 'lib', 43 | configFileName: 'custom.config.js' 44 | }); 45 | expect(config1).to.be.a('string'); 46 | 47 | const config2 = webpackConfig({ 48 | ts: false, 49 | multiOutput: true, 50 | srcDir: 'src', 51 | outDir: 'dist', 52 | hash: 'chunkhash' 53 | }); 54 | expect(config2).to.be.a('string'); 55 | }); 56 | 57 | it('call dependencies', function (done) { 58 | dependencies({ 59 | build: 'webpack', 60 | project_type: 'spa-react' 61 | }).then(webpackDependencies => { 62 | expect(webpackDependencies).to.be.a('string'); 63 | expect(!!~webpackDependencies.indexOf('webpack')).to.be.true; 64 | dependencies({ 65 | build: 'rollup', 66 | project_type: 'toolkit' 67 | }).then(rollupDependencies => { 68 | expect(!!~rollupDependencies.indexOf('rollup')).to.be.true; 69 | done(); 70 | }); 71 | }); 72 | }); 73 | }); -------------------------------------------------------------------------------- /src/commands/build/dependencies_build.ts: -------------------------------------------------------------------------------- 1 | import { getDependency, arr2str } from '@omni-door/utils'; 2 | /* import types */ 3 | import type { BUILD, PROJECT_TYPE } from '@omni-door/utils'; 4 | 5 | export default async function (config: { 6 | build: BUILD; 7 | project_type: PROJECT_TYPE; 8 | }) { 9 | const dependency = await getDependency('stable', { 10 | '@babel/core': '~7.10.0', 11 | '@babel/preset-env': '~7.10.0', 12 | '@babel/preset-react': '~7.10.0', 13 | '@babel/preset-typescript': '~7.10.0', 14 | '@babel/plugin-transform-runtime': '~7.10.0', 15 | '@babel/plugin-proposal-class-properties': '~7.10.0', 16 | '@omni-door/gulp-plugin-vue-sfc': '~0.1.0', 17 | 'babel-loader': '~8.1.0', 18 | 'cache-loader': '~4.1.0', 19 | 'css-loader': '~3.4.2', 20 | 'cssnano': '4.1.10', 21 | 'file-loader': '~5.0.2', 22 | 'html-webpack-plugin': '3.2.0', 23 | 'less': '~3.11.1', 24 | 'less-loader': '~5.0.0', 25 | 'mini-css-extract-plugin': '0.9.0', 26 | 'optimize-css-assets-webpack-plugin': '5.0.3', 27 | 'sass': '~1.77.6', 28 | 'sass-loader': '~10.2.1', 29 | 'style-loader': '~1.1.3', 30 | 'terser-webpack-plugin': '2.3.4', 31 | 'url-loader': '~3.0.0', 32 | 'vue-loader': 'next', 33 | 'vue-tsc': '~2.0.29', 34 | 'webpack': '~4.41.6', 35 | 'webpack-bundle-analyzer': '3.6.0', 36 | 'webpack-cli': '~3.3.11', 37 | 'webpack-dev-middleware': '3.7.2', 38 | 'webpack-hot-middleware': '2.25.0', 39 | 'webpack-merge': '4.2.2', 40 | 'webpackbar': '4.0.0', 41 | 'rollup': '1.31.1', 42 | 'rollup-plugin-babel': '4.3.3', 43 | 'rollup-plugin-commonjs': '10.1.0', 44 | 'rollup-plugin-json': '4.0.0', 45 | 'rollup-plugin-node-resolve': '5.2.0', 46 | 'rollup-plugin-typescript': '1.0.1', 47 | 'rollup-plugin-typescript2': '0.26.0', 48 | 'ts-patch': '~3.2.1', 49 | 'typescript': '~5.5.4', 50 | 'typescript-transform-paths': '~3.4.7', 51 | 'gulp': '4.0.2', 52 | 'gulp-autoprefixer': '7.0.1', 53 | 'gulp-babel': '8.0.0', 54 | 'gulp-clean-css': '4.3.0', 55 | 'gulp-concat': '2.6.1', 56 | 'gulp-concat-css': '3.1.0', 57 | 'gulp-cssnano': '2.1.3', 58 | 'gulp-less': '4.0.1', 59 | 'gulp-sass': '5.1.0', 60 | 'gulp-if': '3.0.0', 61 | 'gulp-sourcemaps': '3.0.0', 62 | 'gulp-ts-alias': '1.3.0', 63 | 'gulp-typescript': '5.0.1', 64 | 'through2': '4.0.1', 65 | 'next': '~10.0.1', 66 | 'next-url-prettifier': '1.4.0', 67 | '@next/bundle-analyzer': '~10.0.1', 68 | '@zeit/next-css': '~1.0.1', 69 | '@zeit/next-less': '~1.0.1', 70 | '@zeit/next-sass': '~1.0.1', 71 | 'next-compose-plugins': '~2.2.0', 72 | 'next-transpile-modules': '~4.0.2', 73 | }); 74 | const { 75 | build, 76 | project_type 77 | } = config; 78 | 79 | const buildDependencies = build === 'webpack' ? [ 80 | dependency('webpack'), 81 | dependency('webpack-cli'), 82 | dependency('webpack-merge'), 83 | project_type === 'spa-vue' ? dependency('vue-loader') : '', 84 | dependency('babel-loader'), 85 | dependency('cache-loader'), 86 | dependency('style-loader'), 87 | dependency('css-loader'), 88 | dependency('less'), 89 | dependency('less-loader'), 90 | dependency('sass-loader'), 91 | dependency('sass'), 92 | dependency('url-loader'), 93 | dependency('file-loader'), 94 | dependency('html-webpack-plugin'), 95 | dependency('terser-webpack-plugin'), 96 | dependency('optimize-css-assets-webpack-plugin'), 97 | dependency('mini-css-extract-plugin'), 98 | dependency('cssnano'), 99 | dependency('webpackbar'), 100 | dependency('webpack-bundle-analyzer'), 101 | dependency('@babel/core'), 102 | dependency('@babel/preset-env'), 103 | dependency('@babel/preset-react'), 104 | dependency('@babel/preset-typescript') 105 | ] : build === 'rollup' ? [ 106 | dependency('rollup'), 107 | dependency('rollup-plugin-node-resolve'), 108 | dependency('rollup-plugin-babel'), 109 | dependency('rollup-plugin-commonjs'), 110 | dependency('rollup-plugin-node-resolve'), 111 | dependency('rollup-plugin-typescript'), 112 | dependency('rollup-plugin-typescript2'), 113 | dependency('rollup-plugin-json'), 114 | dependency('@babel/core'), 115 | dependency('@babel/preset-env'), 116 | dependency('@babel/preset-typescript') 117 | ] : build === 'gulp' ? [ 118 | dependency('gulp'), 119 | dependency('gulp-autoprefixer'), 120 | dependency('gulp-babel'), 121 | dependency('gulp-concat'), 122 | dependency('gulp-concat-css'), 123 | dependency('gulp-cssnano'), 124 | dependency('gulp-less'), 125 | dependency('gulp-clean-css'), 126 | dependency('sass'), 127 | dependency('gulp-sass'), 128 | dependency('gulp-if'), 129 | dependency('gulp-sourcemaps'), 130 | dependency('gulp-ts-alias'), 131 | dependency('gulp-typescript'), 132 | dependency('through2'), 133 | dependency('@babel/core'), 134 | dependency('@babel/preset-env'), 135 | dependency('@babel/preset-react'), 136 | dependency('@babel/preset-typescript'), 137 | dependency('@babel/plugin-transform-runtime'), 138 | dependency('@babel/plugin-proposal-class-properties'), 139 | project_type === 'component-vue' ? dependency('@omni-door/gulp-plugin-vue-sfc') : '' 140 | ] : build === 'tsc' ? [ 141 | dependency('ts-patch'), 142 | dependency('typescript'), 143 | dependency('typescript-transform-paths'), 144 | project_type === 'component-vue'? dependency('vue-tsc') : '' 145 | ] : build === 'next' ? [ 146 | dependency('next'), 147 | dependency('next-url-prettifier'), 148 | dependency('@zeit/next-css'), 149 | dependency('@zeit/next-less'), 150 | dependency('@zeit/next-sass'), 151 | dependency('next-compose-plugins'), 152 | dependency('next-transpile-modules') 153 | ] 154 | : []; 155 | 156 | return arr2str(buildDependencies); 157 | } -------------------------------------------------------------------------------- /src/commands/build/gulp.ts: -------------------------------------------------------------------------------- 1 | export default function (config: { 2 | srcDir: string; 3 | outDir: string; 4 | esmDir: string; 5 | configurationPath?: string; 6 | configFileName?: string; 7 | pkjFieldName?: string; 8 | }) { 9 | const { srcDir = 'src', outDir = 'lib', esmDir = 'es', configurationPath, pkjFieldName = 'omni', configFileName = 'omni.config.js' } = config; 10 | 11 | return `'use strict'; 12 | 13 | const path = require('path'); 14 | const { requireCwd } = require('@omni-door/utils'); 15 | const gulp = requireCwd('gulp'); 16 | const babel = requireCwd('gulp-babel'); 17 | const less = requireCwd('gulp-less', true); 18 | const sass = requireCwd('gulp-sass', true)(requireCwd('sass', true)); 19 | const gulpif = requireCwd('gulp-if'); 20 | const alias = requireCwd('gulp-ts-alias'); 21 | const typescript = requireCwd('gulp-typescript'); 22 | const sourcemaps = requireCwd('gulp-sourcemaps'); 23 | const autoprefixer = requireCwd('gulp-autoprefixer'); 24 | const cssnano = requireCwd('gulp-cssnano'); 25 | const concat = requireCwd('gulp-concat'); 26 | const concatCss = requireCwd('gulp-concat-css'); 27 | const cleanCSS = requireCwd('gulp-clean-css'); 28 | const through2 = requireCwd('through2'); 29 | const replace = requireCwd('gulp-replace-path', true); 30 | const vueSFC = requireCwd('@omni-door/gulp-plugin-vue-sfc', true); 31 | 32 | const ppkj = requireCwd('./package.json'); 33 | const configFilePath = (ppkj && ppkj.${pkjFieldName} && ppkj.${pkjFieldName}.filePath) || './${configFileName}'; 34 | const configs = requireCwd(configFilePath); 35 | ${configurationPath ? `const customConfig = require('${configurationPath}') 36 | ` : ''} 37 | const { build } = configs || {}; 38 | const { configuration = ({ task }) => { const [compileCJS, compileES, compileSFC, ...rest] = task; return [gulp.series(compileCJS, compileES, compileSFC), ...rest]; } } = build || {}; 39 | const project = typescript && typescript.createProject('tsconfig.json'); 40 | 41 | const params = { 42 | dest: { 43 | lib: '${outDir}', 44 | es: '${esmDir}' 45 | }, 46 | styles: [ 47 | '${srcDir}/**/*.{css,less,scss,sass}', 48 | '!${srcDir}/**/{demo,__demo__,test,__test__,stories,__stories__}/*.{css,less,scss,sass}' 49 | ], 50 | scripts: [ 51 | '${srcDir}/**/*.{ts,tsx,js,jsx}', 52 | '!${srcDir}/**/{demo,__demo__,test,__test__,stories,__stories__}/*.{ts,tsx,js,jsx}', 53 | '!${srcDir}/**/*.{demo,test,stories}.{ts,tsx,js,jsx}' 54 | ], 55 | vue: [ 56 | '${srcDir}/**/*.vue', 57 | '!${srcDir}/**/{demo,__demo__,test,__test__,stories,__stories__}/*.{vue,ts,tsx,js,jsx}', 58 | '!${srcDir}/**/*.{demo,test,stories}.{vue,ts,tsx,js,jsx}' 59 | ] 60 | }; 61 | 62 | function cssInjection (content) { 63 | return content 64 | .replace(/\\\/style\\\/?'/g, "/style/css\'") 65 | .replace(/\\\/style\\\/?"/g, '/style/css\"') 66 | .replace(/\\\.(less|scss|sass)/g, '.css'); 67 | } 68 | 69 | function compileScripts (babelEnv, destDir) { 70 | const { scripts } = params; 71 | process.env.BABEL_ENV = babelEnv; 72 | return gulp 73 | .src(scripts) 74 | .pipe((alias && project) ? alias({ configuration: project.config }) : through2.obj()) 75 | .pipe(sourcemaps ? sourcemaps.init() : through2.obj()) 76 | .pipe(project ? project() : through2.obj()) 77 | .pipe(babel({ root: process.cwd() })) 78 | .pipe(replace ? replace(/\\.vue("|'){1}/g, '.js$1') : through2.obj()) 79 | .pipe( 80 | through2.obj(function (file, encoding, next) { 81 | this.push(file.clone()); 82 | if (file.path.match(/(\\\/|\\\\\)style(\\\/|\\\\\)index\\\.js/)) { 83 | const content = file.contents.toString(encoding); 84 | file.contents = Buffer.from(cssInjection(content)); 85 | file.path = file.path.replace(/index\\\.js/, 'css.js'); 86 | this.push(file); 87 | next(); 88 | } else { 89 | next(); 90 | } 91 | }) 92 | ) 93 | .pipe(sourcemaps ? sourcemaps.write({ sourceRoot: file => path.relative(path.join(file.cwd, file.path), file.base) }) : through2.obj()) 94 | .pipe(gulp.dest(destDir)); 95 | } 96 | 97 | function compileCJS () { 98 | const { dest } = params; 99 | return compileScripts('cjs', dest.lib); 100 | } 101 | 102 | function compileES () { 103 | const { dest } = params; 104 | return compileScripts('es', dest.es); 105 | } 106 | 107 | function compileSFC () { 108 | const { dest, vue } = params; 109 | return gulp 110 | .src(vue) 111 | .pipe(sourcemaps ? sourcemaps.init() : through2.obj()) 112 | .pipe(vueSFC ? vueSFC.default({ ext: '.ts' }) : through2.obj()) 113 | .pipe((alias && project) ? alias({ configuration: project.config }) : through2.obj()) 114 | .pipe(project ? project() : through2.obj()) 115 | .pipe(babel({ root: process.cwd() })) 116 | .pipe(replace ? replace(/\\.vue("|'){1}/g, '.js$1') : through2.obj()) 117 | .pipe(sourcemaps ? sourcemaps.write({ sourceRoot: file => path.relative(path.join(file.cwd, file.path), file.base) }) : through2.obj()) 118 | .pipe(gulp.dest(dest.lib)) 119 | .pipe(gulp.dest(dest.es)); 120 | } 121 | 122 | function copyStylesheet () { 123 | const { dest, styles } = params; 124 | return gulp 125 | .src(styles) 126 | .pipe(gulp.dest(dest.lib)) 127 | .pipe(gulp.dest(dest.es)); 128 | } 129 | 130 | function handleLess (file) { 131 | let result = false; 132 | if (!less) return result; 133 | if (file.path.match(/.less$/)) { 134 | result = true; 135 | } 136 | 137 | return result; 138 | } 139 | 140 | function handleSass (file) { 141 | let result = false; 142 | if (!sass) return result; 143 | if (file.path.match(/.(scss|sass)$/)) { 144 | result = true; 145 | } 146 | 147 | return result; 148 | } 149 | 150 | function trans2css() { 151 | const { dest, styles } = params; 152 | return gulp 153 | .src(styles) 154 | .pipe(gulpif(handleLess, less ? less() : through2.obj())) 155 | .pipe(gulpif(handleSass, sass ? sass() : through2.obj())) 156 | .pipe(autoprefixer()) 157 | .pipe(cssnano({ zindex: false, reduceIdents: false })) 158 | .pipe(gulp.dest(dest.lib)) 159 | .pipe(gulp.dest(dest.es)) 160 | .pipe(concatCss('index.css')) 161 | .pipe(concat('index.css')) 162 | .pipe(cleanCSS()) 163 | .pipe(gulp.dest(dest.lib)) 164 | .pipe(gulp.dest(dest.es)); 165 | } 166 | 167 | const builds = ${ 168 | configurationPath 169 | ? 'customConfig' 170 | : 'gulp.parallel.apply(gulp, configuration({ task: [compileCJS, compileES, compileSFC, copyStylesheet, trans2css], params }));' 171 | } 172 | 173 | exports.build = builds; 174 | 175 | exports.default = builds; 176 | `; 177 | } -------------------------------------------------------------------------------- /src/commands/build/index.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import { execSync } from'child_process'; 4 | import fsExtra from 'fs-extra'; 5 | import inquirer from 'inquirer'; 6 | import del from 'del'; 7 | import rollupConfig from './rollup'; 8 | import webpackConfig from './webpack'; 9 | import gulpConfig from './gulp'; 10 | import { 11 | spinner, 12 | exec, 13 | logErr, 14 | logInfo, 15 | logWarn, 16 | logSuc, 17 | logCongrat, 18 | logEmph, 19 | logTime, 20 | underline, 21 | italic, 22 | outputFile, 23 | nodeVersionCheck 24 | } from '@omni-door/utils'; 25 | import { getHandlers, logo, signal } from '../../utils'; 26 | import dependencies_build from './dependencies_build'; 27 | import release from '../release'; 28 | /* import types */ 29 | import type { BUILD } from '@omni-door/utils'; 30 | import type { OmniConfig, OmniPlugin } from '../../index.d'; 31 | 32 | export default async function ( 33 | config: OmniConfig | null, 34 | buildTactic?: { 35 | config?: string; 36 | verify?: boolean; 37 | buildConfig?: string; 38 | pkjFieldName?: string; 39 | configFileName?: string; 40 | }, 41 | autoBuild?: boolean 42 | ) { 43 | try { 44 | // node version pre-check 45 | await nodeVersionCheck('10.13.0'); 46 | } catch (e) { 47 | logWarn(e as string); 48 | } 49 | 50 | if (!config || JSON.stringify(config) === '{}') { 51 | logWarn('Please initialize project first'); 52 | logWarn('请先初始化项目'); 53 | return process.exit(0); 54 | } 55 | 56 | // bind exit signals 57 | signal(); 58 | 59 | const message = 'Building(开始构建)'; 60 | logTime('BUILD(项目构建)'); 61 | logInfo(message); 62 | 63 | const { type, template, build, release: configRelease, plugins } = config; 64 | 65 | const { 66 | autoRelease, 67 | srcDir, 68 | outDir, 69 | esmDir = '', 70 | hash, 71 | tool, 72 | reserve = {}, 73 | preflight 74 | } = build; 75 | 76 | const { 77 | typescript = false, 78 | test = false, 79 | eslint = false, 80 | prettier = false, 81 | stylelint = false 82 | } = preflight || {}; 83 | 84 | const CWD = process.cwd(); 85 | const { config: configPath, verify, buildConfig, pkjFieldName, configFileName } = buildTactic || {}; 86 | let configurationPath = configPath && path.resolve(CWD, configPath); 87 | if (configurationPath && !fs.existsSync(configurationPath)) configurationPath = void(0); 88 | 89 | if (!outDir || !srcDir) { 90 | handleBuildErr('The "srcDir" or "outDir" were missed in configuration file(配置文件中未定义 "srcDir" 或 "outDir")')(); 91 | } 92 | 93 | function handleBuildSuc (msg?: string) { 94 | msg = msg || 'Building completed(构建成功)!'; 95 | 96 | return function (isExit?: boolean) { 97 | logCongrat(msg!); 98 | isExit && process.exit(0); 99 | }; 100 | } 101 | 102 | function handleBuildErr (msg?: string) { 103 | msg = msg || 'Building failed(构建失败)!'; 104 | 105 | return function (err?: string) { 106 | err && logErr(err); 107 | msg && logErr(msg); 108 | type !== 'toolkit' && spinner.state('fail'); 109 | process.exit(1); 110 | }; 111 | } 112 | 113 | function installDenpendencies (build: BUILD) { 114 | return inquirer.prompt([ 115 | { 116 | name: 'install', 117 | type: 'confirm', 118 | message: `${logo()} Automatic install dependencies(自动安装所需要的依赖)?`, 119 | default: true 120 | } 121 | ]).then(async answers => { 122 | const { install } = answers; 123 | if (install) { 124 | const dependencies = await dependencies_build({ build, project_type: type }); 125 | 126 | // install tool pre-check 127 | let iTool = 'pnpm i --save-dev'; 128 | try { 129 | execSync('pnpm -v', { stdio: 'ignore' }); 130 | } catch (e) { 131 | iTool = 'yarn add -D'; 132 | try { 133 | execSync('yarn -v', { stdio: 'ignore' }); 134 | } catch (e) { 135 | iTool = 'npm i --save-dev'; 136 | } 137 | } 138 | 139 | return exec([ 140 | `${iTool} ${dependencies}` 141 | ], 142 | () => { 143 | logSuc('The dependencies install completed'); 144 | logSuc('构建依赖安装完毕'); 145 | return true; 146 | }, 147 | err => { 148 | logWarn(err); 149 | logWarn('The dependencies install occured some accidents'); 150 | logWarn('依赖安装发生了错误'); 151 | return false; 152 | }); 153 | } else { 154 | return false; 155 | } 156 | }); 157 | } 158 | 159 | function copyStylesheet (dir: string, originDir?: string) { 160 | const list = fs.readdirSync(dir); 161 | list.map((v, k) => { 162 | const filePath = path.resolve(dir, v); 163 | const stats = fs.statSync(filePath); 164 | if (stats.isDirectory()) { 165 | if (!/^.*(test|stories).*$/.test(v)) { 166 | copyStylesheet(filePath, originDir || dir); 167 | } 168 | } else if (/.(css|scss|sass|less)$/.test(v)) { 169 | const relativePath = path.relative(originDir || dir, filePath); 170 | const destPath = path.resolve(outDir, relativePath); 171 | const emsPath = esmDir && path.resolve(esmDir, relativePath); 172 | fsExtra.ensureDirSync(path.resolve(destPath, '..')); 173 | fsExtra.copySync(filePath, destPath); 174 | if (emsPath) { 175 | fsExtra.ensureDirSync(path.resolve(emsPath, '..')); 176 | fsExtra.copySync(filePath, emsPath); 177 | } 178 | } 179 | }); 180 | } 181 | 182 | function copyReserves (reserves: (string | { srcPath: string; relativePath?: string; })[]) { 183 | for (let i = 0, len = reserves.length; i < len; i++) { 184 | const reserveItem = reserves[i]; 185 | let srcPath = ''; 186 | let relativePath = ''; 187 | if (typeof reserveItem === 'string') { 188 | srcPath = reserveItem; 189 | } else { 190 | srcPath = reserveItem.srcPath; 191 | relativePath = reserveItem.relativePath || ''; 192 | } 193 | 194 | let stats; 195 | try { 196 | stats = fs.statSync(srcPath); 197 | } catch (error) { 198 | logWarn(`The path of "${srcPath}" is invaild`); 199 | logWarn(`"${srcPath}" 是一个无效的路径`); 200 | continue; 201 | } 202 | 203 | relativePath = relativePath || path.relative(srcDir, srcPath); 204 | const destPath = path.resolve(outDir, relativePath); 205 | const emsPath = esmDir && path.resolve(esmDir, relativePath); 206 | if (stats.isDirectory()) { 207 | fsExtra.ensureDirSync(destPath); 208 | emsPath && fsExtra.ensureDirSync(emsPath); 209 | } else if (stats.isFile()) { 210 | fsExtra.ensureDirSync(path.resolve(destPath, '..')); 211 | emsPath && fsExtra.ensureDirSync(path.resolve(emsPath, '..')); 212 | } else { 213 | logWarn(`The file or directory path which is "${srcPath}" cannot be found`); 214 | logWarn(`"${srcPath}" 不是有效的文件或文件夹路径`); 215 | continue; 216 | } 217 | fsExtra.copySync(srcPath, destPath); 218 | emsPath && fsExtra.copySync(srcPath, emsPath); 219 | } 220 | } 221 | 222 | try { 223 | if (verify && test) { 224 | await exec(['npm test'], () => logSuc('Unit Test!'), handleBuildErr('The unit test not pass(单元测试失败)')); 225 | } 226 | 227 | if (verify && eslint) { 228 | await exec(['npm run lint:es'], () => logSuc('Eslint!'), handleBuildErr(`The eslint not pass(eslint校验失败) \n try to exec(尝试执行): ${underline('npm run lint:es_fix')}`)); 229 | } 230 | 231 | if (verify && prettier) { 232 | await exec(['npm run lint:prettier'], () => logSuc('Prettier!'), handleBuildErr(`The prettier not pass(prettier校验失败) \n try to exec(尝试执行): ${underline('npm run lint:prettier_fix')}`)); 233 | } 234 | 235 | if (verify && stylelint) { 236 | await exec(['npm run lint:style'], () => logSuc('Stylelint!'), handleBuildErr(`The stylelint not pass(stylelint校验失败) \n try to exec(尝试执行): ${underline('npm run lint:style_fix')}`)); 237 | } 238 | 239 | let realOutDir: string = ''; 240 | const buildCliArr = []; 241 | const buildCliPath = { 242 | tsc: path.resolve(CWD, 'node_modules/typescript/bin/tsc'), 243 | tspc: path.resolve(CWD, 'node_modules/ts-patch/bin/tspc.js'), 244 | vuetsc: path.resolve(CWD, 'node_modules/vue-tsc/bin/vue-tsc.js'), 245 | rollup: path.resolve(CWD, 'node_modules/rollup/dist/bin/rollup'), 246 | webpack: path.resolve(CWD, 'node_modules/webpack-cli/bin/cli.js'), 247 | gulp: path.resolve(CWD, 'node_modules/gulp/bin/gulp.js'), 248 | next: path.resolve(CWD, 'node_modules/next/dist/bin/next') 249 | }; 250 | if (tool === 'tsc') { 251 | if (!typescript) { 252 | handleBuildErr('The typescript had been forbidden(已禁用 typescript,无法完成构建)')(); 253 | } 254 | 255 | let tscPath = buildCliPath.tsc; 256 | // ts-patch is preferred 257 | if (fs.existsSync(buildCliPath.tspc)) tscPath = buildCliPath.tspc; 258 | 259 | if (!fs.existsSync(tscPath)) { 260 | logWarn('Please install typescript first'); 261 | logWarn('请先安装 typescript 相关依赖'); 262 | const is_go_on = await installDenpendencies('tsc'); 263 | if (!is_go_on) return process.exit(0); 264 | tscPath = buildCliPath.tspc; 265 | } 266 | 267 | buildCliArr.push(`${tscPath} --outDir ${outDir} --project ${configurationPath || path.resolve(CWD, 'tsconfig.json')} --rootDir ${srcDir}`); 268 | esmDir && buildCliArr.push(`${tscPath} --module ESNext --target ES6 --outDir ${esmDir} --project ${configurationPath || path.resolve(CWD, 'tsconfig.json')} --rootDir ${srcDir}`); 269 | realOutDir = outDir; 270 | } else if (type === 'ssr-react') { 271 | const nextPath = buildCliPath.next; 272 | 273 | if (!fs.existsSync(nextPath)) { 274 | logWarn('Please install webpack first'); 275 | logWarn('请先安装 webpack 相关依赖'); 276 | const is_go_on = await installDenpendencies('next'); 277 | if (!is_go_on) return process.exit(0); 278 | } 279 | 280 | buildCliArr.push(`${nextPath} build`); 281 | } else { 282 | const content_rollup = !buildConfig && type === 'toolkit' && rollupConfig({ ts: typescript, multiOutput: true, srcDir, outDir, esmDir, configurationPath, pkjFieldName, configFileName }); 283 | const content_webpack = !buildConfig && (type === 'spa-react' || type === 'spa-react-pc' || type === 'spa-vue') && webpackConfig({ ts: typescript, multiOutput: false, srcDir, outDir, configurationPath, pkjFieldName, configFileName, hash }); 284 | const content_gulp = !buildConfig && (type === 'component-react' || type === 'component-vue') && gulpConfig({ srcDir, outDir, esmDir, configurationPath, pkjFieldName, configFileName }); 285 | const content_config = buildConfig || content_rollup || content_webpack || content_gulp; 286 | 287 | // put temporary file for build process 288 | if (content_config) { 289 | const buildConfigPath = path.resolve(__dirname, '../../../', '.omni_cache/build.config.js'); 290 | 291 | let is_go_on = true; 292 | if (type === 'toolkit') { 293 | const rollupPath = buildCliPath.rollup; 294 | 295 | if (!fs.existsSync(rollupPath)) { 296 | logWarn('Please install rollup first'); 297 | logWarn('请先安装 rollup 相关依赖'); 298 | is_go_on = await installDenpendencies('rollup'); 299 | } 300 | 301 | buildCliArr.push(`${rollupPath} -c ${buildConfigPath}`); 302 | } else if (type === 'spa-react' || type === 'spa-react-pc' || type === 'spa-vue') { 303 | const webpackPath = buildCliPath.webpack; 304 | 305 | if (!fs.existsSync(webpackPath)) { 306 | logWarn('Please install webpack first'); 307 | logWarn('请先安装 webpack 相关依赖'); 308 | is_go_on = await installDenpendencies('webpack'); 309 | } 310 | 311 | buildCliArr.push(`${webpackPath} --config ${buildConfigPath}`); 312 | } else if (type === 'component-react' || type === 'component-vue') { 313 | let tscPath = buildCliPath.tsc; 314 | const vTscPath = buildCliPath.vuetsc; 315 | const gulpPath = buildCliPath.gulp; 316 | // ts-patch is preferred 317 | if (fs.existsSync(buildCliPath.tspc)) tscPath = buildCliPath.tspc; 318 | 319 | if (typescript && (!fs.existsSync(tscPath) || (type === 'component-vue' && !fs.existsSync(vTscPath)))) { 320 | logWarn('Please install typescript first'); 321 | logWarn('请先安装 typescript 相关依赖'); 322 | is_go_on = await installDenpendencies('tsc'); 323 | if (is_go_on) tscPath = buildCliPath.tspc; 324 | } 325 | if (is_go_on && !fs.existsSync(gulpPath)) { 326 | logWarn('Please install gulp first'); 327 | logWarn('请先安装 gulp 相关依赖'); 328 | is_go_on = await installDenpendencies('gulp'); 329 | } 330 | 331 | buildCliArr.push( 332 | typescript ? `${tscPath} --outDir ${outDir} --project ${configurationPath || path.resolve(CWD, 'tsconfig.json')} --emitDeclarationOnly --rootDir ${srcDir}` : '', 333 | typescript && esmDir ? `${tscPath} --module ESNext --target ES6 --outDir ${esmDir} --project ${configurationPath || path.resolve(CWD, 'tsconfig.json')} --emitDeclarationOnly --rootDir ${srcDir}` : '', 334 | typescript && type === 'component-vue' ? `${vTscPath} --outDir ${outDir} --project ${configurationPath || path.resolve(CWD, 'tsconfig.json')} --emitDeclarationOnly --rootDir ${srcDir}` : '', 335 | typescript && esmDir && type === 'component-vue' ? `${vTscPath} --module ESNext --target ES6 --outDir ${esmDir} --project ${configurationPath || path.resolve(CWD, 'tsconfig.json')} --emitDeclarationOnly --rootDir ${srcDir}` : '', 336 | `${gulpPath} --gulpfile ${buildConfigPath} --cwd ${CWD}` 337 | ); 338 | } 339 | 340 | if (!is_go_on) return process.exit(0); 341 | 342 | outputFile({ 343 | file_path: buildConfigPath, 344 | file_content: content_config 345 | }); 346 | 347 | if (type === 'spa-react' || type === 'spa-react-pc' || type === 'spa-vue') { 348 | const bConfig = require(buildConfigPath); 349 | realOutDir = (bConfig && bConfig.output && bConfig.output.path) || outDir; 350 | } 351 | } 352 | } 353 | 354 | // loading 355 | if (type !== 'toolkit') { 356 | spinner.color('green'); 357 | spinner.prefix('moon'); 358 | spinner.state('start', 'Building, please wait patiently(项目构建中)'); 359 | } 360 | 361 | try { 362 | del.sync(realOutDir || outDir, { 363 | force: true 364 | }); 365 | esmDir && del.sync(esmDir, { 366 | force: true 367 | }); 368 | } catch (err) { 369 | logWarn(err as string); 370 | } 371 | 372 | await exec(buildCliArr, async function () { 373 | const { style, assets = [] } = reserve; 374 | if (type !== 'component-react' && type !== 'component-vue' && style) copyStylesheet(srcDir); 375 | copyReserves(assets); 376 | 377 | // handle build plugins 378 | const plugin_handles = plugins && plugins.length > 0 && getHandlers<'build'>(plugins as OmniPlugin<'build'>[], 'build'); 379 | if (plugin_handles) { 380 | for (const name in plugin_handles) { 381 | const handler = plugin_handles[name]; 382 | await handler({ 383 | type, 384 | template, 385 | build, 386 | release: configRelease 387 | }, { 388 | verify, 389 | buildConfig 390 | }); 391 | } 392 | } 393 | 394 | if (realOutDir && !fs.existsSync(realOutDir)) { 395 | handleBuildErr(`The output file ${realOutDir} doesn't exist(输出的 ${realOutDir} 文件不存在,构建失败)`)(); 396 | } else { 397 | type !== 'toolkit' && spinner.state('stop'); 398 | logTime('BUILD(项目构建)', true); 399 | const shouldExit = !(autoRelease || autoBuild); 400 | handleBuildSuc()(shouldExit); 401 | } 402 | }, handleBuildErr()); 403 | 404 | // auto release 405 | if (!autoBuild && autoRelease) { 406 | logEmph(italic('Start auto release')); 407 | logEmph(italic('开始自动发布')); 408 | try { 409 | await release(config, { verify: false }, autoRelease); 410 | handleBuildSuc('Auto release success(自动发布成功)!')(true); 411 | } catch (err) { 412 | handleBuildErr('Auto release failed(自动发布失败)!')(); 413 | } 414 | } 415 | } catch (err) { 416 | logErr(err as string); 417 | handleBuildErr('👆 Oops! Building process occured some accidents(糟糕!构建过程发生了点意外)!')(); 418 | } 419 | } -------------------------------------------------------------------------------- /src/commands/build/rollup.ts: -------------------------------------------------------------------------------- 1 | export default function (config: { 2 | ts: boolean; 3 | multiOutput: boolean; 4 | srcDir: string; 5 | outDir: string; 6 | esmDir: string; 7 | configurationPath?: string; 8 | configFileName?: string; 9 | pkjFieldName?: string; 10 | }) { 11 | const { ts, multiOutput, srcDir = 'src', outDir = 'lib', esmDir, configurationPath, pkjFieldName = 'omni', configFileName = 'omni.config.js' } = config; 12 | 13 | return `'use strict'; 14 | 15 | const fs = require('fs'); 16 | const path = require('path'); 17 | const { requireCwd, logErr } = require('@omni-door/utils'); 18 | const { nodeResolve: resolve } = requireCwd('@rollup/plugin-node-resolve', true) || {}; 19 | const commonjs = requireCwd('@rollup/plugin-commonjs', true); 20 | const { babel } = requireCwd('@rollup/plugin-babel', true) || {}; 21 | const json = requireCwd('@rollup/plugin-json', true); 22 | ${ts ? `const typescript = requireCwd('@rollup/plugin-typescript', true); 23 | ` : ''} 24 | const pkj = requireCwd('./package.json'); 25 | const configFilePath = (pkj && pkj.${pkjFieldName} && pkj.${pkjFieldName}.filePath) || './${configFileName}'; 26 | const configs = requireCwd(configFilePath); 27 | ${configurationPath ? `const customConfig = require('${configurationPath}') 28 | ` : ''} 29 | const { build } = configs || {}; 30 | const { configuration = getConfig => getConfig(true) } = build || {}; 31 | 32 | const extensions = ['.ts', '.js']; 33 | const tsExcludes = ['node_modules/**', '**/__test__/*']; 34 | const babelConfig = { 35 | exclude: 'node_modules/**', 36 | plugins: [['@babel/plugin-transform-runtime', { useESModules: false, corejs: 3 }]], 37 | babelHelpers: 'runtime', 38 | extensions 39 | }; 40 | const resolveConfig = { 41 | extensions, 42 | preferBuiltins: true, 43 | browser: true 44 | }; 45 | const tsconfig = { 46 | cjs: (dir = '', emit = false) => ({ 47 | compilerOptions: { 48 | target: 'es5', 49 | module: 'esnext', 50 | declaration: emit, 51 | outDir: path.resolve('${outDir}', dir), 52 | emitDeclarationOnly: emit 53 | }, 54 | exclude: tsExcludes 55 | }), 56 | esm: (dir = '', emit = false) => ({ 57 | compilerOptions: { 58 | target: 'esnext', 59 | module: 'esnext', 60 | declaration: emit, 61 | outDir: path.resolve('${esmDir}', dir), 62 | emitDeclarationOnly: emit 63 | }, 64 | exclude: tsExcludes 65 | }) 66 | }; 67 | 68 | let indexPath = ''; 69 | const exts = ['ts', 'tsx', 'jsx', 'js']; 70 | for (let i = 0, len = exts.length; i < len; i++) { 71 | indexPath = path.resolve('${srcDir}', \`index.\${exts[i]}\`); 72 | if (fs.existsSync(indexPath)) break; 73 | if (i === len - 1) { 74 | logErr('请以 index 为名称指定正确的入口文件!(Please specify the correct entry file with name of index)'); 75 | process.exit(1); 76 | } 77 | } 78 | 79 | function flatten (arr) { 80 | return arr.reduce(function(prev, next){ 81 | return prev.concat(Array.isArray(next) ? flatten(next) : next); 82 | }, []); 83 | } 84 | 85 | function createConfig (bundle = true) { 86 | const filesPaths = []; 87 | ${multiOutput ? `const getEntryPath = (files, preDir) => { 88 | const len = files.length; 89 | for (let i = 0; i < len; i++) { 90 | const file = files[i]; 91 | if (file.includes('test')) continue; 92 | const preDirPath = path.join(preDir, file); 93 | const filePath = path.resolve('${srcDir}', preDirPath); 94 | const stats = fs.statSync(filePath); 95 | if (stats.isDirectory()) { 96 | getEntryPath(fs.readdirSync(filePath), preDirPath); 97 | } else { 98 | let entryPath = ''; 99 | const extArr = path.extname(filePath).split('.'); 100 | entryPath = (extArr.length && !!~exts.indexOf(extArr[1])) ? filePath : ''; 101 | if (!entryPath || !fs.existsSync(entryPath) || fs.existsSync(path.resolve(filePath, '.buildignore'))) { 102 | tsExcludes.push(\`\${filePath}/*\`); 103 | continue; 104 | } 105 | filesPaths.push({ 106 | entry: entryPath, 107 | file: path.join(file, 'index.js'), 108 | dir: preDir 109 | }); 110 | } 111 | } 112 | }; 113 | getEntryPath(fs.readdirSync('${srcDir}'), ''); 114 | ` : ''} 115 | return bundle ? [ 116 | { 117 | input: indexPath, 118 | output: { 119 | file: '${outDir}/index.js', 120 | format: 'cjs', 121 | exports: 'named', 122 | compact: true 123 | }, 124 | plugins: [ 125 | resolve(resolveConfig), 126 | commonjs(), 127 | ${ts ? 'typescript(tsconfig.cjs(\'\', true)),' : ''} 128 | babel(babelConfig), 129 | json() 130 | ] 131 | }, 132 | ${esmDir 133 | ? ` 134 | { 135 | input: indexPath, 136 | output: { 137 | file: '${esmDir}/index.js', 138 | format: 'esm', 139 | compact: true 140 | }, 141 | plugins: [ 142 | resolve(resolveConfig), 143 | commonjs({ transformMixedEsModules: true, esmExternals: true }), 144 | ${ts ? 'typescript(tsconfig.esm(\'\', true)),' : ''} 145 | json() 146 | ] 147 | },` 148 | : ''} 149 | 150 | ...flatten(filesPaths.map(fileParams => { 151 | const { entry, file, dir } = fileParams; 152 | return [ 153 | { 154 | input: entry, 155 | output: { 156 | dir: path.resolve('${outDir}', dir), 157 | format: 'cjs', 158 | exports: 'named', 159 | compact: true 160 | }, 161 | plugins: [ 162 | resolve(resolveConfig), 163 | commonjs(), 164 | ${ts ? 'typescript(tsconfig.cjs(dir)),' : ''} 165 | json() 166 | ] 167 | }, 168 | ${esmDir 169 | ? ` 170 | { 171 | input: entry, 172 | output: { 173 | dir: path.resolve('${esmDir}', dir), 174 | format: 'esm', 175 | compact: true 176 | }, 177 | plugins: [ 178 | resolve(resolveConfig), 179 | commonjs({ transformMixedEsModules: true, esmExternals: true }), 180 | ${ts ? 'typescript(tsconfig.esm(dir)),' : ''} 181 | json() 182 | ] 183 | }` 184 | : ''} 185 | ]; 186 | })) 187 | ] : [ 188 | { 189 | input: indexPath, 190 | output: { 191 | dir: '${outDir}', 192 | format: 'cjs', 193 | exports: 'named', 194 | compact: true, 195 | preserveModules: true, 196 | preserveModulesRoot: '${srcDir}' 197 | }, 198 | plugins: [ 199 | resolve(resolveConfig), 200 | commonjs(), 201 | ${ts ? 'typescript(tsconfig.cjs(\'\', true)),' : ''} 202 | babel(babelConfig), 203 | json() 204 | ] 205 | }, 206 | ${esmDir 207 | ? ` 208 | { 209 | input: indexPath, 210 | output: { 211 | dir: '${esmDir}', 212 | format: 'esm', 213 | compact: true, 214 | preserveModules: true, 215 | preserveModulesRoot: '${srcDir}' 216 | }, 217 | plugins: [ 218 | resolve(resolveConfig), 219 | commonjs({ transformMixedEsModules: true, esmExternals: true }), 220 | ${ts ? 'typescript(tsconfig.esm(\'\', true)),' : ''} 221 | json() 222 | ] 223 | },` 224 | : ''} 225 | ] 226 | }; 227 | 228 | module.exports = ${ 229 | configurationPath 230 | ? 'typeof customConfig === \'function\' ? customConfig((bundle = true) => createConfig(bundle)) : customConfig' 231 | : 'configuration((bundle = true) => createConfig(bundle));' 232 | } 233 | `; 234 | } -------------------------------------------------------------------------------- /src/commands/build/webpack.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | /* import types */ 3 | import type { HASH } from '@omni-door/utils'; 4 | 5 | export default function (config: { 6 | ts: boolean; 7 | multiOutput: boolean; 8 | srcDir: string; 9 | outDir: string; 10 | configurationPath?: string; 11 | pkjFieldName?: string; 12 | configFileName?: string; 13 | hash?: boolean | HASH; 14 | }) { 15 | const { multiOutput, srcDir = path.resolve(__dirname, '../src/'), outDir = path.resolve(__dirname, '../lib/'), configurationPath, pkjFieldName = 'omni', configFileName = 'omni.config.js', hash } = config; 16 | const hashType = 17 | typeof hash === 'string' 18 | ? hash 19 | : hash 20 | ? 'contenthash' 21 | : hash; 22 | 23 | return `'use strict'; 24 | 25 | const fs = require('fs'); 26 | const path = require('path'); 27 | const { requireCwd } = require('@omni-door/utils'); 28 | const merge = requireCwd('webpack-merge'); 29 | 30 | const ppkj = requireCwd('./package.json'); 31 | const configFilePath = (ppkj && ppkj.${pkjFieldName} && ppkj.${pkjFieldName}.filePath) || './${configFileName}'; 32 | const configs = requireCwd(configFilePath); 33 | ${configurationPath ? `const customConfig = require('${configurationPath}')` : ''} 34 | 35 | const { build } = configs || {}; 36 | const { configuration = config => config } = build || {}; 37 | 38 | ${ 39 | multiOutput 40 | ? `const entriesPath = '${srcDir}'; 41 | const entriesList = getFolders(entriesPath); 42 | 43 | function getFolders (folderPath) { 44 | const list = fs.readdirSync(folderPath) 45 | const folderList = list.filter((v, k) => { 46 | const stats = fs.statSync(\`\${folderPath}/\${v}\`); 47 | return stats.isDirectory(); 48 | }) 49 | return folderList; 50 | }` 51 | : '' 52 | } 53 | 54 | let indexPath = ''; 55 | const exts = ['ts', 'tsx', 'js', 'jsx']; 56 | for (let i = 0, len = exts.length; i < len; i++) { 57 | indexPath = path.resolve('${srcDir}', \`index.\${exts[i]}\`); 58 | if (fs.existsSync(indexPath)) break; 59 | } 60 | const entry = { 61 | index: indexPath 62 | }; 63 | 64 | ${ 65 | multiOutput 66 | ? `entriesList.forEach(v => { 67 | if (v !== 'style' && v !== 'styles') { 68 | let entryPath = ''; 69 | for (let i = 0, len = exts.length; i < len; i++) { 70 | entryPath = path.resolve('${srcDir}', \`\${v}/index.\${exts[i]}\`); 71 | if (fs.existsSync(entryPath)) break; 72 | } 73 | entry[v] = entryPath; 74 | } 75 | })` 76 | : '' 77 | } 78 | 79 | const basicConfig = { 80 | entry, 81 | output: { 82 | filename: '${hashType ? `[name].[${hashType}:8].js` : '[name].js'}', 83 | path: '${outDir}' 84 | }, 85 | mode: 'production' 86 | }; 87 | 88 | module.exports = ${ 89 | configurationPath 90 | ? 'merge(basicConfig, customConfig);' 91 | : 'configuration(basicConfig);' 92 | }`; 93 | } -------------------------------------------------------------------------------- /src/commands/commands.ts: -------------------------------------------------------------------------------- 1 | import initial from './initial'; 2 | import dev from './dev'; 3 | import start from './start'; 4 | import newTpl from './new'; 5 | import build from './build'; 6 | import release from './release'; 7 | import { setLogo, setBrand } from '@omni-door/utils'; 8 | 9 | export { default as initial } from './initial'; 10 | export { default as dev } from './dev'; 11 | export { default as start } from './start'; 12 | export { default as newTpl } from './new'; 13 | export { default as build } from './build'; 14 | export { default as release } from './release'; 15 | export { setLogo, setBrand } from '@omni-door/utils'; 16 | 17 | export default { 18 | initial, 19 | dev, 20 | start, 21 | new: newTpl, 22 | build, 23 | release, 24 | setLogo, 25 | setBrand 26 | }; -------------------------------------------------------------------------------- /src/commands/dev/__test__/index.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha'; 2 | import { expect } from 'chai'; 3 | import dev from '../index'; 4 | import run from '../run'; 5 | import server from '../server'; 6 | 7 | describe('dev command test', function () { 8 | it('type checking', function () { 9 | expect(dev).to.be.a('function'); 10 | expect(run).to.be.a('function'); 11 | expect(server).to.be.a('function'); 12 | }); 13 | }); -------------------------------------------------------------------------------- /src/commands/dev/index.ts: -------------------------------------------------------------------------------- 1 | import run from './run'; 2 | import { logWarn, nodeVersionCheck } from '@omni-door/utils'; 3 | import { signal } from '../../utils'; 4 | /* import types */ 5 | import type { OmniConfig } from '../../index.d'; 6 | 7 | function handleException (msg?: string) { 8 | logWarn(msg || 'Oops! Some unknown errors have occurred(发生了一些未知错误)!'); 9 | process.exit(0); 10 | } 11 | 12 | export default async function (config: OmniConfig | null, options: { 13 | port?: number | string; 14 | hostname?: string; 15 | }) { 16 | try { 17 | // node version pre-check 18 | await nodeVersionCheck('8'); 19 | } catch (e) { 20 | logWarn(e as string); 21 | } 22 | 23 | if (!config || JSON.stringify(config) === '{}') { 24 | handleException('Please initialize project first(请先初始化项目)!'); 25 | } 26 | 27 | const p = options.port; 28 | const h = options.hostname; 29 | const { type, dev, server } = config!; 30 | 31 | if (!dev || JSON.stringify(dev) === '{}') { 32 | handleException('The dev field is missing in config file(配置文件 dev 字段缺失)!'); 33 | } 34 | 35 | const { serverType: server_type, ...serverOptions } = server || {}; 36 | const { 37 | port, 38 | host, 39 | devMiddlewareOptions, 40 | webpack, 41 | proxy, 42 | middleware, 43 | serverType = 'default', 44 | ...rest 45 | } = { ...serverOptions, ...dev }; 46 | 47 | // bind exit signals 48 | signal(); 49 | 50 | const EWServerList = [ 'spa-react', 'spa-react-pc', 'spa-vue' ]; 51 | if (~EWServerList.indexOf(type) && !webpack) { 52 | handleException(`The ${type}-app missing the dev-server webpack-config(${type}应用 缺少开发服务webpack配置文件)!`); 53 | } 54 | 55 | const _port = !isNaN(+p!) 56 | ? +p! 57 | : !isNaN(+port!) 58 | ? +port! 59 | : 6200; 60 | 61 | run({ 62 | ...rest, 63 | p: _port, 64 | host: h || host, 65 | webpackConfig: webpack!, 66 | devMiddlewareOptions, 67 | proxyConfig: proxy, 68 | middlewareConfig: middleware, 69 | serverType, 70 | projectType: type 71 | }); 72 | } -------------------------------------------------------------------------------- /src/commands/dev/open.chrome.applescript: -------------------------------------------------------------------------------- 1 | property targetTab: null 2 | property targetTabIndex: -1 3 | property targetWindow: null 4 | property theProgram: "Google Chrome" 5 | 6 | on run argv 7 | set theURL to item 1 of argv 8 | 9 | -- Allow requested program to be optional, 10 | -- default to Google Chrome 11 | if (count of argv) > 1 then 12 | set theProgram to item 2 of argv 13 | end if 14 | 15 | using terms from application "Google Chrome" 16 | tell application theProgram 17 | 18 | if (count every window) = 0 then 19 | make new window 20 | end if 21 | 22 | -- 1: Looking for tab running debugger 23 | -- then, Reload debugging tab if found 24 | -- then return 25 | set found to my lookupTabWithUrl(theURL) 26 | if found then 27 | set targetWindow's active tab index to targetTabIndex 28 | tell targetTab to reload 29 | tell targetWindow to activate 30 | set index of targetWindow to 1 31 | return 32 | end if 33 | 34 | -- 2: Looking for Empty tab 35 | -- In case debugging tab was not found 36 | -- We try to find an empty tab instead 37 | set found to my lookupTabWithUrl("chrome://newtab/") 38 | if found then 39 | set targetWindow's active tab index to targetTabIndex 40 | set URL of targetTab to theURL 41 | tell targetWindow to activate 42 | return 43 | end if 44 | 45 | -- 3: Create new tab 46 | -- both debugging and empty tab were not found 47 | -- make a new tab with url 48 | tell window 1 49 | activate 50 | make new tab with properties {URL:theURL} 51 | end tell 52 | end tell 53 | end using terms from 54 | end run 55 | 56 | -- Function: 57 | -- Lookup tab with given url 58 | -- if found, store tab, index, and window in properties 59 | -- (properties were declared on top of file) 60 | on lookupTabWithUrl(lookupUrl) 61 | using terms from application "Google Chrome" 62 | tell application theProgram 63 | -- Find a tab with the given url 64 | set found to false 65 | set theTabIndex to -1 66 | repeat with theWindow in every window 67 | set theTabIndex to 0 68 | repeat with theTab in every tab of theWindow 69 | set theTabIndex to theTabIndex + 1 70 | if (theTab's URL as string) contains lookupUrl then 71 | -- assign tab, tab index, and window to properties 72 | set targetTab to theTab 73 | set targetTabIndex to theTabIndex 74 | set targetWindow to theWindow 75 | set found to true 76 | exit repeat 77 | end if 78 | end repeat 79 | 80 | if found then 81 | exit repeat 82 | end if 83 | end repeat 84 | end tell 85 | end using terms from 86 | return found 87 | end lookupTabWithUrl 88 | -------------------------------------------------------------------------------- /src/commands/dev/open.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | import { requireCwd } from '@omni-door/utils'; 3 | 4 | export default async function (url: string) { 5 | if (process.platform === 'darwin') { 6 | // Will use the first open browser found from list 7 | const supportedChromiumBrowsers = [ 8 | 'Google Chrome Canary', 9 | 'Google Chrome', 10 | 'Microsoft Edge', 11 | 'Brave Browser', 12 | 'Vivaldi', 13 | 'Chromium', 14 | ]; 15 | 16 | for (const chromiumBrowser of supportedChromiumBrowsers) { 17 | try { 18 | // Try our best to reuse existing tab 19 | // on OSX Chromium-based browser with AppleScript 20 | execSync('ps cax | grep "' + chromiumBrowser + '"'); 21 | execSync( 22 | 'osascript open.chrome.applescript "' + 23 | encodeURI(url) + 24 | '" "' + 25 | chromiumBrowser + 26 | '"', 27 | { 28 | cwd: __dirname, 29 | stdio: 'ignore', 30 | } 31 | ); 32 | return true; 33 | } catch (err) { 34 | // Ignore errors. 35 | } 36 | } 37 | } 38 | 39 | // Fallback to open 40 | // (It will always open new tab) 41 | try { 42 | const open = requireCwd('open'); 43 | await open(url, { wait: false, url: true }); 44 | return true; 45 | } catch (err) { 46 | return false; 47 | } 48 | } -------------------------------------------------------------------------------- /src/commands/dev/run.ts: -------------------------------------------------------------------------------- 1 | import inquirer from 'inquirer'; 2 | import { logErr, requireCwd } from '@omni-door/utils'; 3 | import server from './server'; 4 | import { logo } from '../../utils'; 5 | /* import types */ 6 | import type { ServerOptions } from './server'; 7 | 8 | export default async function ({ 9 | p, 10 | ...rest 11 | }: ServerOptions): Promise { 12 | try { 13 | const detectPort = requireCwd('detect-port'); 14 | 15 | const _p = await detectPort(p).catch((err: any) => { 16 | logErr(err); 17 | return process.exit(1); 18 | }); 19 | 20 | if (p !== _p) { 21 | inquirer.prompt([ 22 | { 23 | name: 'changePort', 24 | type: 'confirm', 25 | message: `The port ${p} is not available, would you like to run on ${_p}(${logo()} ${p} 端口被占用了,切换到 ${_p})?`, 26 | default: true 27 | } 28 | ]).then(answer => { 29 | if (answer.changePort) { 30 | server(Object.assign(rest, { p: _p })); 31 | } else { 32 | process.exit(0); 33 | } 34 | }); 35 | } else { 36 | server(Object.assign(rest, { p })); 37 | } 38 | } catch (err) { 39 | logErr(err as string); 40 | process.exit(1); 41 | } 42 | } -------------------------------------------------------------------------------- /src/commands/dev/server.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import * as mkcert from 'mkcert'; 4 | import { 5 | logWarn, 6 | logErr, 7 | requireCwd, 8 | exec, 9 | outputFile 10 | } from '@omni-door/utils'; 11 | import { EWServer, KNServer } from '../servers'; 12 | import open from './open'; 13 | /* import types */ 14 | import type { Config } from 'http-proxy-middleware'; 15 | import type { EWServerParams } from '../servers'; 16 | import type { PROJECT_TYPE } from '@omni-door/utils'; 17 | import type { KoaApp, NextRouter, ServerType, PathParams, MiddleWareCallback, Method } from '../../index.d'; 18 | 19 | export type KoaCtx = KoaApp.ParameterizedContext; 20 | 21 | // types-proxy 22 | export type ProxyItem = { route: PathParams; config: Config; }; 23 | export type ProxyConfig = (ProxyItem | ProxyFn)[]; 24 | export type ProxyFn = (params: { 25 | ip: string; 26 | port: number; 27 | host?: string; 28 | middlewareConfig?: MiddlewareConfig; 29 | }) => ProxyItem; 30 | 31 | // types-middleware 32 | export type MiddlewareItem = { route: PathParams; callback: MiddleWareCallback; method?: Method }; 33 | export type MiddlewareConfig = (MiddlewareItem | MiddlewareFn)[]; 34 | export type MiddlewareFn = (params: { 35 | ip: string; 36 | port: number; 37 | host?: string; 38 | proxyConfig?: ProxyConfig; 39 | }) => MiddlewareItem; 40 | 41 | // types-cors 42 | export type CorsConfig = { 43 | origin?: string | ((ctx: KoaCtx) => string); 44 | allowMethods?: string | string[]; 45 | exposeHeaders?: string | string[]; 46 | allowHeaders?: string | string[]; 47 | maxAge?: string | number; 48 | credentials?: boolean | ((ctx: KoaCtx) => string); 49 | keepHeadersOnError?: boolean; 50 | secureContext?: boolean; 51 | privateNetworkAccess?: boolean; 52 | } 53 | 54 | // types-server 55 | type EWServerOptions = Pick 56 | export type ServerOptions = { 57 | p: number; 58 | host?: string; 59 | https?: boolean | { key: string; cert: string; }; 60 | CA?: { 61 | organization?: string; 62 | countryCode?: string; 63 | state?: string; 64 | locality?: string; 65 | validityDays?: number; 66 | }; 67 | proxyConfig?: ProxyConfig; 68 | middlewareConfig?: MiddlewareConfig; 69 | corsConfig?: CorsConfig; 70 | serverType: ServerType; 71 | projectType: PROJECT_TYPE; 72 | nextRouter?: NextRouter; 73 | } & EWServerOptions; 74 | 75 | async function server ({ 76 | p, 77 | host, 78 | https: httpsConfig, 79 | CA, 80 | serverType, 81 | projectType, 82 | devMiddlewareOptions = {}, 83 | webpackConfig, 84 | proxyConfig = [], 85 | middlewareConfig = [], 86 | nextRouter, 87 | corsConfig, 88 | favicon 89 | }: ServerOptions): Promise { 90 | try { 91 | const CWD = process.cwd(); 92 | const ip = requireCwd('ip'); 93 | const ipAddress: string = ip.address(); 94 | const serverHost = host || '0.0.0.0'; 95 | const openHost = host || ipAddress || '0.0.0.0'; 96 | let serverUrl = openHost + ':' + p; 97 | 98 | const nextDevCli = `${path.resolve(CWD, 'node_modules/.bin/next')} dev --port ${p} --hostname ${serverHost}`; 99 | const ServerDevCli = { 100 | storybook: `${path.resolve(CWD, 'node_modules/.bin/storybook')} dev -p ${p} -h ${serverHost} --disable-telemetry --quiet --ci`, 101 | dumi: `PORT=${p} ${path.resolve(CWD, 'node_modules/.bin/dumi')} dev`, 102 | 'next-app': nextDevCli, 103 | 'next-pages': nextDevCli, 104 | nuxt: `${path.resolve(CWD, 'node_modules/.bin/nuxt')} dev --port ${p} --hostname ${serverHost}` 105 | }; 106 | const autoOpenServer = [ 107 | 'storybook', 108 | 'next-app', 109 | 'next-pages', 110 | 'dumi' 111 | ]; 112 | 113 | const needCustomServer = !serverType || serverType === 'default' || serverType === 'express-webpack'; 114 | if (needCustomServer) { 115 | let isHttps = false; 116 | let key, cert; 117 | if (httpsConfig) { 118 | if (typeof httpsConfig === 'boolean') { 119 | try { 120 | const cacheDirPath = path.resolve(__dirname, '../../../.omni_cache'); 121 | const keyPath = path.resolve(cacheDirPath, `${openHost}-key.pem`); 122 | const certPath = path.resolve(cacheDirPath, `${openHost}-cert.pem`); 123 | 124 | if (fs.existsSync(keyPath)) { 125 | key = fs.readFileSync(keyPath); 126 | cert = fs.readFileSync(certPath); 127 | } else { 128 | const ca = await mkcert.createCA({ 129 | organization: 'OMNI-DOOR', 130 | countryCode: 'CN', 131 | state: 'SHANGHAI', 132 | locality: 'SONGJIANG', 133 | validityDays: 365, 134 | ...CA 135 | }); 136 | 137 | const certificate = await mkcert.createCert({ 138 | domains: [openHost, '127.0.0.1', 'localhost'], 139 | validityDays: 365, 140 | caKey: ca.key, 141 | caCert: ca.cert 142 | }); 143 | key = certificate.key; 144 | cert = certificate.cert; 145 | outputFile({ 146 | file_path: keyPath, 147 | file_content: key 148 | }); 149 | outputFile({ 150 | file_path: certPath, 151 | file_content: cert 152 | }); 153 | } 154 | 155 | isHttps = true; 156 | } catch (err) { 157 | logWarn(err as string); 158 | logWarn(`Failing to generate the certificate (生成证书失败)!\nYou can specify the certificate manually (可通过以下方式手动指定证书): 159 | 160 | https: { 161 | key: fs.readFileSync(path.resolve(\${your_path_to_key})), 162 | cert: fs.readFileSync(path.resolve(\${your_path_to_cert})) 163 | }`); 164 | isHttps = false; 165 | } 166 | } else { 167 | key = httpsConfig.key; 168 | cert = httpsConfig.cert; 169 | } 170 | } 171 | 172 | if (isHttps && (!key || !cert)) { 173 | logWarn('Missing the certificate, start the dev-server with http'); 174 | logWarn('证书缺失,将以http启动开发服务'); 175 | isHttps = false; 176 | } 177 | 178 | const serverBasicOptions = { 179 | middlewareConfig, 180 | proxyConfig, 181 | ipAddress, 182 | host: openHost, 183 | listenHost: serverHost, 184 | port: p, 185 | httpsConfig: isHttps ? { key, cert } : void 0 186 | }; 187 | 188 | switch (projectType) { 189 | case 'ssr-react': 190 | KNServer({ 191 | dev: process.env.NODE_ENV === 'production' ? false : true, 192 | nextRouter, 193 | corsConfig, 194 | ...serverBasicOptions 195 | }); 196 | break; 197 | case 'ssr-vue': 198 | logWarn('Not support ssr-vue yet'); 199 | logWarn('暂不支持 ssr-vue 项目'); 200 | break; 201 | case 'spa-react': 202 | case 'spa-react-pc': 203 | case 'spa-vue': 204 | EWServer({ 205 | webpackConfig, 206 | devMiddlewareOptions, 207 | favicon, 208 | ...serverBasicOptions 209 | }); 210 | break; 211 | } 212 | } else { 213 | serverUrl = 'http://' + serverUrl; 214 | exec([ServerDevCli[serverType as keyof typeof ServerDevCli]]); 215 | if (~autoOpenServer.indexOf(serverType)) { 216 | const detectPort = requireCwd('detect-port'); 217 | const openAfterPortAvailable = () => { 218 | setTimeout(() => { 219 | detectPort(p) 220 | .then((_p : number) => { 221 | if (_p === p) { 222 | openAfterPortAvailable(); 223 | } else { 224 | open(serverUrl); 225 | } 226 | }) 227 | .catch((err: any) => { 228 | logWarn(err); 229 | open(serverUrl); 230 | }); 231 | }, 1000); 232 | }; 233 | openAfterPortAvailable(); 234 | } 235 | } 236 | 237 | } catch (err) { 238 | logErr(err as string); 239 | process.exit(1); 240 | } 241 | } 242 | 243 | export default server; -------------------------------------------------------------------------------- /src/commands/index.ts: -------------------------------------------------------------------------------- 1 | import program from 'commander'; 2 | import leven from 'leven'; 3 | import chalk from 'chalk'; 4 | import { nodeVersionCheck, npmVersionCheck, updateNotifier, logErr, logWarn, requireCwd, logInfo } from '@omni-door/utils'; 5 | /* import types */ 6 | import type { OmniConfig } from '../index.d'; 7 | 8 | const commandDicts = { 9 | init: 'init', 10 | dev: 'dev', 11 | start: 'start', 12 | new: 'new', 13 | build: 'build', 14 | release: 'release' 15 | }; 16 | 17 | (async function () { 18 | try { 19 | await nodeVersionCheck('10.13.0'); 20 | } catch (e) { 21 | logWarn(e as string); 22 | } 23 | 24 | const { initial, dev, start, newTpl, build, release } = require('./commands'); 25 | const pkj = require('../../package.json'); 26 | let config: OmniConfig | null = null; 27 | let configFilePath = './omni.config.js'; 28 | 29 | function getConfig (silent?: boolean) { 30 | try { 31 | const ppkj = requireCwd('./package.json', true); 32 | configFilePath = ppkj?.omni?.filePath || configFilePath; 33 | config = requireCwd(configFilePath, silent); 34 | } catch (e) { 35 | logWarn(e as string); 36 | } 37 | } 38 | 39 | function checkConfig () { 40 | if (!config) { 41 | logWarn(`Please initialize project first or checking the "${configFilePath}" configuration file`); 42 | logWarn(`请先初始化项目或检查 "${configFilePath}" 配置文件是否存在问题`); 43 | process.exit(0); 44 | } 45 | } 46 | 47 | function changeCWD (workPath: string) { 48 | try { 49 | process.chdir(workPath); 50 | const cwd = process.cwd(); 51 | logInfo(`The work path change to "${cwd}"`); 52 | logInfo(`工作路径变更为 "${cwd}"`); 53 | } catch (err) { 54 | logWarn(`Please checking the "${workPath}" had existed`); 55 | logWarn(`工作路径变更失败,请检查 "${workPath}" 是否存在`); 56 | process.exit(0); 57 | } 58 | } 59 | 60 | program 61 | .version(pkj.version, '-v, --version') 62 | .name('omni') 63 | .usage('[command] [options]'); 64 | 65 | program 66 | .command(`${commandDicts.init} [strategy]`) 67 | .option('-rb, --react_basic [name]', 'create a basic React SPA project') 68 | .option('-rs, --react_standard [name]', 'create a standard React SPA project') 69 | .option('-re, --react_entire [name]', 'create a most versatile React SPA project') 70 | .option('-rp, --react_pc [name]', 'create a React SPA project based on antd') 71 | .option('-vb, --vue_basic [name]', 'create a basic Vue SPA project') 72 | .option('-vs, --vue_standard [name]', 'create a standard Vue SPA project') 73 | .option('-ve, --vue_entire [name]', 'create a most versatile Vue SPA project') 74 | .option('-rS, --react_ssr [name]', 'create a React component library') 75 | .option('-rc, --react_components [name]', 'create a React component library') 76 | .option('-vc, --vue_components [name]', 'create a Vue component library') 77 | .option('-t, --toolkit [name]', 'create a toolkit project') 78 | .option('-n, --no-install', 'init project without install dependencies') 79 | .option('-P, --path ', 'the workpath for init the project') 80 | .description('initialize your project, [strategy] could be stable(default) or latest', { 81 | strategy: 'stable or latest', 82 | }) 83 | .usage('[strategy] [options]') 84 | .action((strategy, options) => { 85 | updateNotifier(pkj); 86 | 87 | const workPath = options.path; 88 | if (workPath) changeCWD(workPath); 89 | 90 | const CLITAG = pkj?.version?.match?.(/[a-zA-Z]+/g)?.[0]; 91 | const TPLTAG = pkj?.version?.match?.(/[0-9]+\.[0-9]+/g)?.[0]; 92 | const CLICURRENTVERSION = pkj?.version?.match?.(/[0-9]+\.[0-9]+\.[0-9]+/g)?.[0]; 93 | 94 | initial(strategy, options, { tplPkjTag: TPLTAG ? `~${TPLTAG}` : 'latest', tplPkjParams: [ `tag=${CLITAG || (CLICURRENTVERSION ? `~${CLICURRENTVERSION}` : 'latest')}` ] }); 95 | }); 96 | 97 | program 98 | .command(commandDicts.dev) 99 | .option('-p, --port ', 'start the dev-server according to the specified port') 100 | .option('-H, --hostname ', 'start the dev-server according to the specified hostname') 101 | .option('-P, --path ', 'the workpath for start the dev-server') 102 | .description('omni dev [-p ] [-H ] [-P ]', { 103 | port: 'The dev-server listen port.', 104 | host: 'The dev-server running hostname.', 105 | path: 'The cli workpath for running dev-server.' 106 | }) 107 | .action((options) => { 108 | const workPath = options.path; 109 | if (workPath) changeCWD(workPath); 110 | 111 | getConfig(!!workPath); 112 | checkConfig(); 113 | npmVersionCheck(pkj.name, pkj.version); 114 | 115 | dev(config, options); 116 | }); 117 | 118 | program 119 | .command(commandDicts.start) 120 | .option('-p, --port ', 'start the prod-server according to the specified port') 121 | .option('-H, --hostname ', 'start the prod-server according to the specified hostname') 122 | .option('-P, --path ', 'the workpath for start the prod-server') 123 | .description('omni start [-p ] [-H ] [-P ]', { 124 | port: 'The prod-server listen port.', 125 | host: 'The prod-server running hostname.', 126 | path: 'The cli workpath for running prod-server.' 127 | }) 128 | .action((options) => { 129 | const workPath = options.path; 130 | if (workPath) changeCWD(workPath); 131 | 132 | getConfig(!!workPath); 133 | checkConfig(); 134 | 135 | start(config, options); 136 | }); 137 | 138 | program 139 | .command(`${commandDicts.new} [name]`) 140 | .option('-f, --function', 'create a React-Function-Component') 141 | .option('-c, --class', 'create a React-Class-Component') 142 | .option('-r, --render', 'create a Vue-Render-Function') 143 | .option('-s, --single', 'create a Vue-Single-File-Component') 144 | .option('-P, --path ', 'the workpath for create component') 145 | .description('omni new [name] [-f | -c] [-P ]', { 146 | name: 'The name of component.', 147 | }) 148 | .usage('[name] [options]') 149 | .action((componentName, options) => { 150 | const workPath = options.path; 151 | if (workPath) changeCWD(workPath); 152 | 153 | getConfig(!!workPath); 154 | checkConfig(); 155 | updateNotifier(pkj); 156 | const TPLTAG = pkj?.version?.match?.(/[0-9]\.[0-9]/g)?.[0]; 157 | newTpl(config, componentName, { ...options, tplPkjTag: TPLTAG }); 158 | }); 159 | 160 | program 161 | .command(commandDicts.build) 162 | .option('-c, --config ', 'specify the path of config file') 163 | .option('-n, --no-verify', 'bypass all pre-check before building') 164 | .option('-P, --path ', 'the workpath for build project') 165 | .description('build your project according to the [omni.config.js]\'s build field') 166 | .action((buildTactic) => { 167 | const workPath = buildTactic.path; 168 | if (workPath) changeCWD(workPath); 169 | 170 | getConfig(!!workPath); 171 | checkConfig(); 172 | npmVersionCheck(pkj.name, pkj.version); 173 | 174 | build(config, buildTactic); 175 | }); 176 | 177 | program 178 | .command(commandDicts.release) 179 | .option('-a, --automatic', 'auto-increase the version of iteration') 180 | .option('-i, --ignore', 'ignoring the version of iteration') 181 | .option('-m, --manual ', 'manual specify the version of iteration') 182 | .option('-t, --tag ', 'the tag will add to npm-package') 183 | .option('-n, --no-verify', 'bypass all pre-check before release') 184 | .option('-P, --path ', 'the workpath for release project') 185 | .description('publish your project according to the [omni.config.js]\'s release field') 186 | .action((iterTactic) => { 187 | const workPath = iterTactic.path; 188 | if (workPath) changeCWD(workPath); 189 | 190 | getConfig(!!workPath); 191 | checkConfig(); 192 | updateNotifier(pkj); 193 | 194 | release(config, iterTactic); 195 | }); 196 | 197 | program.arguments('') 198 | .action(unknownCommand => { 199 | const availableCommands = program.commands.map(cmd => cmd._name); 200 | 201 | let suggestion: any; 202 | 203 | availableCommands.forEach(cmd => { 204 | const isBestMatch = 205 | leven(cmd, unknownCommand) < leven(suggestion || '', unknownCommand); 206 | if (leven(cmd, unknownCommand) < 3 && isBestMatch) { 207 | suggestion = cmd; 208 | } 209 | }); 210 | 211 | logErr(`Unknown command ${chalk.bold(`omni ${unknownCommand}`)}`); 212 | if (suggestion) { 213 | logWarn(`Try to ${chalk.underline(chalk.green(`omni ${suggestion}`))}`); 214 | } 215 | }); 216 | 217 | program.parse(process.argv); 218 | if (!program.args.length) { 219 | program.help(); 220 | } 221 | })(); 222 | -------------------------------------------------------------------------------- /src/commands/initial/__test__/index.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha'; 2 | import { expect } from 'chai'; 3 | import initial from '../index'; 4 | import { 5 | cli_basic_react, 6 | cli_standard_react, 7 | cli_entire_react, 8 | cli_pc_react, 9 | cli_basic_vue, 10 | cli_standard_vue, 11 | cli_entire_vue, 12 | cli_ssr_react, 13 | cli_components_react, 14 | cli_components_vue, 15 | cli_toolkit 16 | } from '../initial_preset'; 17 | 18 | 19 | describe('initial command test', function () { 20 | it('type checking', function () { 21 | expect(initial).to.be.a('function'); 22 | }); 23 | }); 24 | 25 | describe('initial preset test', function () { 26 | it('cli_basic_react checking', function () { 27 | expect(cli_basic_react).to.be.an('object'); 28 | 29 | expect(cli_basic_react.build).to.be.a('string'); 30 | expect(cli_basic_react.build).to.be.equal('webpack'); 31 | 32 | expect(cli_basic_react.pkgtool).to.be.a('string'); 33 | expect(cli_basic_react.pkgtool).to.be.equal('pnpm'); 34 | 35 | expect(cli_basic_react['project_type']).to.be.a('string'); 36 | expect(cli_basic_react['project_type']).to.be.equal('spa-react'); 37 | 38 | expect(cli_basic_react.ts).to.be.a('boolean'); 39 | expect(cli_basic_react.ts).to.be.false; 40 | 41 | expect(cli_basic_react.testFrame).to.be.a('string'); 42 | expect(cli_basic_react.testFrame).to.be.equal(''); 43 | 44 | expect(cli_basic_react.eslint).to.be.a('boolean'); 45 | expect(cli_basic_react.eslint).to.be.false; 46 | 47 | expect(cli_basic_react.commitlint).to.be.a('boolean'); 48 | expect(cli_basic_react.commitlint).to.be.false; 49 | 50 | expect(cli_basic_react.style).to.be.a('string'); 51 | expect(cli_basic_react.style).to.be.equal('css'); 52 | 53 | expect(cli_basic_react.stylelint).to.be.a('boolean'); 54 | expect(cli_basic_react.stylelint).to.be.false; 55 | }); 56 | 57 | it('cli_standard_react checking', function () { 58 | expect(cli_standard_react).to.be.an('object'); 59 | 60 | expect(cli_standard_react.build).to.be.a('string'); 61 | expect(cli_standard_react.build).to.be.equal('webpack'); 62 | 63 | expect(cli_standard_react.pkgtool).to.be.a('string'); 64 | expect(cli_standard_react.pkgtool).to.be.equal('pnpm'); 65 | 66 | expect(cli_standard_react['project_type']).to.be.a('string'); 67 | expect(cli_standard_react['project_type']).to.be.equal('spa-react'); 68 | 69 | expect(cli_standard_react.ts).to.be.a('boolean'); 70 | expect(cli_standard_react.ts).to.be.true; 71 | 72 | expect(cli_standard_react.testFrame).to.be.a('string'); 73 | expect(cli_standard_react.testFrame).to.be.equal(''); 74 | 75 | expect(cli_standard_react.eslint).to.be.a('boolean'); 76 | expect(cli_standard_react.eslint).to.be.true; 77 | 78 | expect(cli_standard_react.commitlint).to.be.a('boolean'); 79 | expect(cli_standard_react.commitlint).to.be.false; 80 | 81 | expect(cli_standard_react.style).to.be.a('string'); 82 | expect(cli_standard_react.style).to.be.equal('less'); 83 | 84 | expect(cli_standard_react.stylelint).to.be.a('boolean'); 85 | expect(cli_standard_react.stylelint).to.be.true; 86 | }); 87 | 88 | it('cli_entire_react checking', function () { 89 | expect(cli_entire_react).to.be.an('object'); 90 | 91 | expect(cli_entire_react.build).to.be.a('string'); 92 | expect(cli_entire_react.build).to.be.equal('webpack'); 93 | 94 | expect(cli_entire_react.pkgtool).to.be.a('string'); 95 | expect(cli_entire_react.pkgtool).to.be.equal('pnpm'); 96 | 97 | expect(cli_entire_react['project_type']).to.be.a('string'); 98 | expect(cli_entire_react['project_type']).to.be.equal('spa-react'); 99 | 100 | expect(cli_entire_react.ts).to.be.a('boolean'); 101 | expect(cli_entire_react.ts).to.be.true; 102 | 103 | expect(cli_entire_react.testFrame).to.be.a('string'); 104 | expect(cli_entire_react.testFrame).to.be.equal('jest'); 105 | 106 | expect(cli_entire_react.eslint).to.be.a('boolean'); 107 | expect(cli_entire_react.eslint).to.be.true; 108 | 109 | expect(cli_entire_react.commitlint).to.be.a('boolean'); 110 | expect(cli_entire_react.commitlint).to.be.true; 111 | 112 | expect(cli_entire_react.style).to.be.a('string'); 113 | expect(cli_entire_react.style).to.be.equal('all'); 114 | 115 | expect(cli_entire_react.stylelint).to.be.a('boolean'); 116 | expect(cli_entire_react.stylelint).to.be.true; 117 | }); 118 | 119 | it('cli_entire_react checking', function () { 120 | expect(cli_entire_react).to.be.an('object'); 121 | 122 | expect(cli_pc_react.build).to.be.a('string'); 123 | expect(cli_pc_react.build).to.be.equal('webpack'); 124 | 125 | expect(cli_pc_react.pkgtool).to.be.a('string'); 126 | expect(cli_pc_react.pkgtool).to.be.equal('pnpm'); 127 | 128 | expect(cli_pc_react['project_type']).to.be.a('string'); 129 | expect(cli_pc_react['project_type']).to.be.equal('spa-react-pc'); 130 | 131 | expect(cli_pc_react.ts).to.be.a('boolean'); 132 | expect(cli_pc_react.ts).to.be.true; 133 | 134 | expect(cli_pc_react.testFrame).to.be.a('string'); 135 | expect(cli_pc_react.testFrame).to.be.equal(''); 136 | 137 | expect(cli_pc_react.eslint).to.be.a('boolean'); 138 | expect(cli_pc_react.eslint).to.be.true; 139 | 140 | expect(cli_pc_react.commitlint).to.be.a('boolean'); 141 | expect(cli_pc_react.commitlint).to.be.true; 142 | 143 | expect(cli_pc_react.style).to.be.a('string'); 144 | expect(cli_pc_react.style).to.be.equal('less'); 145 | 146 | expect(cli_pc_react.stylelint).to.be.a('boolean'); 147 | expect(cli_pc_react.stylelint).to.be.true; 148 | }); 149 | 150 | it('cli_basic_vue checking', function () { 151 | expect(cli_basic_vue).to.be.an('object'); 152 | 153 | expect(cli_basic_vue.build).to.be.a('string'); 154 | expect(cli_basic_vue.build).to.be.equal('webpack'); 155 | 156 | expect(cli_basic_vue.pkgtool).to.be.a('string'); 157 | expect(cli_basic_vue.pkgtool).to.be.equal('pnpm'); 158 | 159 | expect(cli_basic_vue['project_type']).to.be.a('string'); 160 | expect(cli_basic_vue['project_type']).to.be.equal('spa-vue'); 161 | 162 | expect(cli_basic_vue.ts).to.be.a('boolean'); 163 | expect(cli_basic_vue.ts).to.be.false; 164 | 165 | expect(cli_basic_vue.testFrame).to.be.a('string'); 166 | expect(cli_basic_vue.testFrame).to.be.equal(''); 167 | 168 | expect(cli_basic_vue.eslint).to.be.a('boolean'); 169 | expect(cli_basic_vue.eslint).to.be.false; 170 | 171 | expect(cli_basic_vue.commitlint).to.be.a('boolean'); 172 | expect(cli_basic_vue.commitlint).to.be.false; 173 | 174 | expect(cli_basic_vue.style).to.be.a('string'); 175 | expect(cli_basic_vue.style).to.be.equal('css'); 176 | 177 | expect(cli_basic_vue.stylelint).to.be.a('boolean'); 178 | expect(cli_basic_vue.stylelint).to.be.false; 179 | }); 180 | 181 | it('cli_standard_vue checking', function () { 182 | expect(cli_standard_vue).to.be.an('object'); 183 | 184 | expect(cli_standard_vue.build).to.be.a('string'); 185 | expect(cli_standard_vue.build).to.be.equal('webpack'); 186 | 187 | expect(cli_standard_vue.pkgtool).to.be.a('string'); 188 | expect(cli_standard_vue.pkgtool).to.be.equal('pnpm'); 189 | 190 | expect(cli_standard_vue['project_type']).to.be.a('string'); 191 | expect(cli_standard_vue['project_type']).to.be.equal('spa-vue'); 192 | 193 | expect(cli_standard_vue.ts).to.be.a('boolean'); 194 | expect(cli_standard_vue.ts).to.be.true; 195 | 196 | expect(cli_standard_vue.testFrame).to.be.a('string'); 197 | expect(cli_standard_vue.testFrame).to.be.equal(''); 198 | 199 | expect(cli_standard_vue.eslint).to.be.a('boolean'); 200 | expect(cli_standard_vue.eslint).to.be.true; 201 | 202 | expect(cli_standard_vue.commitlint).to.be.a('boolean'); 203 | expect(cli_standard_vue.commitlint).to.be.false; 204 | 205 | expect(cli_standard_vue.style).to.be.a('string'); 206 | expect(cli_standard_vue.style).to.be.equal('less'); 207 | 208 | expect(cli_standard_vue.stylelint).to.be.a('boolean'); 209 | expect(cli_standard_vue.stylelint).to.be.true; 210 | }); 211 | 212 | it('cli_entire_vue checking', function () { 213 | expect(cli_entire_vue).to.be.an('object'); 214 | 215 | expect(cli_entire_vue.build).to.be.a('string'); 216 | expect(cli_entire_vue.build).to.be.equal('webpack'); 217 | 218 | expect(cli_entire_vue.pkgtool).to.be.a('string'); 219 | expect(cli_entire_vue.pkgtool).to.be.equal('pnpm'); 220 | 221 | expect(cli_entire_vue['project_type']).to.be.a('string'); 222 | expect(cli_entire_vue['project_type']).to.be.equal('spa-vue'); 223 | 224 | expect(cli_entire_vue.ts).to.be.a('boolean'); 225 | expect(cli_entire_vue.ts).to.be.true; 226 | 227 | expect(cli_entire_vue.testFrame).to.be.a('string'); 228 | expect(cli_entire_vue.testFrame).to.be.equal('jest'); 229 | 230 | expect(cli_entire_vue.eslint).to.be.a('boolean'); 231 | expect(cli_entire_vue.eslint).to.be.true; 232 | 233 | expect(cli_entire_vue.commitlint).to.be.a('boolean'); 234 | expect(cli_entire_vue.commitlint).to.be.true; 235 | 236 | expect(cli_entire_vue.style).to.be.a('string'); 237 | expect(cli_entire_vue.style).to.be.equal('all'); 238 | 239 | expect(cli_entire_vue.stylelint).to.be.a('boolean'); 240 | expect(cli_entire_vue.stylelint).to.be.true; 241 | }); 242 | 243 | it('cli_ssr_react checking', function () { 244 | expect(cli_ssr_react).to.be.an('object'); 245 | 246 | expect(cli_ssr_react.build).to.be.a('string'); 247 | expect(cli_ssr_react.build).to.be.equal('next'); 248 | 249 | expect(cli_ssr_react.pkgtool).to.be.a('string'); 250 | expect(cli_ssr_react.pkgtool).to.be.equal('pnpm'); 251 | 252 | expect(cli_ssr_react['project_type']).to.be.a('string'); 253 | expect(cli_ssr_react['project_type']).to.be.equal('ssr-react'); 254 | 255 | expect(cli_ssr_react.ts).to.be.a('boolean'); 256 | expect(cli_ssr_react.ts).to.be.true; 257 | 258 | expect(cli_ssr_react.testFrame).to.be.a('string'); 259 | expect(cli_ssr_react.testFrame).to.be.equal('jest'); 260 | 261 | expect(cli_ssr_react.eslint).to.be.a('boolean'); 262 | expect(cli_ssr_react.eslint).to.be.true; 263 | 264 | expect(cli_ssr_react.prettier).to.be.a('boolean'); 265 | expect(cli_ssr_react.prettier).to.be.true; 266 | 267 | expect(cli_ssr_react.commitlint).to.be.a('boolean'); 268 | expect(cli_ssr_react.commitlint).to.be.true; 269 | 270 | expect(cli_ssr_react.style).to.be.a('string'); 271 | expect(cli_ssr_react.style).to.be.equal('all'); 272 | 273 | expect(cli_ssr_react.stylelint).to.be.a('boolean'); 274 | expect(cli_ssr_react.stylelint).to.be.true; 275 | 276 | expect(cli_ssr_react.serverType).to.be.a('string'); 277 | expect(cli_ssr_react.serverType).to.be.equal('next-app'); 278 | }); 279 | 280 | it('cli_components_react checking', function () { 281 | expect(cli_components_react).to.be.an('object'); 282 | 283 | expect(cli_components_react.build).to.be.a('string'); 284 | expect(cli_components_react.build).to.be.equal('tsc'); 285 | 286 | expect(cli_components_react.pkgtool).to.be.a('string'); 287 | expect(cli_components_react.pkgtool).to.be.equal('yarn'); 288 | 289 | expect(cli_components_react['project_type']).to.be.a('string'); 290 | expect(cli_components_react['project_type']).to.be.equal('component-react'); 291 | 292 | expect(cli_components_react.ts).to.be.a('boolean'); 293 | expect(cli_components_react.ts).to.be.true; 294 | 295 | expect(cli_components_react.testFrame).to.be.a('string'); 296 | expect(cli_components_react.testFrame).to.be.equal('jest'); 297 | 298 | expect(cli_components_react.eslint).to.be.a('boolean'); 299 | expect(cli_components_react.eslint).to.be.true; 300 | 301 | expect(cli_components_react.commitlint).to.be.a('boolean'); 302 | expect(cli_components_react.commitlint).to.be.true; 303 | 304 | expect(cli_components_react.style).to.be.a('string'); 305 | expect(cli_components_react.style).to.be.equal('less'); 306 | 307 | expect(cli_components_react.stylelint).to.be.a('boolean'); 308 | expect(cli_components_react.stylelint).to.be.true; 309 | 310 | expect(cli_components_react.devServer).to.be.a('string'); 311 | expect(cli_components_react.devServer).to.be.equal('storybook'); 312 | }); 313 | 314 | it('cli_components_vue checking', function () { 315 | expect(cli_components_vue).to.be.an('object'); 316 | 317 | expect(cli_components_vue.build).to.be.a('string'); 318 | expect(cli_components_vue.build).to.be.equal('tsc'); 319 | 320 | expect(cli_components_vue.pkgtool).to.be.a('string'); 321 | expect(cli_components_vue.pkgtool).to.be.equal('yarn'); 322 | 323 | expect(cli_components_vue['project_type']).to.be.a('string'); 324 | expect(cli_components_vue['project_type']).to.be.equal('component-vue'); 325 | 326 | expect(cli_components_vue.ts).to.be.a('boolean'); 327 | expect(cli_components_vue.ts).to.be.true; 328 | 329 | expect(cli_components_vue.testFrame).to.be.a('string'); 330 | expect(cli_components_vue.testFrame).to.be.equal('jest'); 331 | 332 | expect(cli_components_vue.eslint).to.be.a('boolean'); 333 | expect(cli_components_vue.eslint).to.be.true; 334 | 335 | expect(cli_components_vue.commitlint).to.be.a('boolean'); 336 | expect(cli_components_vue.commitlint).to.be.true; 337 | 338 | expect(cli_components_vue.style).to.be.a('string'); 339 | expect(cli_components_vue.style).to.be.equal('less'); 340 | 341 | expect(cli_components_vue.stylelint).to.be.a('boolean'); 342 | expect(cli_components_vue.stylelint).to.be.true; 343 | 344 | expect(cli_components_vue.devServer).to.be.a('string'); 345 | expect(cli_components_vue.devServer).to.be.equal('storybook'); 346 | }); 347 | 348 | it('cli_toolkit checking', function () { 349 | expect(cli_toolkit).to.be.an('object'); 350 | 351 | expect(cli_toolkit.build).to.be.a('string'); 352 | expect(cli_toolkit.build).to.be.equal('rollup'); 353 | 354 | expect(cli_toolkit.pkgtool).to.be.a('string'); 355 | expect(cli_toolkit.pkgtool).to.be.equal('yarn'); 356 | 357 | expect(cli_toolkit['project_type']).to.be.a('string'); 358 | expect(cli_toolkit['project_type']).to.be.equal('toolkit'); 359 | 360 | expect(cli_toolkit.ts).to.be.a('boolean'); 361 | expect(cli_toolkit.ts).to.be.true; 362 | 363 | expect(cli_toolkit.testFrame).to.be.a('string'); 364 | expect(cli_toolkit.testFrame).to.be.equal('mocha'); 365 | 366 | expect(cli_toolkit.eslint).to.be.a('boolean'); 367 | expect(cli_toolkit.eslint).to.be.true; 368 | 369 | expect(cli_toolkit.commitlint).to.be.a('boolean'); 370 | expect(cli_toolkit.commitlint).to.be.true; 371 | 372 | expect(cli_toolkit.style).to.be.a('string'); 373 | expect(cli_toolkit.style).to.be.equal(''); 374 | 375 | expect(cli_toolkit.stylelint).to.be.a('boolean'); 376 | expect(cli_toolkit.stylelint).to.be.false; 377 | }); 378 | }); 379 | -------------------------------------------------------------------------------- /src/commands/initial/initial_preset.ts: -------------------------------------------------------------------------------- 1 | 2 | import type { 3 | BUILD, 4 | TEST_FRAME, 5 | PKJ_TOOL, 6 | STYLE, 7 | LAYOUT, 8 | SPA_SERVER, 9 | COMPONENT_SERVER, 10 | SSR_SERVER, 11 | PROJECT_TYPE 12 | } from '@omni-door/utils'; 13 | 14 | type DEV_SERVER = SPA_SERVER | COMPONENT_SERVER; 15 | 16 | export type GInstallCli = { 17 | project_type: PROJECT_TYPE; 18 | pkgtool: PKJ_TOOL; 19 | build: BUILD; 20 | ts: boolean; 21 | testFrame: TEST_FRAME; 22 | eslint: boolean; 23 | prettier: boolean; 24 | commitlint: boolean; 25 | style: STYLE; 26 | layout?: LAYOUT; 27 | stylelint: boolean; 28 | devServer?: DEV_SERVER; 29 | serverType?: SSR_SERVER; 30 | }; 31 | 32 | export const cli_basic_react: GInstallCli = { 33 | project_type: 'spa-react', 34 | pkgtool: 'pnpm', 35 | build: 'webpack', 36 | ts: false, 37 | testFrame: '', 38 | eslint: false, 39 | prettier: false, 40 | commitlint: false, 41 | style: 'css', 42 | layout: 'px', 43 | stylelint: false 44 | }; 45 | 46 | export const cli_standard_react: GInstallCli = { 47 | project_type: 'spa-react', 48 | pkgtool: 'pnpm', 49 | build: 'webpack', 50 | ts: true, 51 | testFrame: '', 52 | eslint: true, 53 | prettier: true, 54 | commitlint: false, 55 | style: 'less', 56 | layout: 'px', 57 | stylelint: true 58 | }; 59 | 60 | export const cli_entire_react: GInstallCli = { 61 | project_type: 'spa-react', 62 | pkgtool: 'pnpm', 63 | build: 'webpack', 64 | ts: true, 65 | testFrame: 'jest', 66 | eslint: true, 67 | prettier: true, 68 | commitlint: true, 69 | style: 'all', 70 | layout: 'px', 71 | stylelint: true 72 | }; 73 | 74 | export const cli_pc_react: GInstallCli = { 75 | project_type: 'spa-react-pc', 76 | pkgtool: 'pnpm', 77 | build: 'webpack', 78 | ts: true, 79 | testFrame: '', 80 | eslint: true, 81 | prettier: true, 82 | commitlint: true, 83 | style: 'less', 84 | stylelint: true 85 | }; 86 | 87 | export const cli_basic_vue: GInstallCli = { 88 | project_type: 'spa-vue', 89 | pkgtool: 'pnpm', 90 | build: 'webpack', 91 | ts: false, 92 | testFrame: '', 93 | eslint: false, 94 | prettier: false, 95 | commitlint: false, 96 | style: 'css', 97 | layout: 'px', 98 | stylelint: false 99 | }; 100 | 101 | export const cli_standard_vue: GInstallCli = { 102 | project_type: 'spa-vue', 103 | pkgtool: 'pnpm', 104 | build: 'webpack', 105 | ts: true, 106 | testFrame: '', 107 | eslint: true, 108 | prettier: true, 109 | commitlint: false, 110 | style: 'less', 111 | layout: 'px', 112 | stylelint: true 113 | }; 114 | 115 | export const cli_entire_vue: GInstallCli = { 116 | project_type: 'spa-vue', 117 | pkgtool: 'pnpm', 118 | build: 'webpack', 119 | ts: true, 120 | testFrame: 'jest', 121 | eslint: true, 122 | prettier: true, 123 | commitlint: true, 124 | style: 'all', 125 | layout: 'px', 126 | stylelint: true 127 | }; 128 | 129 | export const cli_ssr_react: GInstallCli = { 130 | project_type: 'ssr-react', 131 | pkgtool: 'pnpm', 132 | build: 'next', 133 | ts: true, 134 | testFrame: 'jest', 135 | eslint: true, 136 | prettier: true, 137 | commitlint: true, 138 | style: 'all', 139 | stylelint: true, 140 | serverType: 'next-app' 141 | }; 142 | 143 | export const cli_components_react: GInstallCli = { 144 | project_type: 'component-react', 145 | pkgtool: 'yarn', 146 | build: 'tsc', 147 | ts: true, 148 | testFrame: 'jest', 149 | eslint: true, 150 | prettier: true, 151 | commitlint: true, 152 | style: 'less', 153 | stylelint: true, 154 | devServer: 'storybook' 155 | }; 156 | 157 | export const cli_components_vue: GInstallCli = { 158 | project_type: 'component-vue', 159 | pkgtool: 'yarn', 160 | build: 'tsc', 161 | ts: true, 162 | testFrame: 'jest', 163 | eslint: true, 164 | prettier: true, 165 | commitlint: true, 166 | style: 'less', 167 | stylelint: true, 168 | devServer: 'storybook' 169 | }; 170 | 171 | export const cli_toolkit: GInstallCli = { 172 | project_type: 'toolkit', 173 | pkgtool: 'yarn', 174 | build: 'rollup', 175 | ts: true, 176 | testFrame: 'mocha', 177 | eslint: true, 178 | prettier: true, 179 | commitlint: true, 180 | style: '', 181 | stylelint: false 182 | }; 183 | 184 | export default { 185 | cli_basic_react, 186 | cli_standard_react, 187 | cli_entire_react, 188 | cli_pc_react, 189 | cli_basic_vue, 190 | cli_standard_vue, 191 | cli_entire_vue, 192 | cli_ssr_react, 193 | cli_components_react, 194 | cli_components_vue, 195 | cli_toolkit 196 | }; -------------------------------------------------------------------------------- /src/commands/new/__test__/index.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha'; 2 | import { expect } from 'chai'; 3 | import newTpl from '../index'; 4 | 5 | describe('new command test', function () { 6 | it('type checking', function () { 7 | expect(newTpl).to.be.a('function'); 8 | }); 9 | }); -------------------------------------------------------------------------------- /src/commands/new/index.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import inquirer from 'inquirer'; 4 | import { 5 | exec, 6 | arr2str, 7 | logErr, 8 | logInfo, 9 | logWarn, 10 | logSuc, 11 | getNpmVersions, 12 | nodeVersionCheck 13 | } from '@omni-door/utils'; 14 | import { getHandlers, signal, logo } from '../../utils'; 15 | /* import types */ 16 | import type { OmniConfig, OmniPlugin } from '../../index.d'; 17 | 18 | function handleException (msg?: string) { 19 | logWarn(msg || 'Oops! Some unknown errors have occurred(发生了一些未知错误)!'); 20 | process.exit(0); 21 | } 22 | 23 | export default async function (config: OmniConfig | null, componentName: string, options?: { 24 | function?: boolean; 25 | class?: boolean; 26 | render?: boolean; 27 | single?: boolean; 28 | tplPkj?: string; 29 | tplPkjTag?: string; 30 | before?: (params: { 31 | root: string; 32 | componentName: string; 33 | }) => (void | Promise); 34 | after?: (params: { 35 | root: string; 36 | componentName: string; 37 | }) => (void | Promise); 38 | }) { 39 | try { 40 | // node version pre-check 41 | await nodeVersionCheck('8'); 42 | } catch (e) { 43 | logWarn(e as string); 44 | } 45 | 46 | if (!config || JSON.stringify(config) === '{}') { 47 | handleException('Please initialize first(请先初始化项目)!'); 48 | } 49 | 50 | const { 51 | type, 52 | template, 53 | build, 54 | release, 55 | plugins 56 | } = config!; 57 | 58 | if (!type) { 59 | handleException('Cannot find the project type(项目类型缺失)!'); 60 | } 61 | 62 | const { 63 | root, 64 | test, 65 | typescript = false, 66 | stylesheet = '', 67 | readme = false 68 | } = template!; 69 | 70 | let module_cn = '组件'; 71 | let module_en = 'component'; 72 | if (type === 'toolkit') { 73 | module_cn = '模块'; 74 | module_en = 'module'; 75 | } 76 | 77 | // eslint-disable-next-line prefer-const 78 | let { function: fc, class: cc, render: h, single: sfc, tplPkj, tplPkjTag, before, after } = options || {}; 79 | 80 | if (!root) { 81 | handleException(`Missing the path for generate ${module_en}(生成${module_cn}的路径缺失)!`); 82 | } 83 | 84 | if (!componentName || (!fc && !cc && !h && !sfc)) { 85 | const moduleType = { 86 | fc: 'Function-Component(函数组件)', 87 | cc: 'Class-Component(类组件)', 88 | h: 'Render-Function(渲染函数组件)', 89 | sfc: 'Single-File-Component(模板组件)', 90 | }; 91 | const questions = [ 92 | { 93 | name: 'name', 94 | type: 'input', 95 | when: (answer: any) => { 96 | if (componentName) { 97 | return false; 98 | } 99 | return true; 100 | }, 101 | message: `${logo()} Please enter ${module_en} name(请输入${module_cn}名称):` 102 | }, 103 | { 104 | name: 'type', 105 | type: 'list', 106 | when: (answer: any) => { 107 | if (!answer.name && !componentName) { 108 | handleException(`Please input the ${module_en} name(请输入创建的${module_cn}名称)!`); 109 | } 110 | if (type === 'spa-vue' || type === 'toolkit' || fc || cc) { 111 | return false; 112 | } 113 | return true; 114 | }, 115 | choices: type === 'component-vue' ? [ moduleType.h, moduleType.sfc ] : [ moduleType.fc, moduleType.cc ], 116 | message: `${logo()} Select the type of ${module_en}(选择${module_cn}类型):` 117 | } 118 | ]; 119 | await new Promise((resolve) => { 120 | inquirer.prompt(questions) 121 | .then(answers => { 122 | const { name, type } = answers; 123 | componentName = name || componentName; 124 | switch (type) { 125 | case moduleType.fc: 126 | fc = true; 127 | break; 128 | case moduleType.cc: 129 | cc = true; 130 | break; 131 | case moduleType.sfc: 132 | sfc = true; 133 | break; 134 | case moduleType.h: 135 | h = true; 136 | break; 137 | } 138 | resolve(void 0); 139 | }); 140 | }).catch(err => { 141 | handleException(err); 142 | }); 143 | } 144 | 145 | if (!/^[a-zA-Z\_]\w+$/g.test(componentName)) { 146 | handleException( 147 | `Please input a valid module name(请输入合法的${module_cn}名称)!\n 148 | Rules(规则):\n 149 | 1. The ${module_cn} name must greater-or-equal 2(${module_cn}名大于等于2个字符)\n 150 | 2. The first character can only be underscore or upper/lower case letter(第一个字符只能由 下划线_ 或 大小写字母 组成)\n 151 | 3. The subsequent characters can only be numberm, underscore, upper and lower case letter(后续字符只能由 数字、下划线_、大小写字母 组成)\n 152 | ` 153 | ); 154 | } 155 | 156 | // bind exit signals 157 | signal(); 158 | 159 | const mdx = readme === 'mdx'; 160 | const path_cp = path.resolve(root, componentName); 161 | const path_cp_rel = path.relative(process.cwd(), path_cp); 162 | 163 | if (fs.existsSync(path_cp)) { 164 | handleException(`The ${componentName} ${module_en} had been existed(${module_cn} ${componentName} 已存在)!`); 165 | } 166 | 167 | const hasStorybook = fs.existsSync(path.resolve(process.cwd(), '.storybook')); 168 | const params = [ 169 | `componentName=${componentName}`, 170 | `newPath=${path_cp}`, 171 | `stylesheet=${stylesheet}`, 172 | `ts=${typescript}`, 173 | `type=${cc 174 | ? 'cc' 175 | : fc 176 | ? 'fc' 177 | : h 178 | ? 'h' 179 | : sfc 180 | ? 'sfc' 181 | : '' 182 | }`, 183 | `test=${!!test}`, 184 | `hasStorybook=${hasStorybook}`, 185 | readme ? `md=${mdx ? 'mdx' : 'md'}` : '' 186 | ]; 187 | 188 | let newTplPkj = tplPkj; 189 | if (!newTplPkj) { 190 | switch (type) { 191 | case 'spa-react': 192 | newTplPkj = '@omni-door/tpl-spa-react'; 193 | break; 194 | case 'spa-react-pc': 195 | newTplPkj = '@omni-door/tpl-spa-react-pc'; 196 | break; 197 | case 'spa-vue': 198 | newTplPkj = '@omni-door/tpl-spa-vue'; 199 | break; 200 | case 'ssr-react': 201 | newTplPkj = '@omni-door/tpl-ssr-react'; 202 | break; 203 | case 'component-react': 204 | newTplPkj = '@omni-door/tpl-component-react'; 205 | break; 206 | case 'component-vue': 207 | newTplPkj = '@omni-door/tpl-component-vue'; 208 | break; 209 | case 'toolkit': 210 | default: 211 | newTplPkj = '@omni-door/tpl-toolkit'; 212 | break; 213 | } 214 | } 215 | 216 | let templatePackageTag = tplPkjTag || 'latest'; 217 | if (tplPkjTag) { 218 | const matchVer = tplPkjTag.match(/\d+.\d+/)?.[0]; 219 | if (matchVer) { 220 | const versions = await getNpmVersions(newTplPkj); 221 | const [firstNum, secondNum] = matchVer.split('.'); 222 | const regexp = new RegExp(`^${firstNum}{1}.${secondNum}{1}.\\d+$`); 223 | const thirdNum = Math.max(...versions.filter(v => regexp.test(v)).map(v => +(v.split('.')?.[2] ?? 0))); 224 | templatePackageTag = `${firstNum}.${secondNum}.${thirdNum}`; 225 | } 226 | } 227 | 228 | typeof before === 'function' && await before({ 229 | componentName, 230 | root 231 | }); 232 | 233 | const newTpl = `${newTplPkj}@${templatePackageTag}`; 234 | logInfo(`Downloading the ${newTpl}, please wait patiently(正在下载 ${newTpl},请稍后)…`); 235 | exec( 236 | [ 237 | `npx ${newTpl} new ${arr2str(params)}` 238 | ], 239 | async function () { 240 | // handle new plugins 241 | const plugin_handles = plugins && plugins.length > 0 && getHandlers<'new'>(plugins as OmniPlugin<'new'>[], 'new'); 242 | if (plugin_handles) { 243 | for (const name in plugin_handles) { 244 | const handler = plugin_handles[name]; 245 | await handler({ 246 | type, 247 | template, 248 | build, 249 | release 250 | }, { 251 | componentName, 252 | componentType: cc ? 'class' : 'function', 253 | tplSource: newTplPkj! 254 | }); 255 | } 256 | } 257 | typeof after === 'function' && await after({ 258 | componentName, 259 | root 260 | }); 261 | // success logger 262 | logSuc(`The ${componentName} local at ${path_cp_rel}, construction completed(${componentName} 位于 ${path_cp_rel},创建完成)!`); 263 | process.exit(0); 264 | }, 265 | function (err: any) { 266 | logErr(err); 267 | logErr('👆 Oops! Some error occured(完蛋!好像有错误)\n'); 268 | process.exit(1); 269 | }); 270 | } -------------------------------------------------------------------------------- /src/commands/release/__test__/index.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha'; 2 | import { expect } from 'chai'; 3 | import release from '../index'; 4 | 5 | describe('release command test', function () { 6 | it('type checking', function () { 7 | expect(release).to.be.a('function'); 8 | }); 9 | }); -------------------------------------------------------------------------------- /src/commands/release/branch.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | branch=$1 4 | name=$2 5 | 6 | if [ "$name" == "" ] 7 | then 8 | name="🐸 [OMNI-DOOR]" 9 | fi 10 | 11 | checkBranch () { 12 | if [ -z "$branch" ]; then 13 | echo -e "\033[31m \n ${name} The branch cannot be empty \033[0m" 14 | echo -e "\033[31m \n ${name} 分支不能为空\n \033[0m" 15 | exit 1 16 | fi 17 | 18 | currentBranch=$(git branch | grep \* | cut -d " " -f2) 19 | 20 | if [ "$currentBranch" != "$branch" ] 21 | then 22 | if [ "$currentBranch" == "" ] 23 | then 24 | echo -e "\033[31m \n ${name} Please initialize git repository and finishing the first push operation by yourself \033[0m" 25 | echo -e "\033[31m \n ${name} 请先初始化 git 仓库并手动完成第一次推送\n \033[0m" 26 | else 27 | echo -e "\033[31m \n ${name} Please switch to \033[43;30m ${branch} \033[0m \033[31mbranch first \033[0m" 28 | echo -e "\033[31m \n ${name} 请切换到 \033[43;30m ${branch} \033[0m \033[31m分支进行发布\n \033[0m" 29 | fi 30 | exit 1 31 | fi 32 | 33 | echo -e "\033[36m \n ${name} The current branch is ${branch} \033[0m" 34 | echo -e "\033[36m \n ${name} 当前分支为 ${branch}\n \033[0m" 35 | } 36 | 37 | checkBranch -------------------------------------------------------------------------------- /src/commands/release/index.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import inquirer from 'inquirer'; 4 | import semver from 'semver'; 5 | import chalk from 'chalk'; 6 | import { 7 | exec, 8 | logErr, 9 | logInfo, 10 | logWarn, 11 | logSuc, 12 | logCongrat, 13 | logEmph, 14 | logTime, 15 | italic, 16 | underline, 17 | nodeVersionCheck, 18 | getNpmVersions, 19 | logPrefix 20 | } from '@omni-door/utils'; 21 | import { spawn, execSync } from 'child_process'; 22 | import { getHandlers, signal, logo } from '../../utils'; 23 | import buildCommands from '../build'; 24 | /* import types */ 25 | import type { OmniConfig, OmniPlugin } from '../../index.d'; 26 | 27 | const tagCustom = '$omni_custom$'; 28 | 29 | const tagDict = { 30 | '1. alpha (内测版)': 'alpha', 31 | '2. beta (公测版)': 'beta', 32 | '3. rc (候选版)': 'rc', 33 | '4. latest (正式版)': 'latest', 34 | '5. custom (自定义)': tagCustom 35 | }; 36 | 37 | const tagDictWithExtraWords = { 38 | '1. alpha (内测版 - 当前标签)': 'alpha', 39 | '2. beta (公测版 - 当前标签)': 'beta', 40 | '3. rc (候选版 - 当前标签)': 'rc', 41 | '4. latest (正式版 - 当前标签)': 'latest' 42 | }; 43 | 44 | const iterDict = { 45 | automatic: '1. automatic(自动)', 46 | manual: '2. manual(手动)', 47 | ignore: '3. ignore(忽略)' 48 | }; 49 | 50 | function getAutoIterDict (version: string, tag: string) { 51 | if (tag === 'latest') { 52 | return { 53 | [`1. patch (${version} -> ${semver.inc(version, 'patch')})`]: ['latest', semver.inc(version, 'patch')], 54 | [`2. minor (${version} -> ${semver.inc(version, 'minor')})`]: ['latest', semver.inc(version, 'minor')], 55 | [`3. major (${version} -> ${semver.inc(version, 'major')})`]: ['latest', semver.inc(version, 'major')] 56 | }; 57 | } 58 | 59 | if (tag === 'rc') { 60 | return { 61 | [`1. pre-release (${version} -> ${semver.inc(version, 'prerelease', tag)})`]: [tag || 'prerelease', semver.inc(version, 'prerelease', tag)], 62 | [`2. patch (${version} -> ${semver.inc(version, 'patch')})`]: ['latest', semver.inc(version, 'patch')], 63 | [`3. minor (${version} -> ${semver.inc(version, 'minor')})`]: ['latest', semver.inc(version, 'minor')], 64 | [`4. major (${version} -> ${semver.inc(version, 'major')})`]: ['latest', semver.inc(version, 'major')] 65 | }; 66 | } 67 | 68 | return { 69 | [`1. pre-release (${version} -> ${semver.inc(version, 'prerelease', tag)})`]: [tag || 'prerelease', semver.inc(version, 'prerelease', tag)], 70 | [`2. pre-patch (${version} -> ${semver.inc(version, 'prepatch', tag)})`]: [tag || 'prepatch', semver.inc(version, 'prepatch', tag)], 71 | [`3. pre-minor (${version} -> ${semver.inc(version, 'preminor', tag)})`]: [tag || 'preminor', semver.inc(version, 'preminor', tag)], 72 | [`4. pre-major (${version} -> ${semver.inc(version, 'premajor', tag)})`]: [tag || 'premajor', semver.inc(version, 'premajor', tag)], 73 | }; 74 | } 75 | 76 | export default async function ( 77 | config: OmniConfig | null, 78 | iterTactic?: { 79 | automatic?: boolean | string; 80 | ignore?: boolean; 81 | manual?: string; 82 | verify?: boolean; 83 | tag?: string; 84 | config?: string; 85 | buildConfig?: string; 86 | pkjFieldName?: string; 87 | configFileName?: string; 88 | }, 89 | autoRelease?: boolean 90 | ) { 91 | try { 92 | // node version pre-check 93 | await nodeVersionCheck('8'); 94 | } catch (e) { 95 | logWarn(e as string); 96 | } 97 | 98 | if (!config || JSON.stringify(config) === '{}') { 99 | logWarn('Please initialize project first'); 100 | logWarn('请先初始化项目'); 101 | process.exit(0); 102 | } 103 | 104 | // bind exit signals 105 | signal(); 106 | 107 | const { type, template, build, release = {}, plugins } = config; 108 | const { 109 | git, 110 | npm, 111 | autoBuild, 112 | autoTag, 113 | preflight 114 | } = release; 115 | const { 116 | test = false, 117 | eslint = false, 118 | prettier = false, 119 | stylelint = false, 120 | commitlint = false, 121 | branch 122 | } = preflight || {}; 123 | 124 | if (branch) { 125 | // branch check 126 | let branchInfo = ''; 127 | await exec( 128 | [`${path.resolve(__dirname, 'branch.sh')} ${branch} "${logPrefix()}"`], 129 | function (results) { branchInfo = results[0]; }, 130 | function () { process.exit(1); } 131 | ); 132 | if (!~branchInfo.indexOf('current branch is')) { 133 | // branch check failed! 134 | return; 135 | } 136 | } 137 | 138 | function handleReleaseSuc (msg?: string) { 139 | msg = msg || 'Release completed (发布完成)!'; 140 | 141 | return function (isExit?: boolean) { 142 | logCongrat(msg!); 143 | isExit && process.exit(0); 144 | }; 145 | } 146 | 147 | function handleReleaseErr (msg?: string) { 148 | msg = msg || 'Release failed (发布失败)!'; 149 | 150 | return function (err?: string) { 151 | err && logErr(err); 152 | msg && logErr(msg); 153 | process.exit(1); 154 | }; 155 | } 156 | 157 | function getPkjData (pkjPath: string) { 158 | let pkj = { 159 | name: 'OMNI-PROJECT', 160 | version: '0.0.1' 161 | }; 162 | if (fs.existsSync(pkjPath)) { 163 | delete require.cache[pkjPath]; // delete cache in order to avoid version may not correct 164 | pkj = require(pkjPath); 165 | } 166 | return pkj; 167 | } 168 | 169 | try { 170 | // eslint-disable-next-line prefer-const 171 | let { automatic, ignore, manual, tag, verify, ...rest } = iterTactic || {}; 172 | 173 | // package.json data 174 | const pkjPath = path.resolve(process.cwd(), 'package.json'); 175 | let pkj = getPkjData(pkjPath); 176 | 177 | // the version for iteration 178 | let iterVersion = manual || (typeof automatic === 'string' ? automatic : '') || pkj.version; 179 | // whether or not need iteration 180 | const needIteration = ignore === void 0 && manual === void 0 && automatic === void 0; 181 | 182 | const versionErrMsg = `Please input valid version (请输入有效的版本号)\n 183 | Reference to (版本号规则可参考): https://semver.org/`; 184 | const tagErrMsg = 'The tag can only contain letters (标签只能包含字母)'; 185 | const versionRepeatMsg = (ver: string) => `The ${ver} is not available (${ver} 不可用)`; 186 | 187 | const existedVersions = [] as string[]; 188 | let versionsPromise = Promise.resolve(); 189 | if (npm) { 190 | versionsPromise = getNpmVersions(pkj.name, { registry: typeof npm === 'string' ? npm : void 0 }) 191 | .then(res => { existedVersions.push(...res); }); 192 | } 193 | 194 | // infer the tag which according to the version 195 | const defaultTag = manual 196 | ? manual.match(/[a-zA-Z]+/g)?.[0] ?? 'latest' 197 | : pkj?.version?.match(/[a-zA-Z]+/g)?.[0] ?? 'latest'; 198 | 199 | // automatic interation dictionary 200 | const autoIterDict = {} as Record; 201 | 202 | if (needIteration || (npm && !tag)) { 203 | await new Promise((resolve, reject) => { 204 | inquirer.prompt([ 205 | { 206 | name: 'presetTag', 207 | type: 'list', 208 | when: () => !tag && !autoTag, 209 | choices: () => { 210 | const result = Object.keys(tagDict); 211 | const presetTags = Object.values(tagDict); 212 | if (!presetTags.some(v => v === defaultTag)) { 213 | const key = `0. ${defaultTag} (当前标签)`; 214 | result.unshift(key); 215 | tagDictWithExtraWords[key as keyof typeof tagDictWithExtraWords] = defaultTag; 216 | } else { 217 | const ind = presetTags.indexOf(defaultTag); 218 | const preset = result[ind]; 219 | result.splice(ind, 1, preset.replace(')', ' - 当前标签)')); 220 | } 221 | return result; 222 | }, 223 | default: () => { 224 | const result = Object.keys(tagDict); 225 | const presetTags = Object.values(tagDict); 226 | if (presetTags.some(v => v === defaultTag)) { 227 | return result[presetTags.indexOf(defaultTag)].replace(')', ' - 当前标签)'); 228 | } 229 | }, 230 | message: 'Choose the tag (选择标签):' 231 | }, 232 | { 233 | name: 'label', 234 | type: 'input', 235 | when: answer => tagDict[answer.presetTag as keyof typeof tagDict] === tagCustom, 236 | default: () => { 237 | if (defaultTag === 'rc') return 'latest'; 238 | return defaultTag; 239 | }, 240 | validate: val => { 241 | if (/^[a-zA-Z]+$/g.test(val)) { 242 | return true; 243 | } 244 | return tagErrMsg; 245 | }, 246 | message: `${logo()}Input the tag (输入标签):` 247 | }, 248 | { 249 | name: 'iter', 250 | type: 'list', 251 | when: () => needIteration, 252 | choices: [ iterDict.automatic, iterDict.manual, iterDict.ignore ], 253 | message: `${logo()}Select the way of iteration (选择迭代方式):` 254 | }, 255 | { 256 | name: 'version_semantic', 257 | type: 'list', 258 | when: answer => answer.iter === iterDict.automatic, 259 | choices: (answer) => { 260 | Object.assign(autoIterDict, getAutoIterDict( 261 | pkj.version, 262 | answer.label || 263 | tagDict[answer.presetTag as keyof typeof tagDict] || 264 | tagDictWithExtraWords[answer.presetTag as keyof typeof tagDictWithExtraWords] || 265 | defaultTag 266 | )); 267 | return [ ...Object.keys(autoIterDict) ]; 268 | }, 269 | message: `${logo()}Select the version (选择版本):` 270 | }, 271 | { 272 | name: 'version_manual', 273 | type: 'input', 274 | when: answer => answer.iter === iterDict.manual, 275 | validate: val => { 276 | if (!semver.valid(val)) { 277 | console.info('\n'); 278 | logWarn(versionErrMsg); 279 | return false; 280 | } 281 | return true; 282 | }, 283 | message: `${logo()}Input the version (输入版本号):` 284 | }, 285 | { 286 | name: 'changeVersion', 287 | type: 'confirm', 288 | message: () => { 289 | const currentVer = iterVersion; 290 | const type = tag === 'latest' ? 'patch' : 'prerelease'; 291 | while(~existedVersions.indexOf(iterVersion)) { 292 | iterVersion = semver.inc(iterVersion, type, tag)!; 293 | } 294 | 295 | return `The ${chalk.strikethrough.red(currentVer)} had been occupied, would you like change to ${chalk.bold.underline.green(iterVersion)}?`; 296 | }, 297 | when: async (answer) => { 298 | const { version_manual, version_semantic, presetTag, label } = answer; 299 | iterVersion = version_manual || autoIterDict[version_semantic]?.[1] || iterVersion; 300 | const versionTag = iterVersion?.match(/[a-zA-Z]+/g)?.[0]; 301 | tag = label 302 | || tagDict[presetTag as keyof typeof tagDict] 303 | || tagDictWithExtraWords[presetTag as keyof typeof tagDictWithExtraWords] 304 | || (versionTag === 'rc' ? 'latest' : versionTag) 305 | || autoIterDict[version_semantic]?.[0] 306 | || defaultTag; 307 | if (!npm) return false; 308 | await versionsPromise; 309 | return existedVersions.some(v => iterVersion === v); 310 | } 311 | } 312 | ]) 313 | .then(answers => { 314 | const { iter, version_semantic, version_manual, changeVersion } = answers; 315 | if (changeVersion === false) { 316 | const currentVer = version_manual ?? autoIterDict[version_semantic]?.[1] ?? ''; 317 | logWarn(versionRepeatMsg(currentVer)); 318 | process.exit(1); 319 | } 320 | 321 | switch (iter) { 322 | case iterDict.automatic: 323 | // eslint-disable-next-line no-case-declarations 324 | automatic = iterVersion ?? true; 325 | break; 326 | case iterDict.manual: 327 | manual = iterVersion; 328 | break; 329 | case iterDict.ignore: 330 | ignore = true; 331 | break; 332 | } 333 | 334 | resolve(void 0); 335 | }) 336 | .catch(handleReleaseErr()); 337 | }); 338 | } else if (npm) { 339 | await versionsPromise; 340 | if (~existedVersions.indexOf(iterVersion)) { 341 | logWarn(versionRepeatMsg(iterVersion)); 342 | process.exit(0); 343 | } 344 | } 345 | 346 | if (manual && !semver.valid(manual)) { 347 | logWarn(versionErrMsg); 348 | process.exit(0); 349 | } 350 | 351 | // auto build 352 | if (autoBuild && !autoRelease) { 353 | logEmph(italic('Start building the project automatically')); 354 | logEmph(italic('开始自动构建项目')); 355 | try { 356 | await buildCommands( 357 | config, 358 | { 359 | ...rest, 360 | verify 361 | }, 362 | true 363 | ); 364 | } catch (err) { 365 | handleReleaseErr('Auto building the project failed(自动构建项目失败)!')(); 366 | } 367 | } 368 | 369 | logTime('RELEASE(发布)'); 370 | logInfo('Starting release process(开始发布)!'); 371 | if (!autoBuild && verify && test) { 372 | await exec(['npm test'], () => logSuc('Unit Test!'), handleReleaseErr('The unit test not pass(单元测试失败)')); 373 | } 374 | 375 | if (!autoBuild && verify && eslint) { 376 | await exec(['npm run lint:es'], () => logSuc('Eslint!'), handleReleaseErr(`The eslint not pass(eslint校验失败) \n try to exec(尝试执行): ${underline('npm run lint:es_fix')}`)); 377 | } 378 | 379 | if (!autoBuild && verify && prettier) { 380 | await exec(['npm run lint:prettier'], () => logSuc('Prettier!'), handleReleaseErr(`The prettier not pass(prettier校验失败) \n try to exec(尝试执行): ${underline('npm run lint:prettier_fix')}`)); 381 | } 382 | 383 | if (!autoBuild && verify && stylelint) { 384 | await exec(['npm run lint:style'], () => logSuc('Stylelint!'), handleReleaseErr(`The stylelint not pass(stylelint校验失败) \n try to exec(尝试执行): ${underline('npm run lint:style_fix')}`)); 385 | } 386 | 387 | const versionShellSuffix = ignore 388 | ? 'i' 389 | : manual 390 | ? `m ${manual}` 391 | : typeof automatic === 'string' 392 | ? `a ${automatic}` 393 | : ''; 394 | await exec( 395 | [`${path.resolve(__dirname, 'version.sh')} "${logPrefix()}" ${versionShellSuffix}`], 396 | () => { 397 | // re-require to get correct version 398 | pkj = getPkjData(pkjPath); 399 | logEmph(`The current version is ${pkj.version}`); 400 | logEmph(`当前版本号为 ${pkj.version}`); 401 | }, 402 | handleReleaseErr('The version iteration failed(版本迭代失败)!') 403 | ); 404 | 405 | // handle release plugins 406 | const plugin_handles = plugins && plugins.length > 0 && getHandlers<'release'>(plugins as OmniPlugin<'release'>[], 'release'); 407 | if (plugin_handles) { 408 | const version = pkj ? pkj.version : 'unknown'; 409 | const versionIterTactic = ignore ? 'ignore' : manual ? 'manual' : 'auto'; 410 | for (const name in plugin_handles) { 411 | const handler = plugin_handles[name]; 412 | await handler({ 413 | type, 414 | template, 415 | build, 416 | release 417 | }, { 418 | version, 419 | versionIterTactic, 420 | verify, 421 | tag 422 | }); 423 | } 424 | } 425 | 426 | const hasChange = !!execSync('git status -s').toString(); 427 | if (git && hasChange) { 428 | const gitUrl = git.trim(); 429 | let gitOriginUrl = ''; 430 | let gitOmniUrl = ''; 431 | await exec([ 432 | 'git remote get-url origin' 433 | ], function (results) { 434 | gitOriginUrl = results[0] && results[0].trim(); 435 | }, () => {}, true); 436 | await exec([ 437 | 'git remote get-url omni' 438 | ], function (results) { 439 | gitOmniUrl = results[0] && results[0].trim(); 440 | }, () => {}, true); 441 | 442 | let canPush = true; 443 | let remote = gitUrl === gitOmniUrl ? 'omni' : 'origin'; 444 | if (gitUrl !== gitOriginUrl && gitUrl !== gitOmniUrl) { 445 | !gitOmniUrl && logInfo(`Adding remote omni ${git}(新增远程地址omni ${git})`); 446 | const execArr = ['git remote remove omni', `git remote add omni ${git}`]; 447 | !gitOmniUrl && execArr.shift(); // remote没有omni,移除remove操作 448 | 449 | await exec( 450 | execArr, 451 | () => { 452 | logEmph(`git remote omni: ${git}`); 453 | remote = 'omni'; 454 | }, 455 | () => { 456 | logWarn('setting git remote failed'); 457 | logWarn('git remote 设置失败'); 458 | canPush = false; 459 | } 460 | ); 461 | } 462 | 463 | const commit = commitlint && !verify 464 | ? `git commit -m'[${pkj.name.toUpperCase()}]: ${pkj.version}' --no-verify` 465 | : `git commit -m'[${pkj.name.toUpperCase()}]: ${pkj.version}'`; 466 | 467 | const push = commitlint && !verify 468 | ? `git push ${remote} ${branch || 'master'} --no-verify` 469 | : `git push ${remote} ${branch || 'master'}`; 470 | 471 | canPush && await exec( 472 | [ 473 | 'git add -A', 474 | `${commit}`, 475 | `${push}` 476 | ], 477 | () => { 478 | logSuc('Pushing to git-repo successfully!'); 479 | logSuc('git仓库推送成功!'); 480 | }, 481 | handleReleaseErr('Pushing to git-repo failed(git仓库推送失败)!') 482 | ); 483 | } 484 | 485 | if (npm) { 486 | let npmUrl = ''; 487 | await exec( 488 | ['npm get registry'], 489 | function (results) { 490 | npmUrl = results[0] && results[0].trim(); 491 | }, () => {}, true 492 | ); 493 | 494 | await new Promise((resolve, reject) => { 495 | const npm_publish = spawn( 496 | 'npm', 497 | [ 498 | 'publish', 499 | `--registry=${(npm && typeof npm === 'string') ? npm : npmUrl}`, 500 | `--tag=${tag}`, 501 | '--access public' 502 | ], 503 | { 504 | detached: true, 505 | stdio: 'inherit' 506 | } 507 | ); 508 | 509 | if (npm_publish.stdout) { 510 | npm_publish.stdout.on('data', data => { 511 | console.info(data.toString()); 512 | }); 513 | } 514 | 515 | if (npm_publish.stderr) { 516 | npm_publish.stderr.on('data', data => { 517 | console.info(data.toString()); 518 | }); 519 | } 520 | 521 | npm_publish.on('error', handleReleaseErr('The npm-package publish failed(npm包发布失败)!')); 522 | 523 | npm_publish.on('close', code => { 524 | if (code === 0) { 525 | logSuc(`The npm-package publish success with version ${pkj.version}@${tag}!`); 526 | logSuc(`npm包发布成功, 版本号为 ${pkj.version}@${tag}!`); 527 | resolve(null); 528 | } else { 529 | reject(); 530 | } 531 | }); 532 | }); 533 | } 534 | 535 | logTime('RELEASE(发布)', true); 536 | const shouldExit = !autoRelease; 537 | handleReleaseSuc()(shouldExit); 538 | } catch (err) { 539 | logErr(err as string); 540 | handleReleaseErr('👆 Oops! release process occured some accidents(糟糕!发布过程发生了一点意外)')(); 541 | } 542 | } -------------------------------------------------------------------------------- /src/commands/release/publish.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | name=$1 3 | 4 | if [ "$name" == "" ] 5 | then 6 | name="🐸 [OMNI-DOOR]" 7 | fi 8 | 9 | npm publish --registry='https://registry.npmjs.org/' 10 | 11 | echo -e "\033[35m${name} The npm-package publish success!\033[0m" 12 | echo -e "\033[35m${name} npm包发布成功!\033[0m" -------------------------------------------------------------------------------- /src/commands/release/version.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | name=$1 4 | if [ "$name" == "" ] 5 | then 6 | name="🐸 [OMNI-DOOR]" 7 | fi 8 | iterate=$2 9 | inputVersion=$3 10 | dot="." 11 | OS=`uname` 12 | 13 | replaceVersion () { 14 | if [ "$OS" = "Darwin" ]; then 15 | sed -i "" "s/$1/$2/g" "package.json" 16 | else 17 | sed -i"" "s/$1/$2/g" "package.json" 18 | fi 19 | } 20 | 21 | updateVersion () { 22 | versionLine=$(grep \"version\" package.json) 23 | version=$(echo ${versionLine} | tr -cd "[0-9-a-zA-Z]." | sed -ne "s/[^0-9]*\(\([0-9a-zA-Z]\.\)\)/\1/p") 24 | prevSubVersion=$(echo ${version#*.}) 25 | subVersion=$(echo ${prevSubVersion%.*}) 26 | subSubVersion=$(echo ${version##*.}) 27 | if [ "$iterate" = "i" -o "$iterate" = "ignore" ] 28 | then 29 | echo -e "\n\033[33m${name} Ignoring the version of iteration \033[0m\n" 30 | echo -e "\033[33m${name} 忽略版本号迭代\033[0m\n" 31 | elif [ "$iterate" = "m" -o "$iterate" = "manual" ] 32 | then 33 | newVersion=$(echo ${version/${version}/${inputVersion}}) 34 | newVersionLine=$(echo "${versionLine/${version}/${newVersion}}") 35 | echo -e "\n\033[35m${name} Manual specify the version of iteration to ${manualVersion} \033[0m\n" 36 | echo -e "\033[35m${name} 版本号手动迭代至 ${inputVersion}\033[0m\n" 37 | replaceVersion "$versionLine" "$newVersionLine" 38 | elif [ "$iterate" = "a" -o "$iterate" = "auto" ] 39 | then 40 | newVersion=$(echo ${version/${version}/${inputVersion}}) 41 | newVersionLine=$(echo "${versionLine/${version}/${newVersion}}") 42 | echo -e "\n\033[36m${name} Auto-increase the version of iteration to ${newVersion} \033[0m\n" 43 | echo -e "\033[36m${name} 版本号自动迭代至 ${newVersion}\033[0m\n" 44 | replaceVersion "$versionLine" "$newVersionLine" 45 | elif [ -z "$iterate" ] 46 | then 47 | newSubSubVersion=`expr $subSubVersion + 1` 48 | newVersion=$(echo ${version/${dot}${subVersion}${dot}${subSubVersion}/${dot}${subVersion}${dot}${newSubSubVersion}}) 49 | newVersionLine=$(echo "${versionLine/${version}/${newVersion}}") 50 | echo -e "\n\033[36m${name} Auto-increase the version of iteration to ${newVersion} \033[0m\n" 51 | echo -e "\033[36m${name} 版本号自动迭代至 ${newVersion}\033[0m\n" 52 | replaceVersion "$versionLine" "$newVersionLine" 53 | else 54 | echo -e "\n\033[31m${name} Version iteration failed \033[0m\n" 55 | echo -e "\033[31m${name} 版本迭代失败\033[0m\n" 56 | exit 1 57 | fi 58 | } 59 | 60 | updateVersion -------------------------------------------------------------------------------- /src/commands/servers/__test__/index.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha'; 2 | import { expect } from 'chai'; 3 | import { 4 | EWServer, 5 | KNServer 6 | } from '../index'; 7 | 8 | describe('express-webpack server test', function () { 9 | it('type checking', function () { 10 | expect(EWServer).to.be.a('function'); 11 | }); 12 | }); 13 | 14 | describe('koa-next server test', function () { 15 | it('type checking', function () { 16 | expect(KNServer).to.be.a('function'); 17 | }); 18 | }); -------------------------------------------------------------------------------- /src/commands/servers/express-webpack.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import http from 'http'; 4 | import https from 'https'; 5 | import { logInfo, requireCwd } from '@omni-door/utils'; 6 | import open from '../dev/open'; 7 | /* import types */ 8 | import type { Express } from 'express'; 9 | import type { Configuration, Compiler } from 'webpack'; 10 | import type { WebpackDevMiddleware, Options } from 'webpack-dev-middleware'; 11 | import type { NextHandleFunction } from 'connect'; 12 | import type { ProxyConfig, MiddlewareConfig } from '../dev/server'; 13 | import type { EWMiddleWareCallback } from '../../index.d'; 14 | 15 | export interface EWServerParams { 16 | webpackConfig: Configuration | (() => Configuration); 17 | devMiddlewareOptions?: Partial; 18 | proxyConfig?: ProxyConfig; 19 | middlewareConfig?: MiddlewareConfig; 20 | ipAddress: string; 21 | host: string; 22 | listenHost?: string; 23 | port: number; 24 | httpsConfig?: { 25 | key?: string | Buffer; 26 | cert?: string | Buffer; 27 | }; 28 | favicon?: string; 29 | } 30 | 31 | export default function ({ 32 | webpackConfig, 33 | devMiddlewareOptions, 34 | proxyConfig = [], 35 | middlewareConfig = [], 36 | ipAddress, 37 | host, 38 | listenHost, 39 | port, 40 | httpsConfig, 41 | favicon: faviconPath 42 | }: EWServerParams) { 43 | const express = requireCwd('express'); 44 | const favicon = requireCwd('serve-favicon'); 45 | const { createProxyMiddleware } = requireCwd('http-proxy-middleware'); 46 | const webpack = requireCwd('webpack'); 47 | const compiler: Compiler = webpack(typeof webpackConfig === 'function' ? webpackConfig() : webpackConfig); 48 | const devMiddleware: WebpackDevMiddleware & NextHandleFunction = requireCwd('webpack-dev-middleware')(compiler, { 49 | publicPath: '/', 50 | ...devMiddlewareOptions 51 | }); 52 | const hotMiddleware= requireCwd('webpack-hot-middleware'); 53 | 54 | const app: Express = express(); 55 | 56 | // dev server middleware 57 | app.use(devMiddleware); 58 | 59 | // hot refresh middleware 60 | app.use(hotMiddleware(compiler, { 61 | log: logInfo, 62 | path: '/__webpack_hmr', 63 | heartbeat: 10 * 1000 64 | })); 65 | 66 | // http proxy middleware 67 | for (let i = 0; i < proxyConfig.length; i++) { 68 | const item = proxyConfig[i]; 69 | const { route, config } = typeof item === 'function' ? item({ 70 | ip: ipAddress, 71 | port, 72 | host, 73 | middlewareConfig 74 | }) : item; 75 | 76 | app.use( 77 | route, 78 | createProxyMiddleware(config) 79 | ); 80 | } 81 | 82 | // custom middleware 83 | for (let i = 0; i < middlewareConfig.length; i++) { 84 | const item = middlewareConfig[i]; 85 | const { route, callback, method } = typeof item === 'function' ? item({ 86 | ip: ipAddress, 87 | port, 88 | host, 89 | proxyConfig 90 | }) : item; 91 | 92 | let _method = (method?.toLowerCase() ?? 'use') as 'get' | 'post' | 'delete' | 'del' | 'put' | 'use'; 93 | if (_method === 'del') _method = 'delete'; 94 | app[_method]( 95 | route, 96 | callback as EWMiddleWareCallback 97 | ); 98 | } 99 | 100 | // favicon.ico 101 | const icoPath = faviconPath && fs.existsSync(faviconPath) ? faviconPath : path.resolve(__dirname, 'favicon.ico'); 102 | fs.existsSync(icoPath) && app.use(favicon(icoPath)); 103 | 104 | // index.html for SPA browser router 105 | app.use('*', devMiddleware); 106 | 107 | let server; 108 | let serverUrl = `${host}:${port}`; 109 | if (httpsConfig) { 110 | server = https.createServer({ 111 | key: httpsConfig.key, 112 | cert: httpsConfig.cert 113 | }, app); 114 | serverUrl = 'https://' + serverUrl; 115 | } else { 116 | server = http.createServer(app); 117 | serverUrl = 'http://' + serverUrl; 118 | } 119 | 120 | server.listen(port, listenHost || host, async () => { 121 | await open(serverUrl); 122 | logInfo('> Ready on: ' + serverUrl); 123 | }); 124 | } -------------------------------------------------------------------------------- /src/commands/servers/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omni-door/cli/7349af22a058d59d20b4194609dc2e9d4ae35010/src/commands/servers/favicon.ico -------------------------------------------------------------------------------- /src/commands/servers/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | default as EWServer, 3 | EWServerParams 4 | } from './express-webpack'; 5 | export { 6 | default as KNServer, 7 | KNServerParams 8 | } from './koa-next'; -------------------------------------------------------------------------------- /src/commands/servers/koa-next.ts: -------------------------------------------------------------------------------- 1 | // !deprecated 2 | import path from 'path'; 3 | import http from 'http'; 4 | import https from 'https'; 5 | import { logInfo, logWarn, logErr, requireCwd, _typeof } from '@omni-door/utils'; 6 | import open from '../dev/open'; 7 | /* import types */ 8 | import type NextServer from 'next-server/dist/server/next-server'; 9 | import type * as KoaRouter from 'koa-router'; 10 | import type { ProxyConfig, MiddlewareConfig, CorsConfig } from '../dev/server'; 11 | import type { NextRouter, KNMiddleWareCallback, KoaApp, ANY_OBJECT } from '../../index.d'; 12 | 13 | export interface KNServerParams { 14 | dev: boolean; 15 | proxyConfig?: ProxyConfig; 16 | middlewareConfig?: MiddlewareConfig; 17 | corsConfig?: CorsConfig; 18 | ipAddress: string; 19 | host: string; 20 | listenHost?: string; 21 | port: number; 22 | httpsConfig?: { 23 | key?: string | Buffer; 24 | cert?: string | Buffer; 25 | }; 26 | nextRouter?: NextRouter; 27 | } 28 | 29 | export default function ({ 30 | dev, 31 | ipAddress, 32 | proxyConfig = [], 33 | middlewareConfig = [], 34 | corsConfig, 35 | host, 36 | listenHost, 37 | port, 38 | httpsConfig, 39 | nextRouter 40 | }: KNServerParams) { 41 | const Koa = requireCwd('koa'); 42 | const next = requireCwd('next'); 43 | const Router = requireCwd('koa-router'); 44 | const bodyParser = requireCwd('koa-bodyparser'); 45 | const k2c = requireCwd('koa2-connect'); 46 | const statics = requireCwd('koa-static'); 47 | const cors = requireCwd('@koa/cors'); 48 | const proxy = requireCwd('http-proxy-middleware'); 49 | const { pathToRegexp } = requireCwd('path-to-regexp'); 50 | const publicPath = path.resolve(process.cwd(), 'public'); 51 | 52 | const nextApp: NextServer = next({ dev }); 53 | nextApp 54 | .prepare() 55 | .then(() => { 56 | const app: KoaApp = new Koa(); 57 | const router: KoaRouter = new Router(); 58 | 59 | // cors 60 | app.use(cors(corsConfig)); 61 | 62 | // middleware: http-proxy 63 | app.use(async (ctx, next) => { 64 | const { path } = ctx; 65 | let needProxy = false; 66 | 67 | for (let i = 0; i < proxyConfig.length; i++) { 68 | const item = proxyConfig[i]; 69 | const { route, config } = typeof item === 'function' ? item({ 70 | ip: ipAddress, 71 | port, 72 | host, 73 | middlewareConfig 74 | }) : item; 75 | 76 | try { 77 | if ( 78 | pathToRegexp(route).test(path) || 79 | new RegExp(`^${route}`).test(path) 80 | ) { 81 | needProxy = true; 82 | await k2c(proxy(path, config))(ctx, next); 83 | break; 84 | } 85 | } catch (err) { 86 | logWarn(err as any); 87 | logWarn(`The http-proxy「${route})」match occur error`); 88 | logWarn(`http-proxy「${route}」匹配异常`); 89 | } 90 | } 91 | 92 | if (!needProxy) { 93 | await next(); 94 | } else { 95 | return; 96 | } 97 | }); 98 | 99 | // middleware: custom 100 | const middlewares = [...middlewareConfig]; 101 | for (let i = 0; i < middlewares.length; i++) { 102 | const item = middlewares[i]; 103 | const { route, callback, method } = typeof item === 'function' ? item({ 104 | ip: ipAddress, 105 | port, 106 | host, 107 | proxyConfig 108 | }) : item; 109 | const _method = (method?.toLowerCase() ?? 'get') as 'get' | 'post' | 'put' | 'del'; 110 | const anyStr = '@#$%^#*(&^!~)::;;".._--'; 111 | const wildcardRoute = !route || pathToRegexp(route).test(anyStr) || new RegExp(`^${route}`).test(anyStr); 112 | if (wildcardRoute) { 113 | router.use(callback); 114 | } else { 115 | router[_method](route, callback); 116 | } 117 | } 118 | 119 | // inject routes 120 | // based on next-url-prettifier 121 | // https://github.com/BDav24/next-url-prettifier 122 | nextRouter && nextRouter?.forEachPattern(({ page, pattern, defaultParams, beforeRender }) => router.get(pattern, async (ctx, next) => { 123 | let shouldRender: boolean | ANY_OBJECT = true; 124 | 125 | try { 126 | const { req, res, query, params } = ctx; 127 | 128 | if (typeof beforeRender === 'function') { 129 | try { 130 | shouldRender = await beforeRender(ctx, next); 131 | } catch (err) { 132 | logWarn(err as any); 133 | logWarn(`The ${page}'s beforeRender error!`); 134 | logWarn(`${page} 页面 beforeRender 执行异常`); 135 | } 136 | } 137 | 138 | shouldRender && nextApp.render(req, res, `/${page}`, Object.assign(Object.create(null), defaultParams, query, params, _typeof(shouldRender) === 'object' ? shouldRender : null)); 139 | } catch (err) { 140 | shouldRender = false; 141 | logWarn(JSON.stringify(err)); 142 | logWarn(`The ${page} router error`); 143 | logWarn(`${page} 路由出错`); 144 | } 145 | 146 | if (shouldRender) { 147 | ctx.status = 200; 148 | ctx.respond = false; 149 | } 150 | })); 151 | 152 | // other source redirect to '/' 153 | router.get('(.*)', async ctx => { 154 | await nextApp.render(ctx.req, ctx.res, '/', ctx.query); 155 | ctx.status = 200; 156 | ctx.respond = false; 157 | }); 158 | 159 | // middleware: static-server, body-parser and router 160 | app.use(statics(publicPath)); 161 | app.use(bodyParser()); 162 | app.use(router.routes()).use(router.allowedMethods()); 163 | 164 | let server; 165 | let serverUrl = `${host}:${port}`; 166 | if (httpsConfig) { 167 | server = https.createServer({ 168 | key: httpsConfig.key, 169 | cert: httpsConfig.cert 170 | }, app.callback()); 171 | serverUrl = 'https://' + serverUrl; 172 | } else { 173 | server = http.createServer(app.callback()); 174 | serverUrl = 'http://' + serverUrl; 175 | } 176 | 177 | server.listen(port, listenHost || host, async () => { 178 | dev && await open(serverUrl); 179 | logInfo(`The server running with ${dev ? 'DEV' : 'PROD'}-MODE!`); 180 | logInfo('> Ready on: ' + serverUrl); 181 | }); 182 | }) 183 | .catch(err => { 184 | const error = new Error(err); 185 | logErr(`${error}\nThe Error stack: ${error.stack}`); 186 | }); 187 | } -------------------------------------------------------------------------------- /src/commands/start/__test__/index.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha'; 2 | import { expect } from 'chai'; 3 | import start from '../index'; 4 | 5 | describe('start command test', function () { 6 | it('type checking', function () { 7 | expect(start).to.be.a('function'); 8 | }); 9 | }); -------------------------------------------------------------------------------- /src/commands/start/index.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { logWarn, nodeVersionCheck, requireCwd, exec, _typeof } from '@omni-door/utils'; 3 | import { KNServer } from '../servers'; 4 | import { signal } from '../../utils'; 5 | /* import types */ 6 | import type { OmniConfig } from '../../index.d'; 7 | 8 | function handleException (msg?: string) { 9 | logWarn(msg || '发生了一些未知错误!(Ops! Some unknown errors have occurred!)'); 10 | process.exit(0); 11 | } 12 | 13 | export default async function (config: OmniConfig | null, options: { 14 | port?: number | string; 15 | hostname?: string; 16 | }) { 17 | try { 18 | // node version pre-check 19 | await nodeVersionCheck('8'); 20 | } catch (e) { 21 | logWarn(e as string); 22 | } 23 | 24 | if (!config || JSON.stringify(config) === '{}') { 25 | handleException('Please initialize project first(请先初始化项目)!'); 26 | } 27 | const { server } = config!; 28 | 29 | if (!server || JSON.stringify(server) === '{}') { 30 | handleException('The start field is missing in config file(配置文件 start 字段缺失)!'); 31 | } 32 | const { 33 | port, 34 | host, 35 | serverType, 36 | middleware, 37 | https, 38 | ...rest 39 | } = server || {}; 40 | if (!serverType) { 41 | handleException('Please specify server-type(请指定 server 类型)!'); 42 | } 43 | 44 | // bind exit signals 45 | signal(); 46 | 47 | const p = options.port; 48 | const h = options.hostname; 49 | const ip = requireCwd('ip'); 50 | const ipAddress: string = ip.address(); 51 | const CWD = process.cwd(); 52 | const _port = (p ? +p : port) || 6200; 53 | const _host = h || host || '0.0.0.0'; 54 | 55 | if (_typeof(https) === 'boolean') { 56 | logWarn(`The https must specify path when start server at production environment (开发环境中 https 必须指定路径): \n 57 | 58 | https: { 59 | key: fs.readFileSync(path.resolve(\${your_path_to_key})), 60 | cert: fs.readFileSync(path.resolve(\${your_path_to_cert})) 61 | }`); 62 | } 63 | 64 | switch (serverType) { 65 | case 'next-app': 66 | case 'next-pages': 67 | exec([`${path.resolve(CWD, 'node_modules/.bin/next')} start --port ${_port} --hostname ${_host}`]); 68 | break; 69 | case 'nuxt': 70 | default: 71 | logWarn('Not support ssr-vue yet'); 72 | logWarn('暂不支持 ssr-vue 项目'); 73 | } 74 | } -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | import type { Configuration } from 'webpack'; 2 | export type { Configuration } from 'webpack'; 3 | import type { Config } from 'http-proxy-middleware'; 4 | export type { Config } from 'http-proxy-middleware'; 5 | import type { Options as DevMiddlewareOptions } from 'webpack-dev-middleware'; 6 | export type { Options as DevMiddlewareOptions } from 'webpack-dev-middleware'; 7 | import type { Request, Response, NextFunction } from 'express'; 8 | export type { Request, Response, NextFunction } from 'express'; 9 | import type * as KoaApp from 'koa'; 10 | export type { default as KoaApp } from 'koa'; 11 | import type { BUILD, PROJECT_TYPE, STYLE, PLUGIN_STAGE, HASH, SPA_SERVER, COMPONENT_SERVER, SSR_SERVER, MARKDOWN } from '@omni-door/utils'; 12 | 13 | export type ANY_OBJECT = { [propName: string]: any }; 14 | 15 | export type Method = 'get' | 'GET' | 'post' | 'POST' | 'put' | 'PUT' | 'del' | 'DEL'; 16 | 17 | export type PathParams = string | RegExp | (string | RegExp)[]; 18 | 19 | export type KoaCtx = KoaApp.ParameterizedContext; 20 | 21 | export type OptionTemplate = { 22 | componentName: string; 23 | componentType: 'function' | 'class'; 24 | tplSource: string; 25 | }; 26 | 27 | export type OptionBuild = { 28 | verify?: boolean; 29 | buildConfig?: string; 30 | }; 31 | 32 | export type OptionRelease = { 33 | version: string; 34 | versionIterTactic: 'ignore' | 'manual' | 'auto'; 35 | verify?: boolean; 36 | tag?: string; 37 | }; 38 | 39 | export interface PluginHandler { 40 | ( 41 | config: Omit, 42 | options?: T extends 'new' ? OptionTemplate : T extends 'build' ? OptionBuild : OptionRelease 43 | ): Promise; 44 | } 45 | export type HandlerFactory = (handler: PluginHandler, errMsg?: string) => PluginHandler; 46 | 47 | export interface OmniPlugin { 48 | name: string; 49 | stage: T; 50 | handler: PluginHandler; 51 | } 52 | 53 | export type EWMiddleWareCallback = (req: Request, res: Response, next: NextFunction) => void; 54 | export type KNMiddleWareCallback = KoaApp.Middleware; 55 | export type MiddleWareCallback = EWMiddleWareCallback | KNMiddleWareCallback; 56 | 57 | export type ServerType = SPA_SERVER | COMPONENT_SERVER | SSR_SERVER | 'default'; 58 | 59 | export interface NextRouter { 60 | forEachPattern: (apply: (params: { 61 | page: string; 62 | pattern: string; 63 | defaultParams?: ANY_OBJECT; 64 | beforeRender?: (ctx: KoaApp.ParameterizedContext, next: KoaApp.Next) => boolean | ANY_OBJECT; 65 | }) => any) => void; 66 | } 67 | 68 | export type OmniServer = { 69 | port?: number; 70 | host?: string; 71 | https?: boolean | { key: string; cert: string; }; 72 | CA?: { 73 | organization?: string; 74 | countryCode?: string; 75 | state?: string; 76 | locality?: string; 77 | validityDays?: number; 78 | }; 79 | proxy?: { 80 | route: PathParams; 81 | config: Config; 82 | }[]; 83 | middleware?: { 84 | route: PathParams; 85 | callback: MiddleWareCallback; 86 | method?: Method; 87 | }[]; 88 | cors?: { 89 | origin?: string | ((ctx: KoaCtx) => string); 90 | allowMethods?: string | string[]; 91 | exposeHeaders?: string | string[]; 92 | allowHeaders?: string | string[]; 93 | maxAge?: string | number; 94 | credentials?: boolean | ((ctx: KoaCtx) => string); 95 | keepHeadersOnError?: boolean; 96 | secureContext?: boolean; 97 | privateNetworkAccess?: boolean; 98 | }; 99 | nextRouter?: NextRouter; 100 | }; 101 | 102 | export interface OmniBaseConfig { 103 | type: PROJECT_TYPE; 104 | dev?: OmniServer & { 105 | devMiddlewareOptions?: Partial; 106 | webpack?: Configuration | (() => Configuration); 107 | configuration?: (config: ANY_OBJECT) => ANY_OBJECT; 108 | serverType?: ServerType; 109 | favicon?: string; 110 | }; 111 | server?: OmniServer & { serverType?: SSR_SERVER; }; 112 | build: { 113 | autoRelease?: boolean; 114 | srcDir: string; 115 | outDir: string; 116 | esmDir?: string; 117 | hash?: boolean | HASH; 118 | configuration?: (config: ANY_OBJECT) => ANY_OBJECT; 119 | tool?: Exclude; 120 | preflight?: { 121 | typescript?: boolean; 122 | test?: boolean; 123 | eslint?: boolean; 124 | prettier?: boolean; 125 | stylelint?: boolean; 126 | }; 127 | reserve?: { 128 | style?: boolean; 129 | assets?: (string | { srcPath: string; relativePath?: string; })[]; 130 | }; 131 | }; 132 | release: { 133 | git?: string; 134 | npm?: string | boolean; 135 | autoBuild?: boolean; 136 | autoTag?: boolean; 137 | preflight?: { 138 | test?: boolean; 139 | eslint?: boolean; 140 | prettier?: boolean; 141 | stylelint?: boolean; 142 | commitlint?: boolean; 143 | branch?: string; 144 | }; 145 | }; 146 | template: { 147 | root: string; 148 | test?: boolean; 149 | typescript?: boolean; 150 | stylesheet?: STYLE; 151 | readme?: MARKDOWN | boolean; 152 | }; 153 | plugins?: OmniPlugin[]; 154 | } 155 | 156 | export interface OmniRollupConfig extends OmniBaseConfig { 157 | build: OmniBaseConfig['build'] & { 158 | tool: Extract; 159 | configuration?: (getConfig: (bundle: boolean) => ANY_OBJECT) => ANY_OBJECT; 160 | }; 161 | } 162 | 163 | export type OmniConfig = OmniBaseConfig | OmniRollupConfig; -------------------------------------------------------------------------------- /src/utils/__test__/index.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha'; 2 | import { expect } from 'chai'; 3 | import { getHandlers, handlerFactory, logo, signal } from '../'; 4 | 5 | 6 | describe('tackle_plugins test', function () { 7 | it('type checking', function () { 8 | expect(getHandlers).to.be.a('function'); 9 | expect(handlerFactory).to.be.a('function'); 10 | }); 11 | 12 | it('call getHandlers', function () { 13 | getHandlers([ 14 | { 15 | stage: 'build', 16 | name: 'test-build', 17 | handler: function () { 18 | console.info('test handle build plugins'); 19 | return Promise.resolve(); 20 | } 21 | } 22 | ], 'build'); 23 | getHandlers([ 24 | { 25 | stage: 'new', 26 | name: 'test-new', 27 | handler: function () { 28 | console.info('test handle new plugins'); 29 | return Promise.resolve(); 30 | } 31 | } 32 | ], 'new'); 33 | getHandlers([ 34 | { 35 | stage: 'release', 36 | name: 'test-release', 37 | handler: function () { 38 | console.info('test handle release plugins'); 39 | return Promise.resolve(); 40 | } 41 | } 42 | ], 'release'); 43 | }); 44 | 45 | it('call handlerFactory', function () { 46 | const buildFn = handlerFactory(function () { 47 | console.info('test handle build plugins'); 48 | return Promise.resolve(); 49 | }); 50 | const newFn = handlerFactory(function () { 51 | console.info('test handle new plugins'); 52 | return Promise.resolve(); 53 | }); 54 | const releaseFn = handlerFactory(function () { 55 | console.info('test handle release plugins'); 56 | return Promise.resolve(); 57 | }); 58 | }); 59 | }); 60 | 61 | describe('logo test', function () { 62 | it('type checking', function () { 63 | expect(logo).to.be.a('function'); 64 | expect(logo()).to.be.a('string'); 65 | }); 66 | 67 | it('value checking', function () { 68 | expect(logo()).to.be.equal('🐸 '); 69 | }); 70 | }); 71 | 72 | describe('signal test', function () { 73 | it('type checking', function () { 74 | expect(signal).to.be.a('function'); 75 | }); 76 | }); -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export { default as logo } from './logo'; 2 | export { default as signal } from './signal'; 3 | export { getHandlers, handlerFactory } from './tackle_plugins'; -------------------------------------------------------------------------------- /src/utils/logo.ts: -------------------------------------------------------------------------------- 1 | import { getLogo } from '@omni-door/utils'; 2 | 3 | export const logo = () => getLogo() + ' '; 4 | 5 | export default logo; -------------------------------------------------------------------------------- /src/utils/signal.ts: -------------------------------------------------------------------------------- 1 | import { logInfo, logErr } from '@omni-door/utils'; 2 | 3 | export default function () { 4 | (['SIGINT', 'SIGQUIT', 'SIGTERM'] as NodeJS.Signals[]).forEach((sig) => { 5 | process.on(sig, () => { 6 | logInfo(`process exit by ${sig}`); 7 | process.exit(0); 8 | }); 9 | }); 10 | process.on('uncaughtException', e => { 11 | logErr(`uncaughtException - ${e.name}:${e.message}`); 12 | }); 13 | process.on('unhandledRejection', reason => { 14 | logErr(`unhandledRejection - ${JSON.stringify(reason)}`); 15 | }); 16 | process.on('exit', code => { 17 | logInfo(`exit with code ${code}`); 18 | }); 19 | } -------------------------------------------------------------------------------- /src/utils/tackle_plugins.ts: -------------------------------------------------------------------------------- 1 | import { logWarn } from '@omni-door/utils'; 2 | /* import types */ 3 | import type { PLUGIN_STAGE } from '@omni-door/utils'; 4 | import type { HandlerFactory, PluginHandler, OmniPlugin } from '../index.d'; 5 | 6 | export function getHandlers (plugins: OmniPlugin[], stage: T) { 7 | const handlers: { [pluginName: string]: PluginHandler } = {}; 8 | for (let i = 0; i < plugins.length; i++) { 9 | const plugin = plugins[i]; 10 | plugin.stage === stage && (handlers[plugin.name] = handlerFactory(plugin.handler, `The "${plugin.name}" execution error, will skip to continue the rest of the operaions(插件 "${plugin.name}" 执行发生错误,将跳过继续执行剩余操作)`)); 11 | } 12 | 13 | return handlers; 14 | } 15 | 16 | export const handlerFactory: HandlerFactory = (handler, errMsg) => (config, options) => { 17 | try { 18 | return Promise.resolve(handler(config, options)); 19 | } catch (err) { 20 | logWarn(err as any); 21 | logWarn(errMsg || 'The plugin execution error, will skip to continue the rest of the operaions(插件执行发生错误,将跳过继续执行剩余操作)'); 22 | } 23 | return Promise.resolve({}); 24 | }; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "compilerOptions": { 4 | /* Basic Options */ 5 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ 6 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 7 | "lib": ["dom", "es5", "es6", "es7", "es2017", "es2018", "esnext"], /* Specify library files to be included in the compilation. */ 8 | // "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | "jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 12 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 13 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 14 | // "outFile": "./", /* Concatenate and emit output to single file. */ 15 | "outDir": "./lib", /* Redirect output structure to the directory. */ 16 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 17 | // "composite": true, /* Enable project compilation */ 18 | // "removeComments": true, /* Do not emit comments to output. */ 19 | // "noEmit": true, /* Do not emit outputs. */ 20 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 21 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 22 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 23 | 24 | /* Strict Type-Checking Options */ 25 | "strict": true, /* Enable all strict type-checking options. */ 26 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 27 | // "strictNullChecks": true, /* Enable strict null checks. */ 28 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 29 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 30 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 31 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 32 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 33 | 34 | /* Additional Checks */ 35 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 36 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 37 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 38 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 39 | 40 | /* Module Resolution Options */ 41 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 42 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 43 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 44 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 45 | "typeRoots": ["node_modules/@types", "./src/@types"], /* List of folders to include type definitions from. */ 46 | // "types": [], /* Type declaration files to be included in compilation. */ 47 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 48 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 49 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 50 | 51 | /* Source Map Options */ 52 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 53 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 54 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 55 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 56 | 57 | /* Experimental Options */ 58 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 59 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 60 | }, 61 | "exclude": [ 62 | "node_modules", 63 | "src/**/__test__/*", 64 | "lib/*", 65 | "es/*", 66 | "build/*" 67 | ] 68 | } 69 | 70 | --------------------------------------------------------------------------------