关于前端:Node-系列-008-ShellJS

29次阅读

共计 10457 个字符,预计需要花费 27 分钟才能阅读完成。

一 目录

不折腾的前端,和咸鱼有什么区别

目录
一 目录
二 前言
三 Node 编写 bash 脚本的解决方案
四 编程前置
五 敞开端口
六 删除文件 / 文件夹
七 Git 操作
 7.1 工作中罕用 Git 指令
 7.2 切换分支
八 总结
九 参考文献

二 前言

明天 jsliang 在工作中又爽了一把,开明了 VIP 通道:

  1. 主动下载 Excel 文件
  2. 拷贝到指定目录
  3. 执行多语言导入操作
  4. 将导入的资源 git push 下来

尽管看上去粗略的操作是这样,没有什么可讲的。

然而在操作中的时候,可能就比拟繁琐了,例如单单导入资源后,进行的 git 操作:

  1. 增加暂存区:git add .
  2. 切换分支:git checkout -b <branch>
  3. 提交本地版本库:git commit -m "feat:「多语言」新资源 #master_0720"
  4. 提交近程分支:git push --set-upstream origin <branch>

当然,不仅仅是这个,你提交前必定还得校验下你的多语言资源增加完后,构建是否能顺利通过……


所以,叨了那么多,其实单纯是为了引出 bash 指令的操作。

在工作中,你可能会碰到:

  • Git 系列操作
  • 敞开被占用的零碎端口
  • 删除指定文件 / 文件夹等
  • ……

然而,对于这些操作你可能一时半会又遗记它的指令,或者它的指令太繁琐了,所以 jsliang 感觉将这些内容装起来,岂不省事?

三 Node 编写 bash 脚本的解决方案

其实对于这个解决方案,jsliang 还是嫌麻烦,所以间接上了 ShellJS

  • ShellJS – Unix shell commands for Node.js

如果小伙伴感觉这样间接上计划有点唐突,心愿有个参考比照,能够看:

  • Node.js 写 bash 脚本终极计划

作者比对了 Node 自带的 child_process API、ShellJSzx,最终采取了 zx 的计划。

当然,jsliang 工作中用的还是 ShellJS,不想那么累再摸索同类库了,所以就装置 ShellJS 吧~

  • 装置:npm i shelljs
  • 装置 TS 编译:npm i @types/shelljs -D

装置结束,开始折腾!

四 编程前置

为了让代码不显得那么又臭又长。

jsliang 写完文章后,通篇浏览 3 次以上,将下文中反复的代码和问题状况给整顿到这里来了,所以小伙伴们拷贝代码前,最初仔细阅读这一章节内容,托付啦~

warn! please read the following carefully

全文目录如下,小伙伴们记得提前新建好目录:

而后反复代码如下:

src/common/index.ts

import {inquirer} from '../base/inquirer';
import {Result} from '../base/interface';

// 零碎操作
import {sortCatalog} from '../base/file/sortDir';
import {deleteDir} from '../base/file/deleteDir';

// 多语言
import {downLoadExcel} from './language/download';
import {importLanguage} from './language/import';
import {exportLanguage} from './language/export';

// shell 操作
import {closePort} from '../base/shell/closePort';
import {gitCheckout} from '../base/shell/gitCheckout';

// 问题记录器
const answers = {
  q0: '',
  q1: '',
  q2: '',
  q3: '',
  q4: '',
  q5: '',
  q6: '',
  q7: '',
  q8: '',
};

