前端脚手架系列(一):Node.js与 ls -al 模拟

前言

在现代前端开发中,脚手架工具扮演着至关重要的角色。它们不仅提高了开发效率,而且通过为项目提供标准化和一致的开发模式,使得团队协作变得更加无缝和高效。Node.js,作为一个强大的JavaScript运行环境,使得编写和使用这些脚手架工具成为可能。

目标:

  • 脚手架的基本原理即执行流程
  • 完成仿 ls -al 查询文件的创建脚手架开发流程

什么是脚手架?

  • 操作系统的可执行文件,可以通过c、c++、Java、JavaScript(Node.js)等各种语言编写
  • 通过控制台命令即可执行的逻辑
  • 前端脚手架是一种工具或框架,主要用于自动化前端项目的设置和配置流程,以便开发者能够迅速启动并运行新的项目,而无需从头开始配置环境、编写样板代码或设置常见的项目结构。

脚手架执行流程

在创建vue 或者React项目时,通过官网提供的脚手架即可快速的搭建项目,很好奇,为什么在控制台输入简单的命令即可快速生成呢?

我们可以通过查看 Vue 的脚手架方式来一探究竟

这里需要理解几个基本概念

  • 环境变量(相当于操作系统级别的全局变量)

  • 软连接(相当于windows系统的快捷方式)

  • 这里 vue、which 本质都是脚手架

  • 通过 vim 打开文件,就是vue脚手架的执行文件

其中!user/bin/env node 是将当前文件在node环境中跑

脚手架执行原理如下:

  • 在终端输入 vue-create-app
  • 终端解析出 vue 命令
  • 终端在环境变量中找到 vue 命令
  • 终端根据 vue 命令链接到实际 vue.js
  • 终端利用 node 执行 vue.js
  • vue.js 解析 command / options
  • vue.js 执行command
  • 执行完毕,退出执行

脚手架开发入门

脚手架开发流程

创建 npm 项目

javascript 复制代码
npm init -y

创建 /bin/index.js 脚手架入口文件,最上方添加:操作系统按照node来解析文件

javascript 复制代码
#!/usr/bin/env node
console.log("我们来开发脚手架!");

配置 package.json ,添加 bin 属性

