如何打造一个属于自己的命令行工具

it2024-03-12  58

平常经常使用一些npm cli工具,你是否好奇这些工具又是怎么开发出来的呢?接下来这篇文章,就会介绍如何利用Node.js开发一个属于你自己的命令行工具。

创建基础的文件目录

首先,我们需要先创建一个基本的项目结构:

mkdir git-repo-cli cd git-repo-cli npm init #初始化项目

接着我们创建所需的文件:

touch index.js mkdir lib cd lib touch files.js touch github_credentials.js touch inquirer.js touch create_a_repo.js

接着我们先来写一个简单的入口程序,在index.js文件中代码如下:

const chalk = require('chalk'); const clear = require('clear'); const figlet = require('figlet'); const commander = require('commander'); commander .command('init') .description('Hello world') .action(() => { clear(); console.log(chalk.magenta(figlet.textSync('Git Repo Cli', { hosrizontalLayout: 'full' }))); }); commander.parse(process.argv); if (!commander.args.length) { commander.help(); }

上面的代码中引用了chalk,clear,figlet和commander这几个npm库,,其中chalk负责命令行不同颜色文本的显示,便于用户使用。clear库负责清空命令行界面,figlet可以在命令行中以ASCII ART形式显示文本。最后commander库就是用来实现命令行接口最主要的库。

写完代码后,我们可以在命令行中输入如下代码启动:

node index.js init

接着我们就可以看到这样的结果:

功能模块

文件模块

把基本的架子搭起来后,我们就可以开始写功能模块的代码了。

在lib/files.js文件中,主要要实现以下两个功能::

获取当前文件目录名检查该路径是否已经是个git仓库 const fs = require('fs'); const path = require('path'); module.exports = { getCurrentDirectoryBase: () => path.basename(process.cwd()), directoryExists: (filePath) => { try { return fs.statSync(filePath).isDirectory(); } catch (err) { return false; } }, isGitRepository: () => { if (files.directoryExists('.git')) { console.log(chalk.red("Sorry! Can't create a new git repo because this directory has been existed")) process.exit(); } } };

询问模块

在用户在执行命令行工具的时候,需要收集一些变量信息,因此,可以我们需要利用inquirer这个npm库来实现“询问模块”。

const inquirer = require('inquirer'); module.exports = { askGithubCredentials: () => { const questions = [ { name: "username", type: 'input', message: 'Enter your Github username or e-mail address:', validate: function(value) { if (value.length) { return true; } else { return 'Please enter your Github username:' } } }, { name: "password", type: 'password', message: 'Enter your password:', validate: function(value) { if (value.length) { return true; } else { return 'Please enter your Github username:' } } } ]; return inquirer.prompt(questions); } }

github认证

为了实现和github的接口通信,需要获得token认证信息。因此,在lib/github_credentials.js文件中,我们获得token,并借助configstore库写入package.json文件中。

const Configstore = require('configstore'); const pkg = require('../package.json') const octokit = require('@octokit/rest')(); const _ = require('lodash'); const inquirer = require("./inquirer"); const conf = new Configstore(pkg.name); module.exports = { getInstance: () => { return octokit; }, githubAuth: (token) => { octokit.authenticate({ type: 'oauth', token: token }); }, getStoredGithubToken: () => { return conf.get('github_credentials.token'); }, setGithubCrendeitals: async () => { const credentials = await inquirer.askGithubCredentials(); octokit.authenticate( _.extend({ type: 'basic' }, credentials) ) }, registerNewToken: async () => { // 该方法可能会被弃用,可以手动在github设置页面设置新的token try { const response = await octokit.oauthAuthorizations.createAuthorization({ scope: ['user', 'public_repo', 'repo', 'repo:status'], note: 'git-repo-cli: register new token' }); const token = response.data.token; if (token) { conf.set('github_credentials.token', token); return token; } else { throw new Error('Missing Token', 'Can not retrive token') } } catch(error) { throw error; } } }

其中@octokit/rest是node端与github通信主要的库。

接下来就可以写我们的接口了:

// index.js const github = require('./lib/gitub_credentials'); commander. command('check-token') .description('Check user Github credentials') .action(async () => { let token = github.getStoredGithubToken(); if (!token) { await github.setGithubCredentials(); token = await github.registryNewToken(); } console.log(token); });

最后,在命令行中输入如下命令:

node index.js check-token

它会先会在configstore的默认文件夹下~/.config寻找token, 如果没有发现的话,就会提示用户输入用户名和密码后新建一场新的token。

有了token后,就可以执行github的很多的操作,我们以新建仓库为例:

首先,先在inquirer.js中新建askRepositoryDetails用来获取相关的repo信息:

askRepositoryDetails: () => { const args = require('minimist')(process.argv.slice(2)); const questions = [ { type: 'input', name: 'name', message: 'Please enter a name for your repository:', default: args._[1] || files.getCurrentDirectoryBase(), validate: function(value) { if (value.length) { return true; } else { return 'Please enter a unique name for the repository.' } } }, { type: 'input', name: 'description', default: args._[2] || null, message: 'Now enter description:' }, { type: 'input', name: 'visiblity', message: 'Please choose repo type', choices: ['public', 'private'], default: 'public' } ]; return inquirer.prompt(questions); }, askIgnoreFiles: (filelist) => { const questions = [{ type: 'checkbox', choices: filelist, message: 'Please choose ignore files' }]; return inquirer.prompt(questions); }

接着,实现对应的新建仓库、新建.gitignore文件等操作:

// create_a_repo.js const _ = require('lodash'); const fs = require('fs'); const git = require('simple-git')(); const inquirer = require('./inquirer'); const gh = require('./github_credentials'); module.exports = { createRemoteRepository: async () => { const github = gh.getInstance(); // 获取octokit实例 const answers = await inquirer.askRepositoryDetails(); const data = { name: answers.name, descriptions: answers.description, private: (answers.visibility === 'private') }; try { // 利用octokit 来新建仓库 const response = await github.repos.createForAuthenticatedUser(data); return response.data.ssh_url; } catch (error) { throw error; } }, createGitIgnore: async () => { const filelist = _.without(fs.readdirSync('.'), '.git', '.gitignore'); if (filelist.length) { const answers = await inquirer.askIgnoreFiles(filelist); if (answers.ignore.length) { fs.writeFileSync('.gitignore', answers.ignore.join('\n')); } else { touch('.gitnore'); } } else { touch('.gitignore'); } }, setupRepo: async (url) => { try { await git. init() .add('.gitignore') .add('./*') .commit('Initial commit') .addRemote('origin', url) .push('origin', 'master') return true; } catch (err) { throw err; } } }

最后,在index.js文件中新建一个create-repo的命令,执行整个流程。

// index.js commander .command('create-repo') .description('create a new repo') .action(async () => { try { const token = await github.getStoredGithubToken(); github.githubAuth(token); const url = await repo.createRemoteRepository(); await repo.createGitIgnore(); const complete = await repo.setupRepository(url); if (complete) { console.log(chalk.green('All done!')); } } catch (error) { if (error) { switch (error.status) { case 401: console.log('xxx'); break; } } } })

写完代码后,在命令行中执行如下命令即可:

node index.js create-repo

总结

总的来说,利用node.js来实现命令行工具还是比较简单的。目前有很多比较成熟的工具库,基本上常用的功能都能够实现。如果有需要自己造轮子,大家可以参考本文的实现思路  

最新回复(0)