const common = (): void => {
  // 问题路线:看 questionList.ts
  const questionList = [
    // q0
    {
      type: 'list',
      message: '请问须要什么服务?',
      choices: ['公共服务', '多语言'],
    },
    // q1
    {
      type: 'list',
      message: '以后公共服务有:',
      choices: ['文件排序', '敞开端口', '删除文件夹', 'Git 操作'],
    },
    // q2
    {
      type: 'input',
      message: '须要排序的文件夹为?(绝对路径)',
    },
    // q3
    {
      type: 'list',
      message: '请问多语言须要什么反对?',
      choices: [
        '下载多语言资源',
        '导入多语言资源',
        '导出多语言资源',
      ],
    },
    // q4
    {
      type: 'input',
      message: '资源下载地址(HTTP)?',
      default: 'https://www.kdocs.cn/l/sdwvJUKBzkK2',
    },
    // q5
    {
      type: 'input',
      message: '你须要敞开的端口是?',
    },
    // q6
    {
      type: 'input',
      message: '你须要删除的门路是?(全门路)',
    },
    // q7
    {
      type: 'list',
      message: '请问 Git 须要什么反对?',
      choices: [
        '切换分支',
        // More...
      ],
    },
    // q8
    {
      type: 'inupt',
      message: 'Git 分支名是?',
    },
  ];

  const answerList = [
    // q0 - 请问须要什么服务?async (result: Result, questions: any) => {
      answers.q0 = result.answer;
      switch (result.answer) {
        case '公共服务':
          questions[1]();
          break;
        case '多语言':
          questions[3]();
          break;
        default: break;
      }
    },
    // q1 - 以后公共服务有:async (result: Result, questions: any) => {
      answers.q1 = result.answer;
      switch (result.answer) {case '文件排序': questions[2](); break;
        case '敞开端口': questions[5](); break;
        case '删除文件夹': questions[6](); break;
        case 'Git 操作': questions[7](); break;
        default: break;
      }
    },
    // q2 - 须要排序的文件夹为?(绝对路径)async (result: Result, _questions: any, prompts: any) => {
      answers.q2 = result.answer;
      const sortResult = await sortCatalog(result.answer);
      if (sortResult) {console.log('排序胜利!');
        prompts.complete();}
    },
    // q3 - 请问多语言须要什么反对?async (result: Result, questions: any, prompts: any) => {
      answers.q3 = result.answer;
      switch (result.answer) {
        case '下载多语言资源':
        case '导入多语言资源':
          questions[4]();
          break;
        case '导出多语言资源':
          const exportResult = await exportLanguage();
          if (exportResult) {console.log('导出胜利!');
            prompts.complete();}
        default: break;
      }
    },
    // q4 - 资源下载地址(HTTP)?async (result: Result) => {
      answers.q4 = result.answer;
      const download = async (): Promise<any> => {const downloadResult = await downLoadExcel(result.answer);
        if (downloadResult) {console.log('下载胜利!');
          return true;
        }
      };
      switch (answers.q3) {
        case '下载多语言资源':
          await download();
          break;
        case '导入多语言资源':
          await download();
          const importResult = await importLanguage();
          if (importResult) {console.log('导入结束!');
          }
        default:
          break;
      }
    },
    // q5 - 你须要敞开的端口是?async (result: Result, _questions: any, prompts: any) => {
      answers.q5 = result.answer;
      const closeResult = await closePort(result.answer);
      if (closeResult) {console.log('敞开胜利');
        prompts.complete();}
    },
    // q6 - 你须要删除的门路是?(全门路)async (result: Result, _questions: any, prompts: any) => {
      answers.q6 = result.answer;
      const deleteResult = await deleteDir(result.answer);
      if (deleteResult) {console.log('删除胜利');
        prompts.complete();}
    },
    // q7 - 请问 Git 须要什么反对?async (result: Result, questions: any) => {
      answers.q7 = result.answer;
      questions[8]();},
    // q8 - Git 分支名是?async (result: Result, _questions: any, prompts: any) => {
      answers.q8 = result.answer;
      const checkoutResult = await gitCheckout(result.answer);
      if (checkoutResult) {console.log('切换胜利');
        prompts.complete();}
    },
  ];

  inquirer(questionList, answerList);
};

export default common;

src/common/questionList.ts

