npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

jcae-cli

v1.0.0

Published

| 包名 | 用途 | | ---------- | ---------------------------------------------------------------------------------------------

Readme

依赖的基础包

| 包名 | 用途 | | ---------- | ------------------------------------------------------------------------------------------------------------------------------------ | | commander | 命令行工具,读取命令行命令,知道用户想要做什么 | | inquirer | 交互式命令工具,给用户提供一个提问流方式 | | chalk | 颜色插件,用来修改命令行输出样式,通过颜色区分 info、error 日志,清晰直观 | | ora | 用于显示加载中的效果,类似于前端页面的 loading 效果,想下载模版这种耗时的操作,有了 loading 效果,可以提示用户正在进行中,请耐心等待 | | globby | 用于检索文件 | | fs-extra | node fs 文件系统模块的增强版 | | pacote | 获取 node 包最新版本等信息 | | handlebars | 提供了必要的功能,使你可以高效地构建语义化模板 |

目录结构
cli-template
├─ .gitignore
├─ README.md
├─ build // 打包后文件夹
├─ project-template // 初始化项目模版
├─ bin.js // 生产环境执行文件入口,具体见下
├─ bin-local.js // 本底调试执行文件入口,具体见下
├─ package.json // 配置文件,具体见下
├─ src
│ ├─ commands // 命令文件夹
│ │ ├─ create.ts // create 命令
│ │ ├─ scope.ts // scope 命令
│ │ ├─ package.ts // package 命令
│ │ └─ utils // 公共函数
│ ├─ index.ts // 入口文件
│ └─ lib // 公共第三方包
│ ├─ consts.ts // 常量
│ ├─ index.ts
│ ├─ logger.ts // 控制台颜色输出
│ └─ spinner.ts // 控制台 loading
├─ tsconfig.json // TypeScript 配置文件
└─ tslint.json // tslint 配置文件
package.json
  • 1、npm init 初始化 package.json
  • 2、npm i typescript ts-node tslint rimraf -D 安装开发依赖
  • 3、npm i typescript chalk commander execa fs-extra globby handlebars inquirer ora pacote 安装生产依赖
  • scripts配置clear、build、publish、lint命令,具体使用最后发包会讲到

完成的package.json配置文件如下

{
  "name": "cli-template",
  "version": "0.0.2",
  "description": "cli模版仓库",
  "main": "./build",
  "scripts": {
    "clear": "rimraf build",
    "build": "npm run clear && tsc",
    "publish": "npm run build && npm publish",
    "lint": "tslint ./src/**/*.ts --fix"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/zxyue25/cli-template.git"
  },
  "bin": {
    "privatify": "./bin.js",
    "privatify-local": "./bin-local.js"
  },
  "files": ["build", "bin.js"],
  "keywords": ["cli", "node", "typescript", "command"],
  "author": "zxyue25",
  "license": "ISC",
  "devDependencies": {
    "rimraf": "^3.0.2",
    "ts-node": "^10.3.0",
    "tslint": "^6.1.3",
    "typescript": "^4.4.4"
  },
  "dependencies": {
    "chalk": "^4.1.2",
    "commander": "^8.2.0",
    "execa": "^5.1.1",
    "fs-extra": "^10.0.0",
    "globby": "^11.0.4",
    "inquirer": "^8.2.0",
    "leven": "^4.0.0",
    "ora": "^5.4.1",
    "pacote": "^12.0.2"
  }
}

重点需要关注bin字段files字段

  • bin字段见下面(2)
  • files字段即 npm 的白名单,如下图,npm 官方解释,也就是说发包后需要包括哪些文件,不配置的话默认发布全部文件,这自然是不好的,你想别人看到你的源码嘛?所以这里我们配置了"files": [ "build", "bin.js" ],包括 build 跟 bin.js,src 文件夹不在白名单内 image.png
(3) tsconfig.json 配置文件

