├── .prettierignore ├── lib ├── template │ └── github │ │ ├── meta.json │ │ └── index.js ├── github.js ├── BasicCreate.js └── run.js ├── .debug ├── index.js ├── .gitignore ├── .prettierrc ├── cli.js ├── package.json └── README.md /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | 3 | -------------------------------------------------------------------------------- /lib/template/github/meta.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.debug: -------------------------------------------------------------------------------- 1 | Used in bin/cli.js to determine if it is in the local debug state. 2 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const runCli = require('./lib/run'); 2 | 3 | module.exports.run = runCli; 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /yarn.lock 3 | /dist 4 | package-lock.json 5 | .DS_Store 6 | coverage 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "printWidth": 100, 5 | "proseWrap": "never", 6 | "overrides": [ 7 | { 8 | "files": ".prettierrc", 9 | "options": { 10 | "parser": "json" 11 | } 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const yParser = require('yargs-parser'); 4 | const chalk = require('chalk'); 5 | const package = require('./package.json'); 6 | const { existsSync } = require('fs'); 7 | const { join } = require('path'); 8 | const semver = require('semver'); 9 | const runCli = require('./lib/run'); 10 | 11 | const args = yParser(process.argv.slice(2)); 12 | 13 | // 查看版本 14 | 15 | if (args.v || args.version) { 16 | console.log('version', chalk.blue(package.version)); 17 | if (existsSync(join(__dirname, '.debug'))) { 18 | // 如果是本地调试会打印 @debug 19 | console.log(chalk.cyan('@debug')); 20 | } 21 | process.exit(0); 22 | } 23 | 24 | if (!semver.satisfies(process.version, '>= 8.0.0')) { 25 | console.error(chalk.red('✘ The generator will only work with Node v8.0.0 and up!')); 26 | process.exit(1); 27 | } 28 | 29 | // 取默认应用名 30 | const name = args._[0] || ''; 31 | 32 | (async () => { 33 | await runCli({ 34 | name, 35 | args, 36 | }); 37 | process.exit(0); 38 | })(); 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-osdoc-app", 3 | "version": "0.0.6", 4 | "description": "create-osdoc-app cli", 5 | "main": "index.js", 6 | "repository": "git@github.com:osdoc-dev/create-osdoc-app.git", 7 | "author": "ahwgs ", 8 | "homepage": "https://github.com/osdoc-dev/create-osdoc-app", 9 | "bugs": { 10 | "url": "https://github.com/osdoc-dev/create-osdoc-app/issues" 11 | }, 12 | "license": "MIT", 13 | "files": [ 14 | "cli.js", 15 | "index.js", 16 | "lib" 17 | ], 18 | "bin": { 19 | "create-osdoc-app": "cli.js" 20 | }, 21 | "scripts": { 22 | "prettier": "prettier -c --write '**/*'" 23 | }, 24 | "dependencies": { 25 | "chalk": "^4.1.0", 26 | "clipboardy": "^2.3.0", 27 | "execa": "^4.0.2", 28 | "fs-extra": "^9.0.1", 29 | "glob": "^7.1.6", 30 | "inquirer": "^7.2.0", 31 | "mkdirp": "^1.0.4", 32 | "prettier": "^2.0.5", 33 | "semver": "^7.3.2", 34 | "yargs-parser": "^18.1.3", 35 | "yeoman-environment": "^2.10.3" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/github.js: -------------------------------------------------------------------------------- 1 | const githubConfig = [ 2 | { 3 | name: `${'fast_h5_umi'.padEnd(15)}- 基于umi3.x+typescript+antd-mobile 构建h5模板`, 4 | value: 'fast_h5_umi', 5 | short: 'fast_h5_umi', 6 | github: 'https://github.com/ahwgs/fast_h5_umi.git', 7 | }, 8 | { 9 | name: `${'fast_h5_vue'.padEnd(15)}- vue +vuex-cli3+vuex+vue-router+vant 快速开发 h5模板`, 10 | value: 'fast_h5_vue', 11 | short: 'fast_h5_vue', 12 | github: 'https://github.com/ahwgs/fast_h5_vue.git', 13 | }, 14 | { 15 | name: `${'fast_react_native'.padEnd( 16 | 15, 17 | )} - 基于 react-native+dvajs+antd-mobile-rn+react-navigation@5.x+axios+typescript 开发的通用react-native`, 18 | value: 'fast_react_native', 19 | short: 'fast_react_native', 20 | github: 'https://github.com/ahwgs/fast_react_native.git', 21 | }, 22 | { 23 | name: `${'fast_nest_temp'.padEnd(15)} - 基于 Nestjs7.x 快速构建Web应用`, 24 | value: 'fast_nest_temp', 25 | short: 'fast_nest_temp', 26 | github: 'https://github.com/ahwgs/fast_nest_temp.git', 27 | }, 28 | ]; 29 | module.exports = githubConfig; 30 | -------------------------------------------------------------------------------- /lib/template/github/index.js: -------------------------------------------------------------------------------- 1 | const BasicCreate = require('../../BasicCreate'); 2 | const fs = require('fs-extra'); 3 | const path = require('path'); 4 | const chalk = require('chalk'); 5 | const glob = require('glob'); 6 | const exec = require('execa'); 7 | const rimraf = require('rimraf'); 8 | 9 | function log(...args) { 10 | console.log(`${chalk.gray('>')}`, ...args); 11 | } 12 | 13 | class GithubCreate extends BasicCreate { 14 | async writing() { 15 | const projectName = this.opts.name || this.opts.env.cwd; 16 | const githubUrl = this.opts.github || ''; 17 | const projectPath = path.resolve(projectName); 18 | 19 | const yoConfigPth = path.join(projectPath, '.yo-repository'); 20 | if (fs.existsSync(yoConfigPth)) { 21 | // 删除 .yo-repository 22 | rimraf.sync(yoConfigPth); 23 | } 24 | 25 | if ( 26 | fs.existsSync(projectPath) && 27 | fs.statSync(projectPath).isDirectory() && 28 | fs.readdirSync(projectPath).length > 0 29 | ) { 30 | console.log('\n'); 31 | console.log(`🙈 请在空文件夹中使用`); 32 | process.exit(1); 33 | } 34 | 35 | const gitArgs = [`clone`, githubUrl, projectName, `--depth=1`]; 36 | 37 | // git action 38 | await exec( 39 | `git`, 40 | gitArgs, 41 | process.env.TEST 42 | ? {} 43 | : { 44 | stdout: process.stdout, 45 | stderr: process.stderr, 46 | stdin: process.stdin, 47 | }, 48 | ); 49 | 50 | log(`🚚 clone success`); 51 | } 52 | } 53 | 54 | module.exports = GithubCreate; 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # create-osdoc-app 2 | 3 | 快速新建业务模块脚手架 4 | 5 | ## 使用 6 | 7 | 全局安装 `create-osdoc-app` 8 | 9 | ```bash 10 | $ npm install create-osdoc-app -g 11 | ``` 12 | 13 | 安装后执行如下命令 14 | 15 | ```bash 16 | $ create-osdoc-app [appName] 17 | ``` 18 | 19 | ## 模板 20 | 21 | 目前支持模板有 22 | 23 | - [`fast_h5_umi`](https://github.com/ahwgs/fast_h5_umi) - 基于 umi3.x+typescript+antd-mobile 构建 h5 模板 24 | 25 | - [`fast_h5_vue`](https://github.com/ahwgs/fast_h5_vue) - vue +vuex-cli3+vuex+vue-router+vant 快速开发 h5 模板 26 | 27 | - [`fast_react_native`](https://github.com/ahwgs/fast_react_native) - 基于 react-native+dvajs+antd-mobile-rn+react-navigation@5.x+axios+typescript 开发的通用`react-native`模板 28 | 29 | - [`fast_nest_temp`](https://github.com/ahwgs/fast_nest_temp) - 基于`Nest.js@7.x` 快速构建 Web 应用 30 | 31 | ## 使用示例 32 | 33 | ```bash 34 | $ create-osdoc-app 35 | 36 | ? 🍖 请输入应用名称 (new-app)? 37 | new-app-demo 38 | 39 | ? 🌮 请选择应用模板 (Use arrow keys) 40 | > fast_h5_umi - 基于umi3.x+typescript+antd-mobile 构建h5模板 41 | fast_h5_vue - vue +vue-cli3+vuex+vue-router+vant 快速开发 h5模板 42 | fast_react_native - 基于 react-native+dvajs+antd-mobile-rn+react-navigation@5.x+axios+typescript 开发的通用react-native 43 | fast_nest_temp - 基于 Nest.js@7.x 快速构建 Web 应用 44 | 45 | Cloning into 'new-app'... 46 | remote: Enumerating objects: 123, done. 47 | remote: Counting objects: 100% (123/123), done. 48 | remote: Compressing objects: 100% (111/111), done. 49 | Receiving objects: 22% (28/123), 50 | Receiving objects: 100% (123/123), 99.90 KiB | 7.00 KiB/s, done. 51 | Resolving deltas: 100% (4/4), done. 52 | > 🚚 clone success 53 | 📋 Copied to clipboard, just use Ctrl+V 54 | ✨ File Generate Done 55 | ``` 56 | 57 | ## LICENSE 58 | 59 | MIT 60 | -------------------------------------------------------------------------------- /lib/BasicCreate.js: -------------------------------------------------------------------------------- 1 | const Generator = require('yeoman-generator'); 2 | const glob = require('glob'); 3 | const { statSync } = require('fs'); 4 | const { basename } = require('path'); 5 | const debug = require('debug')('create-osdoc-app:BasicCreate'); 6 | 7 | class BasicCreate extends Generator { 8 | constructor(opts) { 9 | super(opts); 10 | this.opts = opts; 11 | this.name = basename(opts.env.cwd); 12 | } 13 | 14 | isTsFile(f) { 15 | return f.endsWith('.ts') || f.endsWith('.tsx') || !!/(tsconfig\.json)/g.test(f); 16 | } 17 | 18 | writeFiles({ context, filterFiles = noop }) { 19 | // We have multiple welcome file, random this 20 | const welcomeImages = glob 21 | .sync('**/assets/welcomeImgs/*', { 22 | cwd: this.templatePath(), 23 | dot: true, 24 | }) 25 | .filter(filterFiles); 26 | 27 | if (welcomeImages.length) { 28 | const welcomeImg = welcomeImages[Math.floor(Math.random() * welcomeImages.length)]; 29 | debug(`copy ${welcomeImg}`); 30 | 31 | this.fs.copyTpl( 32 | this.templatePath(welcomeImg), 33 | this.destinationPath(welcomeImg.replace(/welcomeImgs.*$/, 'yay.jpg')), 34 | context, 35 | ); 36 | } 37 | 38 | debug(`context: ${JSON.stringify(context)}`); 39 | glob 40 | .sync('**/*', { 41 | cwd: this.templatePath(), 42 | dot: true, 43 | }) 44 | .filter(filterFiles) 45 | .filter((file) => !file.includes('welcomeImgs')) 46 | .forEach((file) => { 47 | debug(`copy ${file}`); 48 | const filePath = this.templatePath(file); 49 | if (statSync(filePath).isFile()) { 50 | this.fs.copyTpl( 51 | this.templatePath(filePath), 52 | this.destinationPath(file.replace(/^_/, '.')), 53 | context, 54 | ); 55 | } 56 | }); 57 | } 58 | 59 | prompt(questions) { 60 | process.send && process.send({ type: 'prompt' }); 61 | process.emit('message', { type: 'prompt' }); 62 | return super.prompt(questions); 63 | } 64 | } 65 | 66 | module.exports = BasicCreate; 67 | -------------------------------------------------------------------------------- /lib/run.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer'); 2 | const fs = require('fs'); 3 | const chalk = require('chalk'); 4 | const mkdirp = require('mkdirp'); 5 | const clipboardy = require('clipboardy'); 6 | const yeoman = require('yeoman-environment'); 7 | const path = require('path'); 8 | const githubConfig = require('./github'); 9 | 10 | const allGitHub = githubConfig.map((v) => v.value); 11 | 12 | const tempList = fs 13 | .readdirSync(`${__dirname}/template`) 14 | .filter((f) => !f.startsWith('.') && f !== 'github') 15 | .map((f) => { 16 | return { 17 | name: `${f.padEnd(15)} - ${chalk.gray(require(`./template/${f}/meta.json`).description)}`, 18 | value: f, 19 | short: f, 20 | }; 21 | }); 22 | 23 | const getChoices = () => { 24 | return [...tempList, ...githubConfig]; 25 | }; 26 | 27 | // 生成 28 | const runCreate = (generatorPath, { name = '', cwd = process.cwd(), args = {}, type }) => { 29 | return new Promise((resolve) => { 30 | if (name) { 31 | mkdirp.sync(name); 32 | cwd = path.join(cwd, name); 33 | } 34 | if (type && allGitHub.indexOf(type) >= 0) { 35 | generatorPath = `./template/github`; 36 | } 37 | 38 | const { github = '' } = githubConfig.find((v) => v.value === type) || ''; 39 | 40 | const Generator = require(generatorPath); 41 | 42 | const env = yeoman.createEnv([], { 43 | cwd, 44 | }); 45 | const generator = new Generator({ 46 | name, 47 | env, 48 | resolved: require.resolve(generatorPath), 49 | args, 50 | github, 51 | }); 52 | 53 | return generator.run(() => { 54 | if (name) { 55 | if (process.platform !== `linux` || process.env.DISPLAY) { 56 | clipboardy.writeSync(`cd ${name}`); 57 | console.log('📋 Copied to clipboard, just use Ctrl+V'); 58 | } 59 | } 60 | console.log('✨ File Generate Done'); 61 | resolve(true); 62 | }); 63 | }); 64 | }; 65 | 66 | async function runCli(payload) { 67 | process.send && process.send({ type: 'prompt' }); 68 | process.emit('message', { type: 'prompt' }); 69 | let { name } = payload; 70 | let type = ''; // 应用类型 71 | // 没有名字 先输入名字 72 | if (!name) { 73 | const answers = await inquirer.prompt([ 74 | { 75 | name: 'name', 76 | message: '🍖 请输入应用名称', 77 | type: 'input', 78 | default: 'new-app', 79 | }, 80 | ]); 81 | name = answers.name; 82 | } 83 | // 获取类型 84 | if (!type) { 85 | const answers = await inquirer.prompt([ 86 | { 87 | name: 'type', 88 | message: '🌮 请选择应用模板', 89 | type: 'list', 90 | choices: getChoices(), 91 | }, 92 | ]); 93 | type = answers.type; 94 | } 95 | try { 96 | console.log(name, type); 97 | return runCreate(`./template/${type}`, { 98 | ...payload, 99 | name, 100 | type, 101 | }); 102 | } catch (e) { 103 | console.error(chalk.red(`> Generate failed`), e); 104 | process.exit(1); 105 | } 106 | } 107 | 108 | module.exports = runCli; 109 | --------------------------------------------------------------------------------