// common 板块的问题征询路线
export const questionList = {
  '公共服务': { // q0
    '文件排序': { // q1
      '须要排序的文件夹': 'Work 工作', // q2
    },
    '敞开端口': { // q1
      '须要敞开的端口': 'Work 工作', // q5
    },
    '删除文件夹': { // q1
      '须要删除的门路': 'Work 工作', // q6
    },
    'Git 操作': { // q1
      '切换分支': { // q7
        '分支名': 'Work 工作', // q8
      },
    },
  },
  '多语言': { // q0
    '下载多语言资源': { // q3
      '下载地址': 'Work 工作', // q4
    },
    '导入多语言资源': { // q3
      '下载地址': 'Work 工作', // q4
    },
    '导出多语言资源': { // q3
      '导出全量资源': 'Work 工作',
      '导出单门资源': 'Work 工作',
    }
  },
};

前面文章表明:

更新 src/common/index.ts

更新 src/common/questionList.ts

这种状况,小伙伴们就不必操作啦,本文有 3 个中央都有这种形容的。

同样,为了防止援用和运行代码的时候,报 TypeScript 谬误,新增的 3 个文件结尾也列一下:

src/base/file/deleteDir.ts

import shell from 'shelljs';

export const deleteDir = async (path: string): Promise<boolean> => {console.log(path)
};

src/base/shell/closePort.ts

import shell from 'shelljs';

export const closePort = async (port: string): Promise<boolean> => {console.log(port);
};

src/base/shell/gitCheckout.ts

import shell from 'shelljs';

/**
 * @name 切换分支
 * @description 指令合并:* 1. git checkout ${branch}
 * 2. git pull
 * @param {string} branch 分支名
 */
export const gitCheckout = (branch: string): boolean => {console.log(branch)
};

另外 common 下的 sortCatalog.ts 迁徙到 base/file 目录下,并且改名为 sortDir.ts 了。

那么,筹备结束,开始输入!

五 敞开端口

在起一些神奇的服务时,会碰到端口被占用的场景,这时候就须要敞开端口:

  • 查看端口占用状况:netstat -ano|findstr "端口号"
PS F:\xxx> netstat -ano|findstr "3001"
  TCP    0.0.0.0:3001           0.0.0.0:0              LISTENING       33396
  TCP    10.13.144.170:63001    183.2.199.241:443      ESTABLISHED     28228
  TCP    [::]:3001              [::]:0                 LISTENING       33396
  • 终止 PID:taskkill -F -PID PID 号
PS F:\xxx> taskkill -F -PID 33396
胜利: 已终止 PID 为 33396 的过程。

那么到了 Node 工具库这边,必定就不要本人去这样操作啦,搞个省事的形式吧:

更新 src/common/index.ts

更新 src/common/questionList.ts

而后编写 closePort.ts

src/base/shell/closePort.ts

import shell from 'shelljs';

export const closePort = async (port: string): Promise<boolean> => {await shell.exec(`netstat -ano | findstr :${port}`);

  // Windows 下会返回一个端口占用清单,须要自行删除
  console.log('已找到下面清单列表,请执行指令删除端口:taskkill -F -PID PID 号'); 

  return await true;
};

注:Windows 打印后果最开端的为 PID 号

当然,因为 3001 可能会有好几个 ip 对应的端口,所以前面那个步骤咱们仅做了提醒,而不是敞开了所有 3001 的端口(须要用户手动操作)。

然而这样总好过咱们去记忆这个指令(毕竟 Windows 和 Mac 等的操作指令还不通)

执行 npm run jsliang,运行后果如下:

这样咱们就封装好了敞开端口的,因为不是一键彻底敞开,实用指数给到 ☆☆☆

六 删除文件 / 文件夹

之前在公司为了钻研 Windows 如何疾速删除 node_modulesjsliang 还真翻了下材料找到 3 种删除文件 / 文件夹的形式:

  1. cmd.exerd /s /q <path>
  2. PowerShellrd -r <path>
  3. Macrm -rf <path>

通过屡次亲自体验,在公司中的 32G 内存,500 SSD 的台式电脑中,通过 PowerShell 的删除操作比 cmd.exe 的快(别问我为啥,反正就是快点,仅集体体验,不做迷信撑持)。

而后看了下 ShellJS,是有删除形式的:

  • ShellJS:rm() 删除文件,rm('rf', <path>) 删除文件夹

当然!奔着摸索精力,咱们看看它源码咋实现的:

  • GitHub:ShellJS – rm.js
