├── images ├── GitFlowV2.jpg ├── npm-assistor-tag.gif ├── popular-license.png └── npm-assistor-init.gif ├── license-files ├── popular-license.png └── popular-license.xmind ├── lib ├── base │ ├── gitlab_fetch.js │ └── github_fetch.js ├── eslintrc.js ├── makefile.js ├── gitignore.js └── command │ └── add.js ├── Makefile ├── test-init ├── .Makefile ├── .gitignore ├── package.json └── .eslintrc ├── npm_assistor.yml ├── config.js ├── LICENSE ├── .eslintrc ├── package.json ├── .gitignore ├── test ├── index.spec.js └── tag.spec.js ├── index.js ├── command_init.js ├── README.md └── command_tag.js /images/GitFlowV2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plusmancn/npm-assistor/HEAD/images/GitFlowV2.jpg -------------------------------------------------------------------------------- /images/npm-assistor-tag.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plusmancn/npm-assistor/HEAD/images/npm-assistor-tag.gif -------------------------------------------------------------------------------- /images/popular-license.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plusmancn/npm-assistor/HEAD/images/popular-license.png -------------------------------------------------------------------------------- /images/npm-assistor-init.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plusmancn/npm-assistor/HEAD/images/npm-assistor-init.gif -------------------------------------------------------------------------------- /license-files/popular-license.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plusmancn/npm-assistor/HEAD/license-files/popular-license.png -------------------------------------------------------------------------------- /license-files/popular-license.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plusmancn/npm-assistor/HEAD/license-files/popular-license.xmind -------------------------------------------------------------------------------- /lib/base/gitlab_fetch.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * created at 2016.07.21 14:40:56 4 | * 5 | * Copyright (c) 2016 Souche.com, all rights 6 | * reserved. 7 | * 8 | * Gitlab 资源获取类 9 | */ 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | MOCHA_PATH=./node_modules/.bin/_mocha 2 | 3 | install-test-dependencies: 4 | @npm install --save-dev mocha should 5 | 6 | # 单元测试 7 | test: 8 | @NODE_ENV=test $(MOCHA_PATH) test/**/*.spec.js 9 | 10 | 11 | .PHONY: install-test-dependencies test 12 | -------------------------------------------------------------------------------- /test-init/.Makefile: -------------------------------------------------------------------------------- 1 | MOCHA_PATH=./node_modules/.bin/_mocha 2 | 3 | install-test-dependencies: 4 | @npm install --save-dev mocha should rewire 5 | 6 | # 单元测试 7 | test: 8 | @NODE_ENV=test $(MOCHA_PATH) test/**/*.spec.js 9 | 10 | 11 | .PHONY: install-test-dependencies test 12 | -------------------------------------------------------------------------------- /test-init/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/osx 3 | 4 | ### OSX ### 5 | *.DS_Store 6 | .AppleDouble 7 | .LSOverride 8 | 9 | # Icon must end with two \r 10 | Icon 11 | 12 | # Thumbnails 13 | ._* 14 | 15 | # Files that might appear in the root of a volume 16 | .DocumentRevisions-V100 17 | .fseventsd 18 | .Spotlight-V100 19 | .TemporaryItems 20 | .Trashes 21 | .VolumeIcon.icns 22 | .com.apple.timemachine.donotpresent 23 | 24 | # Directories potentially created on remote AFP share 25 | .AppleDB 26 | .AppleDesktop 27 | Network Trash Folder 28 | Temporary Items 29 | .apdisk 30 | -------------------------------------------------------------------------------- /npm_assistor.yml: -------------------------------------------------------------------------------- 1 | ################### npm-assistor Configuration Example ######################### 2 | 3 | ################## 公共模版库配置 ######################### 4 | templates: 5 | # github 远端配置库,会调用 api.github.com 相关 Api 进行内容获取 6 | # 支持多仓库 7 | github: 8 | - https://github.com/plusmancn/npm-assistor-template.git 9 | # - https://github.com/yourname/yourself-tempalte.git 10 | # Todo 最好支持下 gitlab,实现内网服务 11 | # gitlab: 12 | # - 13 | ################## gitignore 服务器地址 ######################### 14 | # 项目地址: https://github.com/joeblau/gitignore.io 15 | # 土豪大大们,可以选择在国内部署一份,屌丝只用得起国外便宜货 16 | gitignore_server: https://www.gitignore.io 17 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * created at 2016.07.20 14:15:10 4 | * 5 | * Copyright (c) 2016 Souche.com, all rights 6 | * reserved. 7 | */ 8 | 9 | const os = require('os'); 10 | const fs = require('fs'); 11 | const YAML = require('yamljs'); 12 | 13 | let config; 14 | 15 | function main(){ 16 | let configString = ''; 17 | try{ 18 | configString = fs.readFileSync(os.homedir()+ '/.npm_assistor.yml', 'utf8'); 19 | }catch(e){ 20 | configString = fs.readFileSync(__dirname + '/npm_assistor.yml', 'utf8'); 21 | } 22 | 23 | config = YAML.parse(configString); 24 | return config; 25 | } 26 | 27 | main(); 28 | 29 | module.exports = config; 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, plusmancn 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /test-init/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "npm-assistor", 3 | "version": "0.1.1", 4 | "description": "npm 包初始化和发布工具", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/plusmancn/npm-assistor.git" 12 | }, 13 | "keywords": [ 14 | "npm", 15 | "tool", 16 | "node" 17 | ], 18 | "author": "plusmancn@gmail.com", 19 | "license": "ISC", 20 | "bugs": { 21 | "url": "https://github.com/plusmancn/npm-assistor/issues" 22 | }, 23 | "homepage": "https://github.com/plusmancn/npm-assistor#readme", 24 | "devDependencies": { 25 | "mocha": "^2.5.3", 26 | "rewire": "^2.5.2", 27 | "should": "^10.0.0" 28 | }, 29 | "dependencies": { 30 | "async": "^2.0.0", 31 | "co": "^4.6.0", 32 | "colors": "^1.1.2", 33 | "inquirer": "^1.1.2", 34 | "inquirer-autocomplete-prompt": "^0.2.0", 35 | "lodash": "^4.13.1", 36 | "moment": "^2.14.1", 37 | "request": "^2.73.0", 38 | "yamljs": "^0.2.8" 39 | } 40 | } -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "node": true 5 | }, 6 | "extends": "eslint:recommended", 7 | "rules": { 8 | "indent": [ 9 | "error", 10 | 4 11 | ], 12 | "linebreak-style": [ 13 | "error", 14 | "unix" 15 | ], 16 | "quotes": [ 17 | "error", 18 | "single" 19 | ], 20 | "semi": [ 21 | "error", 22 | "always" 23 | ], 24 | "no-console": [ 25 | "error", 26 | { 27 | "allow": ["log", "info", "error"] 28 | } 29 | ], 30 | "guard-for-in": [ 31 | "error" 32 | ], 33 | "strict": [ 34 | "error", 35 | "global" 36 | ], 37 | "no-var": [ 38 | "error" 39 | ], 40 | "no-multiple-empty-lines": [ 41 | "error", 42 | { 43 | "max": 1 44 | } 45 | ] 46 | }, 47 | "globals":{ 48 | "describe": true, 49 | "it": true 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test-init/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "node": true 5 | }, 6 | "extends": "eslint:recommended", 7 | "rules": { 8 | "indent": [ 9 | "error", 10 | 4 11 | ], 12 | "linebreak-style": [ 13 | "error", 14 | "unix" 15 | ], 16 | "quotes": [ 17 | "error", 18 | "single" 19 | ], 20 | "semi": [ 21 | "error", 22 | "always" 23 | ], 24 | "no-console": [ 25 | "error", 26 | { 27 | "allow": ["log", "info", "error"] 28 | } 29 | ], 30 | "guard-for-in": [ 31 | "error" 32 | ], 33 | "strict": [ 34 | "error", 35 | "global" 36 | ], 37 | "no-var": [ 38 | "error" 39 | ], 40 | "no-multiple-empty-lines": [ 41 | "error", 42 | { 43 | "max": 1 44 | } 45 | ] 46 | }, 47 | "globals":{ 48 | "describe": true, 49 | "it": true 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/eslintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * created at 2016.07.21 13:08:49 4 | * 5 | * Copyright (c) 2016 Souche.com, all rights 6 | * reserved. 7 | */ 8 | const fs = require('fs'); 9 | const request = require('request'); 10 | const GithubFetch = require('./base/github_fetch.js'); 11 | const config = require('../config.js'); 12 | 13 | // 文件写入 14 | function write(url){ 15 | return new Promise(function(resolve, reject){ 16 | request 17 | .get(url) 18 | .on('error', function(err){ 19 | console.error('npm-begin error: ', err); 20 | reject(err); 21 | }) 22 | .on('end', function(){ 23 | console.info('🎉 ' + '>>> '.green + '.eslintrc has been generated under current folder successfully!'.grey); 24 | resolve('success'); 25 | }) 26 | .pipe(fs.createWriteStream('.eslintrc')); 27 | }); 28 | } 29 | 30 | function list(){ 31 | let githubFetch = new GithubFetch(config.templates.github); 32 | return githubFetch.list('template-eslint'); 33 | } 34 | 35 | module.exports = { 36 | write: write, 37 | list: list 38 | }; 39 | -------------------------------------------------------------------------------- /lib/makefile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * created at 2016.07.21 13:08:49 4 | * 5 | * Copyright (c) 2016 Souche.com, all rights 6 | * reserved. 7 | */ 8 | const fs = require('fs'); 9 | const request = require('request'); 10 | const GithubFetch = require('./base/github_fetch.js'); 11 | const config = require('../config.js'); 12 | 13 | // 文件写入 14 | function write(url){ 15 | return new Promise(function(resolve, reject){ 16 | request 17 | .get(url) 18 | .on('error', function(err){ 19 | console.error('npm-begin error: ', err); 20 | reject(err); 21 | }) 22 | .on('end', function(){ 23 | console.info('🎉 ' + '>>> '.green + 'Makefile has been generated under current folder successfully!'.grey); 24 | resolve('success'); 25 | }) 26 | .pipe(fs.createWriteStream('Makefile')); 27 | }); 28 | } 29 | 30 | function list(){ 31 | let githubFetch = new GithubFetch(config.templates.github); 32 | return githubFetch.list('template-makefile'); 33 | } 34 | 35 | module.exports = { 36 | write: write, 37 | list: list 38 | }; 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "npm-assistor", 3 | "version": "1.0.0", 4 | "description": "npm 包初始化和发布工具", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "bin": { 10 | "npm-assistor": "./index.js" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/plusmancn/npm-assistor.git" 15 | }, 16 | "keywords": [ 17 | "npm", 18 | "tool", 19 | "node" 20 | ], 21 | "author": "plusmancn@gmail.com", 22 | "license": "ISC", 23 | "bugs": { 24 | "url": "https://github.com/plusmancn/npm-assistor/issues" 25 | }, 26 | "homepage": "https://github.com/plusmancn/npm-assistor#readme", 27 | "devDependencies": { 28 | "eslint": "^3.16.1", 29 | "mocha": "^2.5.3", 30 | "rewire": "^2.5.2", 31 | "should": "^10.0.0" 32 | }, 33 | "dependencies": { 34 | "async": "^2.0.0", 35 | "co": "^4.6.0", 36 | "colors": "^1.1.2", 37 | "commander": "^5.0.0", 38 | "inquirer": "^1.1.2", 39 | "inquirer-autocomplete-prompt": "^0.2.0", 40 | "lodash": "^4.13.1", 41 | "moment": "^2.14.1", 42 | "request": "^2.73.0", 43 | "yamljs": "^0.2.8" 44 | } 45 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/osx,vim,node 3 | 4 | ### OSX ### 5 | *.DS_Store 6 | .AppleDouble 7 | .LSOverride 8 | 9 | # Icon must end with two \r 10 | Icon 11 | 12 | # Thumbnails 13 | ._* 14 | 15 | # Files that might appear in the root of a volume 16 | .DocumentRevisions-V100 17 | .fseventsd 18 | .Spotlight-V100 19 | .TemporaryItems 20 | .Trashes 21 | .VolumeIcon.icns 22 | .com.apple.timemachine.donotpresent 23 | 24 | # Directories potentially created on remote AFP share 25 | .AppleDB 26 | .AppleDesktop 27 | Network Trash Folder 28 | Temporary Items 29 | .apdisk 30 | 31 | 32 | ### Vim ### 33 | # swap 34 | [._]*.s[a-w][a-z] 35 | [._]s[a-w][a-z] 36 | # session 37 | Session.vim 38 | # temporary 39 | .netrwhist 40 | *~ 41 | # auto-generated tag files 42 | tags 43 | 44 | 45 | ### Node ### 46 | # Logs 47 | logs 48 | *.log 49 | npm-debug.log* 50 | 51 | # Runtime data 52 | pids 53 | *.pid 54 | *.seed 55 | *.pid.lock 56 | 57 | # Directory for instrumented libs generated by jscoverage/JSCover 58 | lib-cov 59 | 60 | # Coverage directory used by tools like istanbul 61 | coverage 62 | 63 | # nyc test coverage 64 | .nyc_output 65 | 66 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 67 | .grunt 68 | 69 | # node-waf configuration 70 | .lock-wscript 71 | 72 | # Compiled binary addons (http://nodejs.org/api/addons.html) 73 | build/Release 74 | 75 | # Dependency directories 76 | node_modules 77 | jspm_packages 78 | 79 | # Optional npm cache directory 80 | .npm 81 | 82 | # Optional REPL history 83 | .node_repl_history 84 | -------------------------------------------------------------------------------- /test/index.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * created at 2016.07.21 09:37:01 4 | * 5 | * Copyright (c) 2016 Souche.com, all rights 6 | * reserved. 7 | */ 8 | const should = require('should'); 9 | const rewire = require('rewire'); 10 | const eslintrc = rewire('../lib/eslintrc.js'); 11 | const GithubFetch = require('../lib/base/github_fetch.js'); 12 | 13 | describe('npm-assistor#init', function(){ 14 | describe('#eslintrc', function(){ 15 | 16 | describe('#parseGithubUrl', function(){ 17 | let parseGithubUrl = GithubFetch.prototype.parseGithubUrl; 18 | describe('#input https://github.com/plusmancn/npm-assistor-template.git', function(){ 19 | it('should return { owner: \'plusmancn\', repo: \'npm-assistor-template\' }', function(){ 20 | let actual = parseGithubUrl('https://github.com/plusmancn/npm-assistor-template.git'); 21 | let expected = { 22 | owner: 'plusmancn', 23 | repo: 'npm-assistor-template' 24 | }; 25 | should.deepEqual(actual, expected); 26 | }); 27 | }); 28 | }); 29 | 30 | describe('#list', function(){ 31 | this.timeout(15e3); 32 | describe('#读取配置文件,npm_assistor.yml', function(){ 33 | it('should 结果集长度大于1', function(done){ 34 | eslintrc.list() 35 | .then(function(result){ 36 | (result.length).should.be.above(1); 37 | done(); 38 | }); 39 | }); 40 | }); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | /** 4 | * created at 2016.07.20 13:34:55 5 | * 6 | * Copyright (c) 2016 Souche.com, all rights 7 | * reserved. 8 | */ 9 | 10 | const co = require('co'); 11 | const inquirer = require('inquirer'); 12 | const { program } = require('commander'); 13 | 14 | const commandInit = require('./command_init.js'); 15 | const commandTag = require('./command_tag.js'); 16 | 17 | function main(){ 18 | let isCommand = false; 19 | program 20 | .command('init') 21 | .description('用于 npm 包初始化,请在 npm init 后执行') 22 | .action((source, destination) => { 23 | isCommand = true; 24 | commandInit(); 25 | }); 26 | program 27 | .command('tag') 28 | .description('用于发布前 master 分支的 tag 标记)') 29 | .action((source, destination) => { 30 | isCommand = true; 31 | commandTag(); 32 | }); 33 | program.parse(process.argv); 34 | if(isCommand) return; 35 | 36 | let schema = [{ 37 | type: 'list', 38 | name: 'command', 39 | message: '选择要执行的命令', 40 | choices: [ 41 | { 42 | name: 'init (用于 npm 包初始化,请在 npm init 后执行)', 43 | value: 'init', 44 | short: 'init' 45 | }, 46 | { 47 | name: 'tag (用于发布前 master 分支的 tag 标记)', 48 | value: 'tag', 49 | short: 'tag' 50 | } 51 | ] 52 | }]; 53 | 54 | co(function *(){ 55 | let result = yield inquirer.prompt(schema); 56 | 57 | switch (result.command) { 58 | case 'init': 59 | commandInit(); 60 | break; 61 | case 'tag': 62 | commandTag(); 63 | break; 64 | } 65 | }); 66 | } 67 | 68 | main(); 69 | -------------------------------------------------------------------------------- /lib/base/github_fetch.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * created at 2016.07.21 14:23:18 4 | * 5 | * Copyright (c) 2016 Souche.com, all rights 6 | * reserved. 7 | * 8 | * Github Api 资源获取类 9 | */ 10 | 11 | const request = require('request'); 12 | const inquirer = require('inquirer'); 13 | const co = require('co'); 14 | 15 | function GithubFetch(repos){ 16 | this.repos = repos; 17 | } 18 | 19 | // 获取可用选项 20 | GithubFetch.prototype.getAvailableOptions = function getAvailableOptions(user, repo, part){ 21 | return new Promise(function(resolve, reject){ 22 | let url = `https://api.github.com/repos/${user}/${repo}/contents/${part}`; 23 | request({ 24 | url: url, 25 | headers: { 26 | 'User-Agent':'Npm-assistor/0.1.0 (contact to plusmancn@gmail.com)' 27 | }, 28 | json: true 29 | }, function(err, resp, body){ 30 | if(err){ 31 | return reject(err); 32 | } 33 | resolve(body); 34 | }); 35 | }); 36 | }; 37 | 38 | // repo 解析 39 | GithubFetch.prototype.parseGithubUrl = function parseGithubUrl(url){ 40 | let matchRes = url.match(/http[s]?:\/\/github.com\/([\w\-]+)\/([\w\-]+)/); 41 | return { 42 | owner: matchRes[1], 43 | repo: matchRes[2] 44 | }; 45 | }; 46 | 47 | // 获取配置列表 48 | GithubFetch.prototype.list = function list(part){ 49 | let self = this; 50 | 51 | return co(function *(){ 52 | let choices = []; 53 | for(let i = 0, l = self.repos.length; i < l; i++){ 54 | let repoInfo = self.parseGithubUrl(self.repos[i]); 55 | let body = yield self.getAvailableOptions(repoInfo.owner, repoInfo.repo, part); 56 | choices.push(new inquirer.Separator(`------ ${repoInfo.owner}/${repoInfo.repo} ------`)); 57 | body.forEach(function(item){ 58 | choices.push({ 59 | name: item.name, 60 | value: item.download_url 61 | }); 62 | }); 63 | } 64 | 65 | return choices; 66 | }); 67 | }; 68 | 69 | module.exports = GithubFetch; 70 | -------------------------------------------------------------------------------- /command_init.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * created at 2016.07.14 19:23:03 4 | * 5 | * Copyright (c) 2016 Souche.com, all rights 6 | * reserved. 7 | */ 8 | 9 | const co = require('co'); 10 | const colors = require('colors'); 11 | const inquirer = require('inquirer'); 12 | inquirer.registerPrompt('autocomplete', require('inquirer-autocomplete-prompt')); 13 | 14 | // libs 15 | const libGitignore = require('./lib/gitignore.js'); 16 | const libEslintrc = require('./lib/eslintrc.js'); 17 | const libMakefile = require('./lib/makefile.js'); 18 | 19 | // Choose .gitignore 20 | function gitignore(){ 21 | let schema = [{ 22 | type: 'autocomplete', 23 | name: 'gitignore', 24 | message: '.gitignore'.green + ' (多个配置用英文逗号分隔)', 25 | source: libGitignore.searcher 26 | }]; 27 | 28 | co(function *(){ 29 | let supportSystemList = yield libGitignore.getSupportSystem(); 30 | let tips = '.gitignore Hosting '.blue + colors.green(supportSystemList.length) + ' Operating System, IDE, and Programming Language .gitignore templates'.blue; 31 | console.log(tips); 32 | 33 | let result = yield inquirer.prompt(schema); 34 | return libGitignore.generate(result.gitignore.split(' + ')); 35 | }) 36 | .then(function(){ 37 | eslintrc(); 38 | }) 39 | .catch(function(err){ 40 | console.error(err); 41 | }); 42 | } 43 | 44 | // Choose .eslintrc 45 | function eslintrc(){ 46 | let schema = [{ 47 | type: 'list', 48 | name: 'eslintrc', 49 | message: 'eslint 校验规则模板' 50 | }]; 51 | 52 | co(function *(){ 53 | schema[0].choices = yield libEslintrc.list(); 54 | let result = yield inquirer.prompt(schema); 55 | return libEslintrc.write(result.eslintrc); 56 | }).then(function(){ 57 | makefile(); 58 | }).catch(function(err){ 59 | console.error(err); 60 | }); 61 | } 62 | 63 | // Choose makefile 64 | function makefile(){ 65 | let schema = [{ 66 | type: 'list', 67 | name: 'makefile', 68 | message: 'Makefile 模板' 69 | }]; 70 | 71 | co(function *(){ 72 | schema[0].choices = yield libMakefile.list(); 73 | let result = yield inquirer.prompt(schema); 74 | return libMakefile.write(result.makefile); 75 | }).then(function(){ 76 | console.log('------------------------------------------'.white + '\n🙂 ' + ' >>> '.green + ' Well begun is half done!'.rainbow); 77 | }).catch(function(err){ 78 | console.error(err); 79 | }); 80 | } 81 | 82 | // main 83 | module.exports = gitignore; 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # npm-assistor 2 | [![npm-assistor](http://img.shields.io/npm/v/npm-assistor.svg)](https://www.npmjs.org/package/npm-assistor) 3 | 4 | > npm 包初始化和 git tag 辅助工具。源于搜车前端内部规范化改造,具体指内部功能模块 SDK 化和 GitFlow 流程规范化。Github: [https://github.com/plusmancn/npm-assistor](https://github.com/plusmancn/npm-assistor) 欢迎 star 和 pr 5 | 6 | ## Usage 7 | 全局安装 8 | ```shell 9 | npm install -g npm-assistor 10 | ``` 11 | 在项目根目录(package.json 所在文件夹)执行 12 | ```shell 13 | npm-assistor 14 | ``` 15 | 弹出如下选择界面 16 | ```shell 17 | ? 选择要执行的命令 (Use arrow keys) 18 | ❯ init (用于 npm 包初始化,请在 npm init 后执行) 19 | tag (用于发布前 master 分支的 tag 标记) 20 | ``` 21 | **选择 init 后的交互(Gif)** 22 | ![npm-assistor-init](https://github.com/plusmancn/npm-assistor/raw/master/images/npm-assistor-init.gif) 23 | **选择 tag 后的交互(Gif)** 24 | ![npm-assistor-tag](https://github.com/plusmancn/npm-assistor/raw/master/images/npm-assistor-tag.gif) 25 | 26 | ## Config(Important!!) 27 | **config 读取规则** 28 | 优先读取用户目录下的 `~/.npm_assistor.yml` 文件,如果不存在,则使用项目默认配置。 29 | 默认配置如下: 30 | ```yaml 31 | ################### npm-assistor Configuration Example ######################### 32 | 33 | ################## 公共模版库配置 ######################### 34 | templates: 35 | # github 远端配置库,会调用 api.github.com 相关 Api 进行内容获取 36 | # 支持多仓库 37 | github: 38 | # 可以 fork 示例模板,添加自己的配置 39 | - https://github.com/plusmancn/npm-assistor-template.git 40 | # - https://github.com/yourname/yourself-tempalte.git 41 | # Todo 最好支持下 gitlab,实现内网服务 42 | # gitlab: 43 | # - 44 | ################## gitignore 服务器地址 ######################### 45 | # 项目地址: https://github.com/joeblau/gitignore.io 46 | # 土豪大大们,可以选择在国内部署一份,屌丝只用得起国外便宜货 47 | gitignore_server: http://gitignore.plusman.cn:8000 48 | ``` 49 | 50 | ## Init 说明 51 | **eslintrc** 52 | 具体 IDE 集成参考:[eslint.org](http://eslint.org/) 53 | 54 | **LICENSE(未做集成)** 55 | [licenses list by name]( https://opensource.org/licenses/alphabetical ) 56 | 并未做成命令行,具体可以参考,源码 `licenses-files` 文件夹,内含思维导图 xmind 格式 57 | ![popular-license](https://github.com/plusmancn/npm-assistor/raw/master/images/popular-license.png) 58 | 59 | ## Tag 说明 60 | **Tag说明** 61 | 发布号部分遵循 [semver](http://f2e.souche.com/blog/fan-yi-ru-he-zheng-que-de-ming-ming-ruan-jian-ban-ben-hao/) 规范设计,简版说明如下 62 | ```javascript 63 | 格式如 ${major}.${feature}.${patch},遵循 semver 规范的版本号 64 | 选择需要递增的版本号 65 | major: 主版本号,用于断代更新或大版本发布 66 | feature: 特性版本号,用于向下兼容的特性新增 67 | patch: 修订版本号,用于 bug 修复 68 | 递增位的右侧位需要清零,如 1.1.2 => 1.2.0 69 | ``` 70 | 71 | 发布日期部分遵循 `{year}w{weeks}{a-z: 本周第几次发布}`,此部分可选,如果服务端项目发布必带;sdk 发布一般不带 72 | 73 | **周数定义** 74 | ```javascript 75 | const moment = require('moment'); 76 | // 更新 week 设置 77 | moment.locale('zh-cn', { 78 | // 每年第一周的定义: 79 | // 国内:包含1月4号的那周为每年第一周 80 | // 美国:包含1月1号的那周为每年第一周(苹果日历也是如此) 81 | // 更新了下 moment,现在规则是 包含1月1号的那周为每年第一周,新的一周起始于周一 82 | //(比较好理解,苹果日历也可设置,设置->高级->显示周数) 83 | week : { 84 | dow : 1, // Monday is the first day of the week. 85 | doy : 7 // The week that contains Jan 1th is the first week of the year. 86 | } 87 | }); 88 | ``` 89 | 90 | **GitFlow 流程** 91 | 附上团队内部修改过的GitFlow 流程。[a-successful-git-branching-model/](http://nvie.com/posts/a-successful-git-branching-model/) 原博客结尾有 keynote 源码哈,可以在这基础上改出适合自己团队的 GitFlow 92 | ![GitFlowV2 Of souche](https://github.com/plusmancn/npm-assistor/raw/master/images/GitFlowV2.jpg) 93 | 94 | ## About 95 | Have Fun! 96 | -------------------------------------------------------------------------------- /lib/gitignore.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * created at 2016.07.14 19:22:47 4 | * 5 | * Copyright (c) 2016 Souche.com, all rights 6 | * reserved. 7 | */ 8 | 9 | const _ = require('lodash'); 10 | const fs = require('fs'); 11 | const request = require('request'); 12 | const config = require('../config.js'); 13 | 14 | const HOST = config.gitignore_server; 15 | 16 | // 从远端获取支持的 IDE 17 | let _supportSystem; 18 | function _getSupportSystem(){ 19 | return new Promise(function(resolve, reject){ 20 | request({ 21 | url: `${HOST}/api/list`, 22 | method: 'GET', 23 | json: true 24 | }, function(err, resp, body){ 25 | if(err){ 26 | reject(err); 27 | }else{ 28 | _supportSystem = body.split(/,|\n/).map(item => item.trim()); 29 | resolve(body); 30 | } 31 | }); 32 | }); 33 | } 34 | 35 | function recursion(keywords, searchTree){ 36 | let key = keywords.shift(); 37 | if(key){ 38 | key = key.toLowerCase(); 39 | let matches = _supportSystem.filter(function(state) { 40 | return ~state.toLowerCase().indexOf(key); 41 | }); 42 | 43 | // 按照匹配度排序,以 xx 开头。。。。,完全匹配优先级最高 44 | matches.sort(function(a, b){ 45 | if(a.toLowerCase() === key){ 46 | return -1; 47 | } 48 | if(b.toLowerCase() === key){ 49 | return 1; 50 | } 51 | return a.toLowerCase().indexOf(key) - b.toLowerCase().indexOf(key); 52 | }); 53 | 54 | searchTree.push(matches); 55 | return recursion(keywords, searchTree); 56 | }else{ 57 | return flatArr(searchTree); 58 | } 59 | } 60 | 61 | function flatArr(_2wArray){ 62 | let deep = _2wArray.length; 63 | if(deep <=1){ 64 | return _2wArray[0]; 65 | } 66 | 67 | let flatArray; 68 | for(let i = 1; i < deep; i++){ 69 | if(!flatArray){ 70 | flatArray = twoToOne(_2wArray[0], _2wArray[1]); 71 | }else{ 72 | flatArray = twoToOne(flatArray, _2wArray[i]); 73 | } 74 | } 75 | return flatArray; 76 | } 77 | 78 | // 二维数组一纬展开 79 | function twoToOne(arr1, arr2){ 80 | let newArr = []; 81 | for(let i =0; i < arr1.length; i++){ 82 | newArr = newArr.concat(arr2.map(function(item){ 83 | return `${arr1[i]} + ${item}`; 84 | })); 85 | } 86 | return newArr; 87 | } 88 | 89 | function searcher(answers, input){ 90 | return new Promise(function(resolve){ 91 | if(input){ 92 | let keywords = input.split(',').map(function(item){ 93 | return _.trim(item); 94 | }); 95 | let searchTree = []; 96 | resolve(recursion(keywords, searchTree)); 97 | }else{ 98 | resolve(_supportSystem); 99 | } 100 | }); 101 | } 102 | 103 | function generate(arr){ 104 | return new Promise(function(resolve, reject){ 105 | let url = `${HOST}/api/` + encodeURIComponent(arr.join(',')); 106 | request 107 | .get(url) 108 | .on('error', function(err){ 109 | console.error(err); 110 | reject(err); 111 | }) 112 | .on('end', function(){ 113 | console.info('🎉 ' + '>>> '.green + '.gitignore has been generated under current folder successfully!'.grey); 114 | resolve('success'); 115 | }) 116 | .pipe(fs.createWriteStream('.gitignore')); 117 | }); 118 | } 119 | 120 | module.exports = { 121 | generate: generate, 122 | searcher: searcher, 123 | getSupportSystem: _getSupportSystem 124 | }; 125 | -------------------------------------------------------------------------------- /test/tag.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * created at 2016.07.05 22:10:39 4 | * 5 | * Copyright (c) 2016 Souche.com, all rights 6 | * reserved. 7 | * 8 | * 测试用例 9 | */ 10 | 11 | const moment = require('moment'); 12 | // 更新 week 设置 13 | moment.locale('zh-cn', { 14 | // 每年第一周的定义: 15 | // 国内:包含1月4号的那周为每年第一周 16 | // 美国:包含1月1号的那周为每年第一周(苹果日历也是如此) 17 | // 更新了下 moment,现在规则是 包含1月1号的那周为每年第一周,新的一周起始于周一(比较好理解,苹果日历也可设置) 18 | week : { 19 | dow : 1, // Monday is the first day of the week. 20 | doy : 7 // The week that contains Jan 1th is the first week of the year. 21 | } 22 | }); 23 | const should = require('should'); 24 | const libAdd = require('../lib/command/add.js'); 25 | 26 | let thisYear = moment().format('YY'); 27 | let thisWeek = moment().weeks(); 28 | 29 | describe('npm-assistor#tag', function(){ 30 | describe('#add', function(){ 31 | describe('#版本递增', function(){ 32 | describe('#递增主版本号,输入1.1.1', function(){ 33 | it('should return 2.0.0', function(){ 34 | let tag = libAdd.generateTag({ 35 | version: '1.1.1', 36 | part: 'major' 37 | }); 38 | should.equal(tag, '2.0.0', '主版本号递增错误'); 39 | }); 40 | }); 41 | 42 | describe('#递增特性号,输入1.1.1', function(){ 43 | it('should return 1.2.0', function(){ 44 | let tag = libAdd.generateTag({ 45 | version: '1.1.1', 46 | part: 'feature' 47 | }); 48 | should.equal(tag, '1.2.0', '特性号递增错误'); 49 | }); 50 | }); 51 | 52 | describe('#递增修订号,输入1.1.1', function(){ 53 | it('should return 1.1.2', function(){ 54 | let tag = libAdd.generateTag({ 55 | version: '1.1.1', 56 | part: 'patch' 57 | }); 58 | should.equal(tag, '1.1.2', '修订号递增错误'); 59 | }); 60 | }); 61 | }); 62 | 63 | describe('#时间 tag 递增,版本号部分递增 major', function(){ 64 | 65 | describe('#前一个 tag 在当前周前发布,输入1.1.1+16w0b', function(){ 66 | let equal = `2.0.0+${thisYear}w${thisWeek}a`; 67 | it(`should return ${equal}`, function(){ 68 | let tag = libAdd.generateTag({ 69 | version: '1.1.1+16w0b', 70 | part: 'major' 71 | }); 72 | should.equal(tag, equal, '时间 tag 递增错误'); 73 | }); 74 | }); 75 | 76 | let input1 = `1.1.1+${thisYear}w${thisWeek}a`; 77 | describe(`#前一个 tag 在当前周发布,输入${input1}`, function(){ 78 | let equal = `2.0.0+${thisYear}w${thisWeek}b`; 79 | it(`should return ${equal}`, function(){ 80 | let tag = libAdd.generateTag({ 81 | version: input1, 82 | part: 'major' 83 | }); 84 | should.equal(tag, equal, '时间 tag 递增错误'); 85 | }); 86 | }); 87 | 88 | let input2 = `1.1.1+${thisYear}w${thisWeek}z`; 89 | describe(`#前一个 tag 在当前周发布,输入${input2}`, function(){ 90 | it('should throw Error 已超过单周发布次数上限[a-z]', function(){ 91 | (function(){ 92 | libAdd.generateTag({ 93 | version: input2, 94 | part: 'major' 95 | }); 96 | }).should.throw('已超过发布次数上限[a-z]'); 97 | }); 98 | }); 99 | 100 | }); 101 | }); 102 | 103 | describe('#versionValidate', function(){ 104 | describe('#输入1.1.1, 1.2.1', function(){ 105 | it('should return 递增位右侧位需要清零', function(){ 106 | let res = libAdd.versionValidate('1.1.1', '1.2.1'); 107 | should.deepEqual(res, { 108 | pass: false, 109 | message: '递增位右侧位需要清零' 110 | }); 111 | }); 112 | }); 113 | 114 | describe('#输入2.1.1, 1.2.1', function(){ 115 | it('should return 新版本号不能低于老版本号', function(){ 116 | let res = libAdd.versionValidate('2.1.1', '1.2.1'); 117 | should.deepEqual(res, { 118 | pass: false, 119 | message: '新版本号不能低于老版本号' 120 | }); 121 | }); 122 | }); 123 | 124 | describe('#输入1.2.1, 1.2.1', function(){ 125 | it('should return 新老版本号不能相同', function(){ 126 | let res = libAdd.versionValidate('1.2.1', '1.2.1'); 127 | should.deepEqual(res, { 128 | pass: false, 129 | message: '新老版本号不能相同' 130 | }); 131 | }); 132 | }); 133 | 134 | describe('#输入1.2.1, 1.3.0', function(){ 135 | it('should return success', function(){ 136 | let res = libAdd.versionValidate('1.2.1', '1.3.0'); 137 | should.deepEqual(res, { 138 | pass: true, 139 | message: 'success' 140 | }); 141 | }); 142 | }); 143 | }); 144 | }); 145 | -------------------------------------------------------------------------------- /lib/command/add.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * created at 2016.07.04 17:57:18 4 | * 5 | * Copyright (c) 2016 Souche.com, all rights 6 | * reserved. 7 | * 8 | * 用于标签创建 9 | */ 10 | const fs = require('fs'); 11 | const moment = require('moment'); 12 | const childProcess = require('child_process'); 13 | const partChoices = ['major', 'feature', 'patch']; 14 | 15 | function generateTagHandInput(argv){ 16 | let newTag = argv.version; 17 | let currentVersion = _getCurentVersion().split('+'); 18 | if(argv.isNeedPublishTimesTag){ 19 | newTag = argv.version + '+' + _transformDate(currentVersion[1]); 20 | } 21 | return newTag; 22 | } 23 | 24 | function generateTag(argv){ 25 | let currentVersion = argv.version || _getCurentVersion(); 26 | if(currentVersion){ 27 | currentVersion = currentVersion.split('+'); 28 | let semverVersion = _transformSemver(currentVersion[0], argv.part); 29 | 30 | if(currentVersion[1]){ 31 | semverVersion = semverVersion + '+' + _transformDate(currentVersion[1]); 32 | } 33 | 34 | return semverVersion; 35 | } 36 | 37 | return ''; 38 | } 39 | 40 | // 读取 packge.json 文件,获取当前版本信息 41 | function _getCurentVersion(){ 42 | let path = process.cwd() + '/package.json'; 43 | let stats = fs.statSync(path); 44 | if(stats.isFile()){ 45 | let packageInfo = JSON.parse(fs.readFileSync(path)); 46 | return packageInfo.version; 47 | } 48 | return ''; 49 | } 50 | 51 | // semver 52 | function _transformSemver(semverVersion, part){ 53 | let semverArray = semverVersion.split('.').map(function(val){ 54 | return +val; 55 | }); 56 | let increasePosition = partChoices.indexOf(part); 57 | semverArray[increasePosition]++; 58 | for(let i = increasePosition + 1; i < semverArray.length; i++){ 59 | semverArray[i] = 0; 60 | } 61 | 62 | return semverArray.join('.'); 63 | } 64 | 65 | // date tag 66 | function _transformDate(timeTag){ 67 | let m = moment(); 68 | let thisWeek = +m.weeks(); 69 | let thisYear = +m.format('YY'); 70 | 71 | if(!timeTag){ 72 | return `${thisYear}w${thisWeek}a`; 73 | } 74 | 75 | let matches = timeTag.match(/(\d{2})w(\d{1,2})(\w)/i); 76 | 77 | let timesLetter = 'a'; 78 | if(thisYear === +matches[1] && thisWeek === +matches[2]){ 79 | let timesCode = matches[3].charCodeAt(0); 80 | if(++timesCode <= 112){ 81 | timesLetter = String.fromCharCode(timesCode); 82 | }else{ 83 | throw new Error('已超过发布次数上限[a-z]'); 84 | } 85 | } 86 | 87 | return `${thisYear}w${thisWeek}${timesLetter}`; 88 | } 89 | 90 | function _gitTagAdd(tag, message, callback){ 91 | let command = `git tag -a '${tag}' -m '${message}'`; 92 | console.log(`>>> Exec ${command} at ${process.cwd()}`); 93 | childProcess.exec(command, function(err, stdout, stderr){ 94 | callback(stderr, stdout); 95 | }); 96 | 97 | } 98 | 99 | function _gitTagPush(tag, callback){ 100 | let command = `git push origin '${tag}'`; 101 | console.log(`>>> Exec ${command} at ${process.cwd()}`); 102 | childProcess.exec(command, function(err, stdout, stderr){ 103 | if(!err){ 104 | stdout = stderr; 105 | stderr = null; 106 | } 107 | callback(stderr, stdout); 108 | }); 109 | } 110 | 111 | function _changePackage(tag, callback){ 112 | let path = process.cwd() + '/package.json'; 113 | let stats = fs.statSync(path); 114 | if(stats.isFile()){ 115 | let packageInfo = JSON.parse(fs.readFileSync(path)); 116 | packageInfo.version = tag; 117 | fs.writeFileSync(path, JSON.stringify(packageInfo, null, ' ')); 118 | 119 | childProcess.execSync('git config --local push.default simple'); 120 | // 过分干涉用户行为了,后来想想又是必须的,╮(╯_╰)╭ 121 | let command = `git add package.json && git commit -m \'chore(package.json): update version to ${tag} by npm-assistor\' && git push`; 122 | console.log(`>>> Exec ${command}`); 123 | childProcess.exec(command, function(err, stdout, stderr){ 124 | if(!err){ 125 | stderr = null; 126 | } 127 | callback(stderr, stdout); 128 | }); 129 | } 130 | } 131 | 132 | function _versionValidate(oldVersion, newVersion){ 133 | if(oldVersion === newVersion){ 134 | return { 135 | pass: false, 136 | message: '新老版本号不能相同' 137 | }; 138 | } 139 | 140 | let regExp = /^(\d+)\.(\d+)\.(\d+)$/; 141 | let oldVersionArr = regExp.exec(oldVersion).slice(1).map( item => +item ); 142 | let newVersionArr = regExp.exec(newVersion).slice(1).map( item => +item ); 143 | 144 | for(let i = 0, length = 3; i < length; i++){ 145 | if(oldVersionArr[i] > newVersionArr[i]){ 146 | return { 147 | pass: false, 148 | message: '新版本号不能低于老版本号' 149 | }; 150 | } 151 | if(oldVersionArr[i] < newVersionArr[i]){ 152 | for(i++; i < length; i++){ 153 | if(newVersionArr[i] !== 0){ 154 | return { 155 | pass: false, 156 | message: '递增位右侧位需要清零' 157 | }; 158 | } 159 | } 160 | break; 161 | } 162 | } 163 | 164 | return { 165 | pass: true, 166 | message: 'success' 167 | }; 168 | 169 | } 170 | 171 | exports.getCurentVersion = _getCurentVersion; 172 | exports.gitTagAdd = _gitTagAdd; 173 | exports.changePackage = _changePackage; 174 | exports.gitTagPush = _gitTagPush; 175 | exports.versionValidate = _versionValidate; 176 | exports.generateTag = generateTag; 177 | exports.generateTagHandInput = generateTagHandInput; 178 | -------------------------------------------------------------------------------- /command_tag.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * created at 2016.07.04 15:47:53 4 | * 5 | * Copyright (c) 2016 Souche.com, all rights 6 | * reserved. 7 | */ 8 | const async = require('async'); 9 | const colors = require('colors'); 10 | const moment = require('moment'); 11 | const inquirer = require('inquirer'); 12 | 13 | const promptMessage = colors.blue('git-tag-generate') + ': '; 14 | 15 | // 更新 week 设置 16 | moment.locale('zh-cn', { 17 | // 每年第一周的定义: 18 | // 国内:包含1月4号的那周为每年第一周 19 | // 美国:包含1月1号的那周为每年第一周(苹果日历也是如此) 20 | // 更新了下 moment,现在规则是 包含1月1号的那周为每年第一周,新的一周起始于周一(比较好理解,苹果日历也可设置) 21 | week : { 22 | dow : 1, // Monday is the first day of the week. 23 | doy : 7 // The week that contains Jan 1th is the first week of the year. 24 | } 25 | }); 26 | 27 | // 功能类库加载 28 | const commandAdd = require('./lib/command/add.js'); 29 | 30 | function main(){ 31 | let currentVersion = ''; 32 | let currentVersionWithoutTag = ''; 33 | 34 | try{ 35 | currentVersion = commandAdd.getCurentVersion(); 36 | let currentVersionArr = currentVersion.split('+'); 37 | 38 | currentVersionWithoutTag = currentVersionArr[0]; 39 | 40 | console.log('!!!!!!!!!!! '.rainbow + `当前版本: ${currentVersion.white}` + ' !!!!!!!!!!!'.rainbow); 41 | }catch(e){ 42 | console.log(`当前目录:${process.cwd()},不存在 package.json 文件,请到 package.json 文件所在目录执行命令`.red); 43 | process.exit(1); 44 | } 45 | 46 | let versionNextSuggest = { 47 | major: commandAdd.generateTag({ 48 | version: currentVersionWithoutTag, 49 | part: 'major' 50 | }), 51 | feature: commandAdd.generateTag({ 52 | version: currentVersionWithoutTag, 53 | part: 'feature' 54 | }), 55 | patch: commandAdd.generateTag({ 56 | version: currentVersionWithoutTag, 57 | part: 'patch' 58 | }) 59 | }; 60 | 61 | let schema = [{ 62 | type: 'list', 63 | name: 'version', 64 | message: promptMessage + colors.gray('semver 规范的版本号:'), 65 | default: versionNextSuggest.patch, 66 | choices: [ 67 | { 68 | short: '自定义', 69 | name: '自定义\n' 70 | + colors.gray(' - 格式如 ${major}.${feature}.${patch}(请遵循 semver 规范)'), 71 | value: false 72 | }, 73 | { 74 | short: versionNextSuggest.patch, 75 | name: 'patch (' + versionNextSuggest.patch + ')\n' 76 | + colors.gray(' - 递增修订版本号(用于 bug 修复)'), 77 | value: versionNextSuggest.patch 78 | }, 79 | { 80 | short: versionNextSuggest.feature, 81 | name: 'feature (' + versionNextSuggest.feature + ')\n' 82 | + colors.gray(' - 递增特性版本号(用于向下兼容的特性新增, 递增位的右侧位需要清零)'), 83 | value: versionNextSuggest.feature 84 | }, 85 | { 86 | short: versionNextSuggest.major, 87 | name: 'major (' + versionNextSuggest.major + ')\n' 88 | + colors.gray(' - 递增主版本号 (用于断代更新或大版本发布,递增位的右侧位需要清零)'), 89 | value: versionNextSuggest.major 90 | } 91 | ] 92 | }]; 93 | 94 | inquirer.prompt(schema).then(function (result) { 95 | if (!result.version) { 96 | reVersion(); 97 | } else { 98 | next(result.version); 99 | } 100 | }); 101 | 102 | function reVersion() { 103 | let schema = [{ 104 | type: 'input', 105 | name: 'version', 106 | message: promptMessage + 'semver 规范的版本号', 107 | validate: function(value){ 108 | if(!/^\d+\.\d+\.\d+$/.test(value)){ 109 | return '[X] 格式如 ${major}.${feature}.${patch} (请遵循 semver 规范)'.red; 110 | } 111 | 112 | let res = commandAdd.versionValidate(currentVersionWithoutTag, value); 113 | if(res.pass){ 114 | return true; 115 | }else{ 116 | return '[X] '.red + res.message.red; 117 | } 118 | } 119 | }]; 120 | 121 | inquirer.prompt(schema).then(function(result){ 122 | next(result.version); 123 | }); 124 | } 125 | 126 | function next (version) { 127 | let schema = [{ 128 | type: 'confirm', 129 | name: 'isNeedPublishTimesTag', 130 | message: promptMessage + '是否添加发布次数 tag (格式如 ${year}w${weeks}${[a-z]本周第几次发布})', 131 | default: false 132 | }]; 133 | 134 | inquirer.prompt(schema).then(function(result){ 135 | let newTag = commandAdd.generateTagHandInput({ 136 | version: version, 137 | isNeedPublishTimesTag: result.isNeedPublishTimesTag 138 | }); 139 | tagConfirm(newTag); 140 | }); 141 | } 142 | 143 | // tag 确认 144 | function tagConfirm(newTag){ 145 | console.log('!!!!!!!!!!! '.rainbow + `新版 tag: ${newTag.white}` + ' !!!!!!!!!!!'.rainbow); 146 | async.waterfall([ 147 | function(callback){ 148 | let schema = [{ 149 | type: 'confirm', 150 | name: 'confirm', 151 | message: promptMessage + '是否更改 package.json 文件的 version 信息', 152 | default: true 153 | }]; 154 | 155 | inquirer.prompt(schema).then(function(result){ 156 | if(result.confirm){ 157 | try{ 158 | commandAdd.changePackage(newTag, function(err){ 159 | if(err){ 160 | console.log(err.red); 161 | }else{ 162 | console.log('>>> package.json 更改成功'.green); 163 | } 164 | return callback(err); 165 | }); 166 | }catch(e){ 167 | console.log(e.message.red); 168 | return callback(e); 169 | } 170 | }else{ 171 | return callback(null); 172 | } 173 | }); 174 | }, 175 | function(callback){ 176 | let schema = [{ 177 | type: 'confirm', 178 | name: 'confirm', 179 | message: promptMessage + '是否执行 git tag add 命令', 180 | default: true 181 | }]; 182 | 183 | inquirer.prompt(schema).then(function(result){ 184 | return callback(null, result); 185 | }); 186 | }, 187 | function(result, callback){ 188 | if(result.confirm){ 189 | let schema = [{ 190 | type: 'input', 191 | name: 'message', 192 | message: promptMessage + 'tag 描述信息', 193 | validate: function(value){ 194 | if(!value){ 195 | return 'tag 描述信息不能为空'; 196 | } 197 | return true; 198 | } 199 | }]; 200 | 201 | inquirer.prompt(schema).then(function(result){ 202 | commandAdd.gitTagAdd(newTag, result.message, function(err){ 203 | if(err){ 204 | console.error(err.red); 205 | }else{ 206 | console.log('>>> git tag 添加成功!'.green); 207 | } 208 | return callback(err, true); 209 | }); 210 | }); 211 | 212 | }else{ 213 | return callback(null, false); 214 | } 215 | }, 216 | function(res, callback){ 217 | if(!res){ 218 | return callback(null); 219 | } 220 | 221 | let schema = [{ 222 | type: 'confirm', 223 | name: 'confirm', 224 | message: promptMessage + '是否 push tag 到远端', 225 | default: true 226 | }]; 227 | 228 | inquirer.prompt(schema).then(function(result){ 229 | if(result.confirm){ 230 | commandAdd.gitTagPush(newTag, function(err){ 231 | if(err){ 232 | console.error(err.red); 233 | }else{ 234 | console.log('>>> tag 成功推送到远端!'.green); 235 | } 236 | return callback(err); 237 | }); 238 | }else{ 239 | return callback(null); 240 | } 241 | }); 242 | } 243 | ], function(err){ 244 | if(!err){ 245 | console.log('Good Job!'.white); 246 | }else{ 247 | console.error('Oops~~~'.red); 248 | } 249 | }); 250 | } 251 | } 252 | 253 | module.exports = main; 254 | --------------------------------------------------------------------------------