如果一个目录下存在一个tsconfig.json文件,那么它意味着这个目录是 TypeScript 项目的根目录。 tsconfig.json文件中指定了用来编译这个项目的根文件和编译选项,按照如下配置即可

不熟悉 typescript 的可以先跳过,理解为我们利用 typescript 编写,需要配置一个配置文件即可

// tsconfig.json
{
  "compilerOptions": {
    "target": "es2017",
    "module": "commonjs",
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "alwaysStrict": true,
    "sourceMap": false,
    "noEmit": false,
    "noEmitHelpers": false,
    "importHelpers": false,
    "strictNullChecks": false,
    "allowUnreachableCode": true,
    "lib": ["es6"],
    "typeRoots": ["./node_modules/@types"],
    "outDir": "./build", // 重定向输出目录
    "rootDir": "./src" // 仅用来控制输出的目录结构
  },
  "exclude": [
    // 不参与打包的目录
    "node_modules",
    "build",
    "**/*.test.ts",
    "temp",
    "scripts",
    "./src/__tests__"
  ],
  "esModuleInterop": true,
  "allowSyntheticDefaultImports": true,
  "compileOnSave": false,
  "buildOnSave": false
}
(4)bin.js 和 npm link 本地调试
  "bin": {
    "privatify": "./bin.js",
    "privatify-local": "./bin-local.js"
  }

生产环境

// bin.js
#!/usr/bin/env node
require('./build')

本地调试

// bin-local.js
#!/usr/bin/env node
require('ts-node/register')
require('./src')
核心代码实现