function rmdirSyncRecursive(dir, force, fromSymlink) {
  
  // 1. 先删除目录中的所有文件
  let files = fs.readdirSync(dir);
  for (var i = 0; i < files.length; i++) {// 1.1 如果是目录则递归调用 rmdirSyncRecursive()
    // 1.2 如果是文件则调用 fs.unlinkSync() 执行删除}

  // 2. 再删除目录
  fs.rmdirSync();}

当然,外面有些细节还是写得不错的,这里就不开展具体解说,感兴趣的小伙伴能够点击下面链接摸索下源码。

所以,咱们就用 ShellJS 的办法吧!如果前面感觉不难受再替换为零碎指令。

更新 src/common/index.ts

更新 src/common/questionList.ts

而后开始编写 deleteDir.ts

src/base/file/deleteDir.ts

import shell from 'shelljs';

export const deleteDir = async (path: string): Promise<boolean> => {
  /**
   * cmd.exe:rd /s /q <path>
   * PowerShell:rd -r <path>
   * Mac:rm -rf <path>
   * ShellJS:rm() 删除文件,rm('rf', <path>) 删除文件夹
   */
  await shell.rm('-rf', path);
  return true;
};

执行 npm run jsliang,打印内容如下:

搞定,出工!因为不分明 Node 操作和零碎指令哪个比拟快,所以暂定实用指数 ☆☆☆

七 Git 操作

那么最初,来到重头 Git 操作。

想必有些小伙伴会跟 jsliang 一样懒?

  • git add .
  • git commit -m "xxx"
  • git push

曾经到了麻痹的状态了,jsliang 甚至开发了特定的 VS Code 插件:

注:VS Code 也卷,更新太快了我插件承受不了(肯定水平上最新 VS Code 用不了该插件),所以我的 VS Code 版本锁死 v1.53.2,啥时候有空操作再更新这个插件了

在 VS Code 插件中,进行疾速的提交操作。

所以在一些惯例的 Git 操作咱们还是心愿能封装起来(不须要记指令,也不想点页面,让它自行跑起来吧)

7.1 工作中罕用 Git 指令

jsliang 在工作中常常应用,并记住了的对应指令:

  • git pull:拉取代码并主动合并,jsliang 这边还会用 git pull --rebase origin master,表明拉取近程分支并基于该分支进行改变
  • git checkout -b <newBranch>:从以后分支切新分支
  • git branch -D <branch>:依据分支名删除指定分支
  • git add <files>:提交到暂存区
  • git commit <description>:提交到本地版本库。如果你们仓库有 eslint 查看之类的,强烈推荐 git commit -m "xxx" --no-verify(有时候真不想搞啥查看)
  • git push:提交到近程库。个别新分支操作为 git push -- set upstream origin <branch>
  • git cherry-pick <commitHash>:将指定的提交(commit)利用于其余分支
  • git stash:暂存内容。将暂存区的内容存储到栈中(屡次 stash 能够通过屡次 pop 推出来)
  • git stash pop:签出内容。将 git stash 中的内容推出来
  • git reset --soft HEAD^:回退版本并保留内容。这个 HEAD^ 是指上一个版本,也能够写成 HEAD~1(就是 commit id)

值得一提的是,jsliang 之前还尝试用过:git worktree,它能够同时批改多个版本。

然而因为嫌麻烦(要记指令),所以就没用了

同一个 Git 仓库,须要同时批改多个分支,或者须要在 A 分支上参照 B 分支的内容进行批改。

当然这种状况能够用 git clone 拷贝一个新仓库,然而如果你的仓库有点大(几 G 那种),那还是有点嫌麻烦的。

于是就有了 git worktree,指令如下:

# 将 abc 分支切出到另一个目录 jsliang 中
# 留神:这个目录不能在主仓库中
git worktree add ../jsliang abc # git add [< 选项 >] < 门路 > [< 提交 >]

# 获取帮忙
git worktree -h

# 查看每个工作树的详细信息
git worktree list

# 更残缺的工作树信息
# git worktree list --porcelain

# 锁定内容,避免被主动删除
git worktree lock

# 解锁内容
git worktree unlock

# 迁徙到新目录
git worktree move abc ../jsliang2

# 删除某条工作树
git worktree remove ../jsliang

# 革除工作树信息
git worktree prune

罕用 git worktree 指令:

  • 切出分支:git worktree add ../jsliang abc
  • 罕用操作:git add .git commit -m "xxx"git push
  • 敞开分支:git worktree prune

当然,还有 Git 设置代理

迷信上网状况下,有时候 Git 并没有失效,克隆或者 push 操作一样卡慢,就须要设置 Git 代理。

  • 设置代理
  1. git config --global http.proxy 代理地址
  2. git config --global https.proxy 代理地址
  • 勾销代理
  1. git config --global --unset http.proxy
  2. git config --global --unset https.proxy
  • 查看曾经设置的代理
  1. git config --global --get http.proxy
  2. git config --global --get https.proxy

我拿当初用的迷信上网代理软件,就设置了 git config --global http.proxy http://127.0.0.1:10809,Git 晦涩度晋升了挺多。

7.2 切换分支

吧啦吧啦说了一通,上面开始干点正活,咱们搞个简略的切换分支:

更新 src/common/index.ts

更新 src/common/questionList.ts

接着更新下 gitCheckout.ts

src/base/shell/gitCheckout.ts

import shell from 'shelljs';

/**
 * @name 切换分支
 * @description 指令合并:* 1. git checkout ${branch}
 * 2. git pull
 * @param {string} branch 分支名
 */
export const gitCheckout = (branch: string): boolean => {if (!shell.which('git')) {shell.echo('Error: 请先装置 Git!');
    shell.exit(1);
  }

  console.log('开始切换分支:');
  const checkoutResult = shell.exec(`git checkout ${branch}`);
  if (checkoutResult.code !== 0) {shell.echo('Error: 切分支失败!');
    shell.exit(1);
  }

  console.log('开始拉取代码,请稍等:');
  const pullResult = shell.exec('git pull');
  const {stdout, stderr} = pullResult;
  if (pullResult.code === 0) {if (stdout.includes('from the remote, but no such ref was fetched.')) {console.log('你的分支是最新的');
    } 
  } else if (pullResult.code === 1) {if (stderr.includes('There is no tracking information for the current branch.')) {console.log('不存在近程分支');
    }
  }

  return true;
};

而后运行:npm run jsliang,跟着指令输出分支名:

此时因为是咱们轻易输出的一个分支,所以它表明切换分支失败了。

当然,小伙伴们能够根据实在业务往里面装载更多的内容,这里就不开展具体解说,仅做抛砖引玉先吧~

八 总结

其实最近两天还有在通过汇总 bash 指令来压缩一些工作流程的事,然而碍于一时半会想不到更好的我的项目例子,所以就没有一一铺开解说,所以小伙伴们碰到这种状况,欢送来吐槽评论留言,衷心示意心愿能和小伙伴们一起探讨。

那么,对于 ShellJS 咱们就铺垫那么一点了,前面 jsliang 在生存、工作中碰到一些乏味的知识点再增加进来吧~

So,下期再会!

九 参考文献

  • GitHub:ShellJS – Unix shell commands for Node.js
  • 掘金:👏 nodejs 写 bash 脚本终极计划!
  • GitHub:Git worktree 作用及应用
  • 简书:git worktree 的应用
  • 知乎:Git 屠龙技:应用 Git Worktree 并行开发测试
  • 政企云前端团队:我在工作中是如何应用 Git 的
  • SegmentFault:Git 屠龙技:应用 Git Worktree 并行开发测试
  • 何方的编程之路:Git 如何应用代理 (VPN)
  • 阮一峰:cherry-pick

jsliang 的文档库由 梁峻荣 采纳 常识共享 署名 - 非商业性应用 - 雷同形式共享 4.0 国内 许可协定 进行许可。<br/> 基于 https://github.com/LiangJunrong/document-library 上的作品创作。<br/> 本许可协定受权之外的应用权限能够从 [https://creativecommons.org/l…](https://creativecommons.org/l…

正文完
 0