前端脚手架系列(一):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发布的过程,未完待续...

相关推荐
酷酷的威朗普2 小时前
医院绩效考核系统
javascript·css·vue.js·typescript·node.js·echarts·html5
前端李易安14 小时前
Webpack 热更新(HMR)详解:原理与实现
前端·webpack·node.js
Ztiddler1 天前
【npm设置代理-解决npm网络连接error network失败问题】
前端·后端·npm·node.js·vue
前端青山1 天前
webpack进阶(一)
前端·javascript·webpack·前端框架·node.js
老攀呀1 天前
安装多个nodejs版本(nvm)
node.js
佚名程序员1 天前
【Node.js】全面解析 Node.js 安全最佳实践:保护您的应用
安全·node.js
zxg_神说要有光2 天前
快速入门 AI:调用 AI 接口生成 React 组件
前端·javascript·node.js
佚名程序员2 天前
【Node.js】深入理解 V8 JavaScript 引擎
前端·javascript·node.js
赵闪闪1682 天前
Node.js 安装与开发环境配置全指南
node.js
前端与小赵2 天前
什么是Webpack,有什么特点
前端·webpack·node.js