src/index.ts为入口文件,src/commands/*.*s下放具体命令文件,这里我们根据3.1中设计的脚手架的命令,创建create.tsscope.tspackage.ts文件 image.png

回顾如下 image.png

可以看到,每个命令都包括command(命令)description(描述)、还有一个执行函数(action),部分还支持option(参数)

src/commands/*.*s下都写成如下形式,如create.ts

// src/commands/create.ts
const action = (projectName) => {
  console.log("projectName:", projectName);
};
export default {
  command: "create <registry-name>",
  description: "创建一个npm私服仓库",
  optionList: [["--context <context>", "上下文路径"]],
  action,
};
公共第三方依赖封装

脚手架属于交互式命令行,涉及到界面的友好提示,成功或失败的颜色、文案等,loading,图标等将此统一封装在路径src/lib下,具体不展开,比较基本的封装

初始化项目模版实现

逻辑大致与create [options] <app-name>一样,通过交互式初始化一个项目,利用inquirer包进行交互,询问用户package.jsonnamedescriptionauthor字段,拿到字段后,开始下载项目模版,项目模版存在的位置有两种实现方式,如下

  • 第一种是和脚手架打包在一起,在安装脚手架的时候就会将项目模板存放在全局目录下了,这种方式每次创建项目的时候都是从本地拷贝的速度很快,但是项目模板自身升级比较困难
  • 第二种是将项目模板存在远端仓库(比如 gitlab 仓库),这种方式每次创建项目的时候都是通过某个地址动态下载的,项目模板更新方便 image.png 我们这里先用第一种,项目模版放在project-template路径下,第二种下面讲
// project-template/package.json
{
  "name": "{{ name }}",
  "version": "1.0.0",
  "description": "{{ description }}",
  "main": "index.js",
  "author": "{{ author }}",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "license": "ISC",
  "dependencies": {
    "npm-package-privatify": "^1.1.4"
  }
}

再利用handlebars包进行模版替换,也就是把上面的{{ name }}{{ description }}{{ author }}换成用户输入的

// // src/commands/create.ts
import * as path from "path";
import * as handlebars from "handlebars";
import * as inquirer from "inquirer";
import {
  chalk,
  execa,
  fs,
  startSpinner,
  succeedSpiner,
  warn,
  info,
} from "../lib";

// 检查是否已经存在相同名字工程
export const checkProjectExist = async (targetDir) => {
  if (fs.existsSync(targetDir)) {
    const answer = await inquirer.prompt({
      type: "list",
      name: "checkExist",
      message: `\n仓库路径${targetDir}已存在,请选择`,
      choices: ["覆盖", "取消"],
    });
    if (answer.checkExist === "覆盖") {
      warn(`删除${targetDir}...`);
      fs.removeSync(targetDir);
    } else {
      return true;
    }
  }
  return false;
};

export const getQuestions = async (projectName) => {
  return await inquirer.prompt([
    {
      type: "input",
      name: "name",
      message: `package name: (${projectName})`,
      default: projectName,
    },
    {
      type: "input",
      name: "description",
      message: "description",
    },
    {
      type: "input",
      name: "author",
      message: "author",
    },
  ]);
};

export const cloneProject = async (targetDir, projectName, projectInfo) => {
  startSpinner(`开始创建私服仓库 ${chalk.cyan(targetDir)}`);
  // 复制'private-server-boilerplate'到目标路径下创建工程
  await fs.copy(
    path.join(__dirname, "..", "..", "private-server-boilerplate"),
    targetDir
  );

  // handlebars模版引擎解析用户输入的信息存在package.json
  const jsonPath = `${targetDir}/package.json`;
  const jsonContent = fs.readFileSync(jsonPath, "utf-8");
  const jsonResult = handlebars.compile(jsonContent)(projectInfo);
  fs.writeFileSync(jsonPath, jsonResult);

  // 新建工程装包
  execa.commandSync("npm install", {
    stdio: "inherit",
    cwd: targetDir,
  });

  succeedSpiner(
    `私服仓库创建完成 ${chalk.yellow(projectName)}\n👉 输入以下命令开启私服:`
  );

  info(`$ cd ${projectName}\n$ sh start.sh\n`);
};

const action = async (projectName: string, cmdArgs?: any) => {
  console.log("projectName:", projectName);
};

export default {
  command: "create <registry-name>",
  description: "创建一个npm私服仓库",
  optionList: [["--context <context>", "上下文路径"]],
  action,
};

当创建的文件存在时,提醒用户,并提供覆盖跟取消选项,执行效果如图 image.png

可以看到创建了项目name,且package.json是用户输入的值 image.png

第二种也比较简单,利用download-git-repo包,下载远程仓库地址即可,前提是你需要新建一个模版仓库,例如如下

需要注意的是,远程仓库地址,不是 git clone 的地址,而是需要稍微调整下 比如 git 仓库地址是https://github.com/zxyue25/vue-demo-cli-templateA.git -> https://github.com:zxyue25/vue-demo-cli-templateA#master

import * as download from "download-git-repo";
// https://github.com/zxyue25/vue-demo-cli-templateA.git
download(
  "https://github.com:zxyue25/vue-demo-cli-templateA#master",
  projectName,
  { clone: true },
  (err) => {
    if (err) {
      spinner.fail();
      return;
    }
    spinner.succeed();
    inquirer
      .prompt([
        {
          type: "input",
          name: "name",
          message: "请输入项目名称",
        },
        {
          type: "input",
          name: "description",
          message: "请输入项目简介",
        },
        {
          type: "input",
          name: "author",
          message: "请输入作者姓名",
        },
      ])
      .then((answers) => {
        const packagePath = `${projectName}/package.json`;
        const packageContent = fs.readFileSync(packagePath, "utf-8");
        //使用handlebars解析模板引擎
        const packageResult = handlebars.compile(packageContent)(answers);
        //将解析后的结果重写到package.json文件中
        fs.writeFileSync(packagePath, packageResult);
        console.log(logSymbols.success, chalk.yellow("初始化模板成功"));
      });
  }
);
(8)发包

到此,脚手架基本的搭建与开发就完成了,发布到 npm

  • 1、npm run lint 校验代码,毕竟都发包了,避免出现问题
  • 2、npm run build typescript 打包
  • 3、npm publish 发布到 npm 发包完成后,安装检查
npm i npm-package-privatify -g
privatify