javascript 复制代码
{
  // 指定名称
  "name": "tianyu.ls",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  // bin 指定 名称 以及 文件路径 在index.js中即可编写脚手架代码
  "bin":{
    "tianyu-ls":"./bin/index.js"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

创建软连接

  1. 首先需要 运行 npm link 将此文件的路径添加到全部变量,也就是对全局进行一个开发测试
javascript 复制代码
npm link
  1. 执行 ~ which node 命令 获取node所在地址
  1. 进入到上一级 bin目录,并 通过 ll 命令 查看当前文件下所有的文件,当前创建的文件也就在这里,后面的地址就是指向实际的路径
  1. 此时就可以直接通过 bin中设置的软链接进行本地任意文件访问,如果其他电脑需要访问,还需要npm进行发布

在项目中访问

在桌面也可以进行访问

编写脚手架代码

在开始之前,如果对 ls 以及 Unix文件权限不了解的可以先链接至

认识 Unix(Linux、MacOS)文件权限系统及文件类型

有个简单的概念后再进行编写 查看文件功能

目标:

tianyu-a tianyu-a-l tianyu-al 的方式 实现系统 ls 查看文件功能

以下是系统ls使用方式 输入ls 将所有的文件进行依次展示

javascript 复制代码
➜  tianyu.ls git:(main) ✗ ls
bin          package.json

输入 ls-l 以列表方式进行展示

javascript 复制代码
➜  tianyu.ls git:(main) ✗ ls -l 
total 8
drwxr-xr-x  3 wangtianyu  staff   96  4 11 21:20 bin
-rw-r--r--  1 wangtianyu  staff  271  4 11 21:20 package.json

输入 ls-a 显示所有文件

javascript 复制代码
➜  tianyu.ls git:(main) ✗ ls -a 
.            ..           bin          package.json

输入 ls -a-l 会将列和文件同时展示,也就是相当于 同时输入了 ls-l ls-a 这一块的含义 链接:认识 Unix(Linux、MacOS)文件权限系统及文件类型

javascript 复制代码
➜  tianyu.ls git:(main) ✗ ls -a -l
total 8
drwxr-xr-x  4 wangtianyu  staff  128  4 11 21:20 .
drwxr-xr-x  4 wangtianyu  staff  128  4 11 21:19 ..
drwxr-xr-x  3 wangtianyu  staff   96  4 11 21:20 bin
-rw-r--r--  1 wangtianyu  staff  271  4 11 21:20 package.json

输入 ls -al 等价于输入了 ls-a-l

javascript 复制代码
➜  tianyu.ls git:(main) ✗ ls -al  
total 8
drwxr-xr-x  4 wangtianyu  staff  128  4 11 21:20 .
drwxr-xr-x  4 wangtianyu  staff  128  4 11 21:19 ..
drwxr-xr-x  3 wangtianyu  staff   96  4 11 21:20 bin

获取控制台输入的参数 process.argv

在创建vue项目的时候 是输入 vue create app 同时输入了三个命令,这个demo也是类似 有-a -l,那么应该应该如何获取呢? 使用node.js中的 **process.argv **

javascript 复制代码
console.log(process.argv);
javascript 复制代码
➜  tianyu.ls git:(main) ✗ tianyu-ls 
[
  '/Users/wangtianyu/.nvm/versions/node/v19.9.0/bin/node',
  '/Users/wangtianyu/.nvm/versions/node/v19.9.0/bin/tianyu-ls'
]

如果 输入 ls-a-l

javascript 复制代码
➜  tianyu.ls git:(main) ✗ tianyu-ls -a -l
[
  '/Users/wangtianyu/.nvm/versions/node/v19.9.0/bin/node',
  '/Users/wangtianyu/.nvm/versions/node/v19.9.0/bin/tianyu-ls',
  '-a',
  '-l'
]

定义parseArgs函数

👆🏻已经获取到参数,那么接下来就可以判断,哪一个输入哪一个没有输入来进行具体的操作

javascript 复制代码
function parseArgs() {
  let isAll = false;
  let isList = false;

  const args = process.argv.slice(2);
  args.forEach((arg) => {
    console.log(arg, "66");
    if (arg.includes("a")) {
      isAll = true;
    }
    if (arg.includes("l")) {
      isList = true;
    }
  });

  return {
    isAll,
    isList,
  };
}

module.exports = parseArgs;
javascript 复制代码
#!/usr/bin/env node
const parseArgs = require("./parseArgs");
const { isAll, isList } = parseArgs();

console.log({
  isAll,
  isList,
});

控制台看下打印尝试各种情况均正常

javascript 复制代码
➜  tianyu.ls git:(main) ✗ tianyu-ls -l 
-l 66
{ isAll: false, isList: true }
➜  tianyu.ls git:(main) ✗ tianyu-ls -a -l
-a 66
-l 66
{ isAll: true, isList: true }
➜  tianyu.ls git:(main) ✗ tianyu-ls -al  
-al 66
{ isAll: true, isList: true }
➜  tianyu.ls git:(main) ✗ tianyu-ls -a 
-a 66
{ isAll: true, isList: false }
➜  tianyu.ls git:(main) ✗ 

完成只输入ls的情况 获取所有的文件 但是不包含隐藏文件

  • 也就是 isAll and is isList 都是false的情况,并排除.开头的文件,也就是隐藏文件
  • process.cwd() 获取当前工作目录文件夹
  • fs.readdirSync(dir) 当前文件夹下的所有文件,并返回一个文件名数组

实现代码

javascript 复制代码
#!/usr/bin/env node
const fs = require("fs");
const parseArgs = require("./parseArgs");
const { isAll, isList } = parseArgs();

// 当前工作目录:
const dir = process.cwd(); // 获取当前工作目录,并赋值给dir

// 没有输入 -a -l 时
if (!isAll && !isList) {
  // 遍历当前文件夹下的所有文件,排除以.开头的文件或文件夹
  let files = fs.readdirSync(dir);
  console.log(files, "files");
  files = files.filter((file) => !file.startsWith("."));
  let output = "";
  // 遍历所有文件
  files.forEach((file) => (output += file + "      "));
  console.log(output);
}

console.log({
  isAll,
  isList,
});

尝试获取项目文件目录

javascript 复制代码
➜  tianyu.ls git:(main) ✗ tianyu-ls
[ 'bin', 'package.json' ] files
bin      package.json      
{ isAll: false, isList: false }

完成 ls -a 的情况,获取所有文件包含隐藏文件

  • 在目录下创建一个以 · 开头的文件 例如 .log
javascript 复制代码
#!/usr/bin/env node
const fs = require("fs");
const parseArgs = require("./parseArgs");
// console.log("我们来开发脚手架!");
// console.log(process.argv);
const { isAll, isList } = parseArgs();

// 当前工作目录:
const dir = process.cwd(); // 获取当前工作目录,并赋值给dir

let files = fs.readdirSync(dir);
let output = "";

// 没有输入 -a -l 时 获取当前文件夹下的所有文件排除以.开头的文件或文件夹(隐藏文件)
if (!isAll && !isList) {
  // 遍历当前文件夹下的所有文件,排除以.开头的文件或文件夹
  files = files.filter((file) => !file.startsWith("."));
  // 遍历所有文件
  files.forEach((file) => (output += file + "      "));
}
// 输入 -a 时 获取所有文件
else if (isAll && !isList) {
  files.forEach((file) => (output += file + "      "));
}

console.log(output);

console.log({
  isAll,
  isList,
});

尝试获取项目的文件 创建的 .log 就可以被打印出来了

javascript 复制代码
➜  tianyu.ls git:(main) ✗ tianyu-ls  -a
-a 66
.log      bin      package.json      
{ isAll: true, isList: false }

同终端测试其他文件夹也是可以的,例如我的桌面

javascript 复制代码
 ~ tianyu-ls -a
.CFUserTextEncoding      .DS_Store      .Huawei      .SwitchHosts      .Trash      .WhistleAppData      .altrc      .android      .aws      .bash_history      .bash_prefile      .bash_prefile.swp      .bash_sessions      .bytertc      .cache      .chat      .config      .degit      .gitconfig      .hvigor      .lesshst      .lingma      .local      .node_repl_history      .npm      .npmrc      .nvm      .oh-my-zsh      .ohpm      .pm2      .pnpm-state      .snipaste      .sogouinput      .ssh      .startingAppData      .swp      .viminfo      .vscode      .vuerc      .yarn      .yarnrc      .zcompdump      .zcompdump-wangtianyu-5.9      .zcompdump-wangtianyu-5.9.zwc      .zprofile      .zsh_history      .zsh_sessions      .zshrc      .zshrc.pre-oh-my-zsh      .zshrc.pre-oh-my-zsh-2023-11-19_02-34-30      .zshrc.save      .zshrc.swl      .zshrc.swm      .zshrc.swn      .zshrc.swo      .zshrc.swp      Applications      Desktop      Documents      Downloads      Library      Movies      Music      Pictures      Postman      Public      Sunlogin Files      \      debug-test      localhost-key.pem      localhost.pem      package-lock.json      yarn-error.log      yarn.lock
{ isAll: true, isList: false }

完成 ls -a -l 的情况,获取文件列表包含权限创建时间等

javascript 复制代码
#!/usr/bin/env node
const fs = require("fs");
const parseArgs = require("./parseArgs");
// console.log("我们来开发脚手架!");
// console.log(process.argv);
const { isAll, isList } = parseArgs();

// 当前工作目录:
const dir = process.cwd(); // 获取当前工作目录,并赋值给dir

let files = fs.readdirSync(dir);
let output = "";

// 没有输入 -a -l 时 获取当前文件夹下的所有文件排除以.开头的文件或文件夹(隐藏文件)
if (!isAll) {
  files = files.filter((file) => !file.startsWith("."));
}
// ls -a的情况 获取当前文件夹下的所有文件包含隐藏文件
if (!isList) {
  files.forEach((file) => (output += file + "      "));
}

// ls -a -l的情况 已列表形式展示当前文件夹下的所有文件包含隐藏文件
else {
  const total = `total ${files.length}` + "\n";
  files.forEach((file, index) => {
    // 这里是优化最后一行不带空行的逻辑
    index === files.length - 1 ? (output += file) : (output += file + "\n");
  });
  // 第一行显示文件总数量
  output = total + output;
}

console.log(output);

console.log({
  isAll,
  isList,
});

测试

javascript 复制代码
➜  tianyu.ls git:(main) ✗ tianyu-ls  -a -l
total 3
.log
bin
package.json
{ isAll: true, isList: true }

➜  tianyu.ls git:(main) ✗ tianyu-ls  -al  
total 3
.log
bin
package.json
{ isAll: true, isList: true }

比较系统ls -al 去烧了文件权限等信息,所以下一步就是Nodejs如何获取系统文件类型和文件权限

javascript 复制代码
 tianyu.ls git:(main) ✗ ls -a -l
// 文件数量
total 8
// 权限  //数量 // 用户名  // 分组 //大小 // 最新修改日期
drwxr-xr-x  5 wangtianyu  staff  160  4 13 12:29 .
drwxr-xr-x  4 wangtianyu  staff  128  4 11 21:19 ..
-rw-r--r--  1 wangtianyu  staff    0  4 13 12:29 .log
drwxr-xr-x  4 wangtianyu  staff  128  4 11 21:59 bin

理解文件存储的表达方式

javascript 复制代码
理解文件存储的表达方式
unix使用32位二进制数存储文件类型和权限
0000 0000 0000 0000
0000(文件类型) 000(特殊权限) 000(用户权限) 000(组权限) 000(其他权限)

0
1
10
11
110
111
1110
1111

当前类型位:0001 如何判断最后一位是1呢?
0001 & 0001 = 0001 true
0000 & 0001  = 0000 false
0000 & 0010 = 0000 false
因为 只要带0 结果都是false 所以所有带0的都要删除

所以最终能够表达状态的只有
1
11
111
1111

所以 例如 用户权限这里 001 010 100 都可以代表一种权限

在nodejs中 可以通过 fs.statSync(file) 的方式获取

javascript 复制代码
else {
  const total = `total ${files.length}` + "\n";
  files.forEach((file, index) => {
    const mode = fs.statSync(file);
    console.log(mode);
    index === files.length - 1 ? (output += file) : (output += file + "\n");
  });
  output = total + output;
}
// 结果
Stats {
  dev: 16777234,
  mode: 33188, // 文件十进制 
  nlink: 1,
  uid: 501,
  gid: 20,
  rdev: 0,
  blksize: 4096,
  ino: 22456782,
  size: 0,
  blocks: 0,
  atimeMs: 1712982550363.2869,
  mtimeMs: 1712982550363.2869,
  ctimeMs: 1712982550363.2869,
  birthtimeMs: 1712982550363.2869,
  atime: 2024-04-13T04:29:10.363Z,
  mtime: 2024-04-13T04:29:10.363Z,
  ctime: 2024-04-13T04:29:10.363Z,
  birthtime: 2024-04-13T04:29:10.363Z
}

**可以通过利用网页工具将十进制转化成二进制 ** f16877 =》 0100 0001 1110 1101 那么0100 就是对应表格中的 文件夹

判断文件类型

判断是否为文件夹

javascript 复制代码
files.forEach((file, index) => {
    const stat = fs.statSync(file); // 文件信息对象
    const mode = stat.mode; // 文件二进制
    const isDirectory = stat.isDirectory(); // 是否是文件夹
    console.log(mode, isDirectory);
    index === files.length - 1 ? (output += file) : (output += file + "\n");
  });
javascript 复制代码
=> tianyu.ls git:(main) ✗ tianyu-ls  -al
33188 false 
16877 true
33188 false
total 3
.log
bin
package.json
{ isAll: true, isList: true }

这里 true 就代表是文件夹 false即不是文件夹,那么isDirectory它是如何实现的呢?下面就是原理部分:

isDirectory原理

这里可以再进行优化 通过 fs.constants.S_IFDIR 获取文件的二进制

javascript 复制代码
 files.forEach((file, index) => {
    const stat = fs.statSync(file); // 文件信息对象
    const mode = stat.mode; // 文件二进制
    const isDirectory = stat.isDirectory(); // 是否是文件夹
    const isDir = mode & fs.constants.S_IFDIR; // mode 和 fs.constants.S_IFDIR是否相同 通过 & 来判断是否是文件夹
    console.log(mode, isDirectory, isDir, fs.constants.S_IFDIR);
    index === files.length - 1 ? (output += file) : (output += file + "\n");
  });

➜  tianyu.ls git:(main) ✗ tianyu-ls  -al
33188 false 0 16384
16877 true 16384 16384 
33188 false 0 16384
total 3
.log
bin
package.json
{ isAll: true, isList: true }

通过这里观察查看 isDir 如果大于0 就是一个文件夹

javascript 复制代码
files.forEach((file, index) => {
    const stat = fs.statSync(file); // 文件信息对象
    const mode = stat.mode; // 文件二进制
    const isDirectory = stat.isDirectory(); // 判断文件类型1:直接利用API方式判断是是文件夹
    const isDir = mode & fs.constants.S_IFDIR; // 判断文件类型2:mode 和 fs.constants.S_IFDIR是否相同 通过 & 来判断是否是文件夹
    console.log(mode, isDir > 0, fs.constants.S_IFDIR); 
    index === files.length - 1 ? (output += file) : (output += file + "\n");
  });

➜  tianyu.ls git:(main) ✗ tianyu-ls  -al
33188 false 16384 //  .log 
16877 true 16384 //  bin
33188 false 16384 // package.json
total 3
.log
bin
package.json
{{isAll: true, isList: true }

其中 bin 是一个文件夹 十进制是 16384 > 0

其余的都不是一个文件夹 ,下图中也是正确的 我们把 16384 转化成 二进制 : 100 0000 0000 0000

javascript 复制代码
bin       16877  0100 0001 1110 1101 
S_IFDIR   16384  0100 0000 0000 0000
// 因为 0100 都相同 那么说明 bin文件就是一个文件夹

以上就是判断文件夹的方式

判断文件类型

这里通过 mode & fs.constants.S_IFREG; // 是否是文件 来判断

javascript 复制代码
files.forEach((file, index) => {
    const stat = fs.statSync(file); // 文件信息对象
    const mode = stat.mode; // 文件二进制
    const isDirectory = stat.isDirectory(); // 判断文件类型1:直接利用API方式判断是是文件夹
    const isDir = mode & fs.constants.S_IFDIR; // 判断文件类型2:mode 和 fs.constants.S_IFDIR是否相同 通过 & 来判断是否是文件夹
    const isFile = mode & fs.constants.S_IFREG; // 是否是文件
    console.log(mode, isFile > 0, fs.constants.S_IFDIR);
    index === files.length - 1 ? (output += file) : (output += file + "\n");
  });

➜  tianyu.ls git:(main) ✗ tianyu-ls  -al
33188 true 16384  
16877 false 16384
33188 true 16384
total 3
.log
bin
package.json
{ isAll: true, isList: true }

所以 在 drwxr-xr-x 中 第一位 例如d 就可以通过这种方式判断出来了 通过 mode & fs.constants.对应的文件类型 > 0 来判断结果 这里再整理一下文件信息

javascript 复制代码
  files.forEach((file, index) => {
    const stat = fs.statSync(file); // 文件信息对象
    const mode = stat.mode; // 文件二进制
    const isDirectory = stat.isDirectory(); // 是否是文件夹
    const isDir = mode & fs.constants.S_IFDIR; // mode 和 fs.constants.S_IFDIR是否相同 通过 & 来判断是否是文件夹
    const isFile = mode & fs.constants.S_IFREG; // 是否是文件
    console.log({
      文件名称: file,
      文件十进制: mode,
      // 二进制这里手动将mode十进制到网站转化器中进行展示
      是否是文件夹: isDir > 0,
      是否是文件: isFile > 0,
    });
    index === files.length - 1 ? (output += file) : (output += file + "\n");
  });

结果 + 手动加上二进制 就是

javascript 复制代码
➜  tianyu.ls git:(main) ✗ tianyu-ls  -al
{ '文件名称': '.log', '文件十进制': 33188, '文件二进制':1000 0001 1010 0100,'是否是文件夹': false, '是否是文件': true }
{ '文件名称': 'bin', '文件十进制': 16877, '文件二进制':100 0001 1110 1101,'是否是文件夹': true, '是否是文件': false }
{ '文件名称': 'package.json', '文件二进制': 33188,'文件二进制':1000 0001 1010 0100,'是否是文件夹': false,'是否是文件': true}

总结:编写函数

mode & fs.constants.文件类型 是否为true

javascript 复制代码
const fs = require("fs");
function getFileType(mode) {
  let fileType = "";
  const isDir = mode & (fs.constants.S_IFDIR === fs.constants.S_IFDIR); // 是否是文件夹
  const isFile = mode & (fs.constants.S_IFREG === fs.constants.S_IFREG); // 是否是文件
  const isLink = mode & (fs.constants.S_IFLNK === fs.constants.S_IFLNK); // 是否是链接

  if (isDir) {
    fileType = "d";
  } else if (isFile) {
    fileType = "-";
  } else if (isLink) {
    fileType = "l";
  }

  return fileType;
}

module.exports = getFileType;

判断文件权限

分析

可以通过这个图来查看,首先 file type已经观察过了, 通过 mode & fs.constants.对应的文件类型 > 0

  • 拿 0100 0001 1110 1101 bin 这个文件夹举例子
javascript 复制代码
 0100 0001 1110 1101 转化一下缩进就是
 0100 000 111 101 101 
   d(文件夹类型)      rwx r-x r-x
减去缩进 drwxr-xr-x 

再进行对比 ls
drwxr-xr-x  5 wangtianyu  staff  160  4 13 14:29 bin

通过上图的结果 对比发现 一模一样

  • 再拿 1000 0001 1010 0100 package举例子
javascript 复制代码
1000 0001 1010 0100 转化缩进
1000 000 110 100 100
-(文件类型)       rw- r-- r-- 
再对比 ls
-rw-r--r--  1 wangtianyu  staff  271  4 11 21:20 package.json 

通过两个例子找规律 已经实现了对文件的权限读取,接下来就是用如何判断文件类型以及文件权限通过node的方式进行对应字符转化

编写函数

javascript 复制代码
/**
 * 理解文件存储的表达方式
 * unix使用32位二进制数存储文件类型和权限
 * 0000 0000 0000 0000
 * 0000(文件类型) 000(特殊权限) 000(用户权限) 000(组权限) 000(其他权限)
 *
 * 0
 * 1
 * 10
 * 11
 * 110
 * 111
 * 1110
 * 1111
 * 当前类型位:0001 如何判断最后一位是1呢?
 * 0001 & 0001 = 0001 true
 * 0000 & 0001  = 0000 false
 * 0000 & 0010 = 0000 false
 * 因为 只要带0 结果都是false 所以所有带0的都要删除
 *
 * 所以最终能够表达状态的只有
 * 1
 * 11
 * 111
 * 1111
 *
 * 所以 例如 用户权限这里 001 010 100 都可以代表一种权限
 */

const fs = require("fs");
function auth(mode) {
  let authString = "";

  // u: 当前登录用户
  const canUserRead = mode & fs.constants.S_IRUSR;
  const canUserWrite = mode & fs.constants.S_IWUSR;
  const canUserExecute = mode & fs.constants.S_IXUSR;

  canUserRead ? (authString += "r") : (authString += "-");
  canUserWrite ? (authString += "w") : (authString += "-");
  canUserExecute ? (authString += "x") : (authString += "-");

  // g: 当前用户所在分组
  const canGroupRead = mode & fs.constants.S_IRGRP;
  const canGroupWrite = mode & fs.constants.S_IWGRP;
  const canGroupExecute = mode & fs.constants.S_IXGRP;

  canGroupRead ? (authString += "r") : (authString += "-");
  canGroupWrite ? (authString += "w") : (authString += "-");
  canGroupExecute ? (authString += "x") : (authString += "-");

  // o:其他用户
  const canOtherRead = mode & fs.constants.S_IROTH;
  const canOtherWrite = mode & fs.constants.S_IWOTH;
  const canOtherExecute = mode & fs.constants.S_IXOTH;

  canOtherRead ? (authString += "r") : (authString += "-");
  canOtherWrite ? (authString += "w") : (authString += "-");
  canOtherExecute ? (authString += "x") : (authString += "-");

  return authString;
}

module.exports = auth;

引入文件类型及权限函数并做对比

javascript 复制代码
#!/usr/bin/env node
const fs = require("fs");
const parseArgs = require("./parseArgs");
// console.log("我们来开发脚手架!");
// console.log(process.argv);
const { isAll, isList } = parseArgs();
const authFn = require("./auth");
const getFileType = require("./getFileType");

// 当前工作目录:
const dir = process.cwd(); // 获取当前工作目录,并赋值给dir

let files = fs.readdirSync(dir);
let output = "";

// 没有输入 -a -l 时 获取当前文件夹下的所有文件排除以.开头的文件或文件夹(隐藏文件)
if (!isAll) {
  files = files.filter((file) => !file.startsWith("."));
}
// ls -a的情况 获取当前文件夹下的所有文件包含隐藏文件
if (!isList) {
  files.forEach((file) => (output += file + "      "));
}

// ls -a -l的情况 已列表形式展示当前文件夹下的所有文件包含隐藏文件
else {
  const total = `total ${files.length}` + "\n";
  files.forEach((file, index) => {
    const stat = fs.statSync(file); // 文件信息对象
    const mode = stat.mode; // 文件二进制
    const authString = authFn(mode); // 权限
    const fileType = getFileType(mode); // 文件类型
    // console.log({
    //   authString,
    //   文件名称: file,
    //   文件十进制: mode,
    //   是否是文件夹: isDir > 0,
    //   是否是文件: isFile > 0,
    // });
    // 拼接字符串并去掉最后行空行
    index === files.length - 1
      ? (output += fileType + authString + "\t" + file)
      : (output += fileType + authString + "\t" + file + "\n");
  });
  output = total + output;
}

console.log(output);

console.log({
  isAll,
  isList,
});

查看对比结果

基本摸一样,其中文件大小 文件名称等都可以从 fs.statSync(file) 文件信息对象中获取,而计算机用户也就是这里的wangtianyu 不能获得,所以下一步先获取 主机名称

javascript 复制代码
➜  tianyu.ls git:(main) ✗ tianyu-ls  -al
total 3
-rw-r--r--      .log
drwxr-xr-x      bin
-rw-r--r--      package.json
➜  tianyu.ls git:(main) ✗ ls  -al       
total 3
-rw-r--r--  1 wangtianyu  staff    0  4 13 12:29 .log
drwxr-xr-x  6 wangtianyu  staff  192  4 13 16:25 bin
-rw-r--r--  1 wangtianyu  staff  271  4 11 21:20 package.json

获取主机名称

javascript 复制代码
// child_process 模块提供了衍生子进程的能力,我们可以通过 execSync 方法来执行 shell 命令,并获取其返回值。
const cp = require("child_process");
function getFileUser(state) {
  const { uid } = state;
  const username = cp.execSync(`id -nu ${uid}`).toString().trim();
  const groupname = cp.execSync(`id -ng ${uid}`).toString().trim();

  return username + "\t" + groupname + "\t";
}

module.exports = getFileUser;

function getFileUser(state) {
  const { uid } = state;
  const username = cp.execSync(`id -nu ${uid}`).toString().trim();
  const groupname = cp.execSync(`id -ng ${uid}`).toString().trim();

  return username + "\t" + groupname + "\t";
}

module.exports = getFileUser;

获取创建时间

javascript 复制代码
const dayjs = require("dayjs");

function getFileSizeAndDate(stat) {
  const { size, birthtimeMs } = stat;
  const date = dayjs(birthtimeMs).format("MMM D HH:mm");
  return size + "\t" + date + "\t";
}

module.exports = getFileSizeAndDate;

获取子文件夹数量

javascript 复制代码
const fs = require("fs");
function getSubFileCount(stat, file) {
  console.log({
    stat,
    file,
  });
  if (file === ".Trash") {
    return "1\t"; // 如果是垃圾箱,直接返回1
  }
  if (!stat.isDirectory() || !fs.readdirSync(file).length) {
    return "1\t"; // 如果不是目录,直接返回1
  }
  // 读取目录下的所有文件,包含 . ------ 指向当前目录自身的引用。 .. ------ 指向父目录的引用。
  const allEntries = Number(fs.readdirSync(file).length) + 2;
  return allEntries.toString().trim() + "\t";
}

module.exports = getSubFileCount;

自测 + 主文件代码

javascript 复制代码
➜  tianyu.ls git:(main) ✗ ls -al
total 8
-rw-r--r--  1 wangtianyu  staff    0  4 13 12:29 .log
drwxr-xr-x  9 wangtianyu  staff  288  4 13 17:53 bin
-rw-r--r--  1 wangtianyu  staff  322  4 13 17:38 package.json
➜  tianyu.ls git:(main) ✗ tianyu-ls  -al
total 3
-rw-r--r--      1       wangtianyu      staff           0       4 13 12:29      .log
drwxr-xr-x      9       wangtianyu      staff           288     4 11 21:20      bin
-rw-r--r--      1       wangtianyu      staff           322     4 11 21:19      package.json
javascript 复制代码
#!/usr/bin/env node
const fs = require("fs");
const parseArgs = require("./parseArgs");
// console.log("我们来开发脚手架!");
// console.log(process.argv);
const { isAll, isList } = parseArgs();
const authFn = require("./auth");
const getFileType = require("./getFileType");
const getFileUser = require("./getFileUser");
const getFileSizeAndDate = require("./getFileSizeAndDate");
const getSubFileCount = require("./getSubFileCount");

// 当前工作目录:
const dir = process.cwd(); // 获取当前工作目录,并赋值给dir

let files = fs.readdirSync(dir);
let output = "";

// 没有输入 -a -l 时 获取当前文件夹下的所有文件排除以.开头的文件或文件夹(隐藏文件)
if (!isAll) {
  files = files.filter((file) => !file.startsWith("."));
}
// ls -a的情况 获取当前文件夹下的所有文件包含隐藏文件
if (!isList) {
  files.forEach((file) => (output += file + "      "));
}

// ls -a -l的情况 已列表形式展示当前文件夹下的所有文件包含隐藏文件
else {
  const total = `total ${files.length}` + "\n";
  files.forEach((file, index) => {
    const stat = fs.statSync(file); // 文件信息对象
    const mode = stat.mode; // 文件二进制
    const authString = authFn(mode); // 文件权限
    const fileType = getFileType(mode); // 文件类型
    const fileUser = getFileUser(stat); // 获取文件用户即用户分组
    const sizeAndDate = getFileSizeAndDate(stat); //大小和日期
    const subDirCount = getSubFileCount(stat, file); // 子文件数量

    index === files.length - 1
      ? (output +=
          fileType +
          authString +
          subDirCount +
          fileUser +
          "\t" +
          sizeAndDate +
          file)
      : (output +=
          fileType +
          authString +
          subDirCount +
          fileUser +
          "\t" +
          sizeAndDate +
          file +
          "\n");
  });
  // console.log('Contents of "bin" directory:', fs.readdirSync("./bin"));
  output = total + output;
}

console.log(output);

console.log({
  isAll,
  isList,
});

至此完成了 模仿 ls -al 查看文件及列表的功能,接下去还需要进打包以及npm发布的过程,未完待续...

相关推荐
一个很帅的帅哥10 小时前
实现浏览器的下拉加载功能(类似知乎)
开发语言·javascript·mysql·mongodb·node.js·vue·express
Bang邦14 小时前
使用nvm管理Node.js多版本
前端·node.js·node多版本管理
新知图书14 小时前
Node.js快速入门
node.js
FakeOccupational16 小时前
nodejs 007:错误npm error Error: EPERM: operation not permitted, symlink
前端·npm·node.js
亦舒.16 小时前
JSDelivr & NPM CDN 国内加速节点
前端·npm·node.js
代码搬运媛16 小时前
code eintegrity npm err sha512
前端·npm·node.js
猿来如此呀19 小时前
运行npm install 时,卡在sill idealTree buildDeps没有反应
前端·npm·node.js
八了个戒1 天前
Koa (下一代web框架) 【Node.js进阶】
前端·node.js
谢尔登1 天前
【Node.js】RabbitMQ 不同交换器类型的使用
node.js·rabbitmq·ruby
weixin_441018351 天前
webpack的热更新原理
前端·webpack·node.js