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 🙏

© 2024 – Pkg Stats / Ryan Hefner

xinxinqian_cli

v1.0.0

Published

脚手架

Downloads

1

Readme

搭建脚手架 - 一键生成项目模板

  • npm init -y初始化 package.json
  • 创建 bin 文件夹,以及 bin/cli.js 文件
  • 创建 README.md 以记录

项目目录

xinxinqian
|- bin
|- |- cli.js
|- package.json
|- README.md

配置 package.json 文件

{
  "bin": {
    "xin-cli": "./bin/cli.js" // 配置启动文件路径,xin-cli为别名,用于下面终端执行`xin-cli`
  }
}

编辑 cli.js 文件

  • #! /usr/bin/env node的理解: 参考
    • #!只是标识作用,表示的是该文件作为执行文件来运行。即可当做脚本来运行。
    • /usr/bin/env node: 表示用 node 来执行此文件。
      • node 怎么来呢?就去用户 usr 的安装根目录 bin 下的 env 环境去找。
      • 在 windows 上,就去安装 node 的 bin 目录去找 node 执行器。,一般都放在环境变量中,所以能正确找到 node 来执行。
#! /usr/bin/env node
console.log("~ working ~");

执行 npm link (后面不加参数)

  • 终端中输入npm link
  • 终端中输入xin-cli
  • 可以看到 终端中打印结果:~ working ~

创建脚手架启动命令

  • commander - 实现终端命令行的输出
    • 参照vue-cli中的命令有 create、config 等
  • 安装 commander 依赖包npm install commander --save
  • 安装完成后,编辑 cli.js 的内容, 创建create的命令:
#! /usr/bin/env node
const program = require("commander");
// 定义命令和参数:创建了 create 的命令,作用是创建一个新的项目目录
program
  .command("create [name]")
  .description("create a new project")
  // -f or --force 为强制创建,如果创建的目录存在,则直接覆盖
  .option("-f, --force", "overwrite target directory if it exits")
  .action((name, options) => {
    // 打印结果,输出用户手动输入的项目名字
    console.log("name", name);
  });
program
  // 配置版本号信息
  .version(`v${require("../package.json").version}`)
  .usage("<command> [option]");

// 解析用户执行命令传入参数
program.parse(process.argv);
  • 执行xin-cli create去验证,xin-cli create my-project,在终端看效果。
  • 创建文件夹 lib,该文件下的内容 - 主要逻辑实现。
  • 创建 lib/create.js 文件, 并编辑内容
module.exports = async function (name, options) {
  // 验证是否正确取到值
  console.log("create success", name);
};
  • 修改 bin/cli.js 文件内容:
.action((name, options) => {
    // 打印结果,输出用户手动输入的项目名字
    // console.log("name", name)
    require("../lib/create")(name, options);
})
  • 执行指令xin-cli create my-project,在终端看效果。

创建项目文件目录

  • 要考虑一个问题:目录是否已经存在,怎么处理已经存在的目录?
    • 如果不存在,则直接创建一个新的项目文件目录
    • 如果存在,是否要直接删除或者用一个新的项目文件目录替换掉
    • 可以给用户提供命令选择
  • 涉及 nodejs 对文件的处理,引入依赖包 fs-extra
  • 安装npm install fs-extra --save

继续创建命令,config 命令

// 配置config命令
program
  .command("config [value]")
  .description("inspect and modify the config")
  .option("-g --get <path>", "get value from option")
  .option("-s, --set <path><value>")
  .option("-d, --delete <path>", "delete option from config")
  .action((value, options) => {
    console.log("自定义config命令", value);
  });

可以使用依赖 chalk、figlet,做出一些好看的样式 - 自定义修改默认的 help 展示信息

// 自定义help输出信息,如果加上program.helpInformation,那么之前默认输出的help信息就会别覆盖。
// program.helpInformation = function () {
//     return '';
// };
// 可以使用chalk、figlet做出一些好看的样式
program.on("--help", () => {
  // 使用figlet 绘制logo
  console.log(
    "\r\n" +
      figlet.textSync("xinxinqian", {
        font: "Ghost",
        horizontalLayout: "default",
        verticalLayout: "default",
        width: 80,
        whitespaceBreak: true,
      })
  );
});
// 说明信息
console.log(`\r\nRun ${chalk.cyan("roc <command> --help")} show details\r\n`);

实现询问哟用户的操作

  • inquirer - npm 文档
  • 安装 inquirer 依赖 npm i [email protected] --save
    • inquirer 版本 8 以下才支持 require 引入,版本 9 以上不支持 es Module,要使用 import inquirer from 'inquirer';
  • 修改 lib/create.js 文件内容
const path = require("path");
const fs = require("fs-extra");

// 引入inquirer - 用于控制台询问,交互式命令行
const inquirer = require("inquirer");

module.exports = async function (name, options) {
  // 选择目录
  const cwd = process.cwd();
  // 需要创建的目录地址
  const targetAir = path.join(cwd, name);
  // 判断目录是否已经存在
  if (fs.existsSync(targetAir)) {
    // 是否为强制创建?
    if (options.force) {
      await fs.remove(targetAir);
    } else {
      // TODO: 询问用户是否确定要覆盖
      // 在终端输出询问用户是否覆盖:
      const inquirerParams = [
        {
          name: "operation",
          type: "list",
          message: "目标文件目录已存在,请选择如下操作:",
          choices: [
            {
              name: "替换当前目录",
              value: "replace",
            },
            {
              name: "移除已有目录",
              value: "remove",
            },
            {
              name: "取消当前操作",
              value: "cancel",
            },
          ],
        },
      ];
      let { operation } = await inquirer.prompt(inquirerParams);
      if (!operation || operation === "cancel") {
        return;
      } else {
        // 移除已存在的目录
        console.log("\r\nRemoving...");
        await fs.remove(targetAir);
      }
    }
  }
  // 验证是否正确取到值
  fs.mkdir(`./${name}`, async (error) => {
    if (error) {
      console.log("目录不存在");
      console.error("create fail", error);
      return;
    }
    console.log("create success");
  });
};

2023.6.19重新学习 参考网址

inquirer 字段描述

  • type:问题类型,可以是input(输入框)、list(列表选择框)、confirm(二选一选择框)等。
  • name:问题名称,用于标识答案对象中对应的属性名
  • message:问题描述,将会作为问题提示信息展示给用户
  • choices:选项列表,只有当问题类型为 list 时才需要提供

项目模板创建

  • bin文件夹下创建templates.js, 内容如下:
/** 暴露模版代码 */
module.exports = [
  {
    name: "xinxin-plus",
    value: "https://github.com:xinxin2qian/xinxin-plus",
  },
  {
    name: "jslib-xinxin",
    value: "https://github.com:xinxin2qian/jslib-xinxin",
  },
  {
    name: "test-demo",
    value: "https://github.com:xinxin2qian/test-demo",
  },
];

注意模版地址部分,域名 github.com 和模版地址之间是用冒号:隔开的,不是斜杠/,这个是下一节下载 git 仓库代码模版所用到的库 download-git-repo 的规则。 实际项目中要根据自己的需求配置不同的模版,比如 gitlab,gitee 等,文章后面也会换成接口动态请求。

  • 在 lib/create.js,加入代码,用于加载 templates 模板列表
const { template } = await inquirer.prompt({
  type: "list",
  name: "template",
  message: "请选择模版:",
  choices: templates, // 模版列表
});
console.log("模版地址:", template);

实现下载模板

  • 拿到用户选择的模板地址后,就要根据用户输入的项目名称,把指定的项目模板下载到对应项目文件夹中,实现下载 git 项目模板的功能需要下载download-git-repo依赖,通过指令npm i download-git-repo -S进行安装。
  • download-git-repo的语法
const downloadGitRepo = require("download-git-repo");

downloadGitRepo("项目git地址", "目标文件夹", function (err) {
  if (err) {
    console.log("下载失败", err);
  } else {
    console.log("下载成功");
  }
});
// 项目git地址:在选择完模板时可以获取到。
// 目标文件夹:应该是用户执行命令行所在位置下的项目名称文件夹。
const path = require("path");
// 目标文件夹 = 用户命令行所在目录 + 项目名称
const dest = path.join(process.cwd(), name);

默认会拉取 master 分支的代码,如果想从其他分支拉取代码,可以在 git 地址后面添加#branch 选择分支。如,指定 feature 分支:https://github.com:xinxin2qian/xinxin-plus#feature

优化 cli 脚手架

  • 由于从github下载的模板,有时候网络不好,下载时间会久一些,使用loading动画来提升用户体验,使用ora来实现,这是一个命令行的loading动画库
  • 使用指令npm i [email protected] -S进行安装,因为安装新的版本需要用 import 引入,由于 node 环境,也没有引入 babel 这些,就直接使用 5.x 的版本,可以直接 require 引入。
// 引入ora,loading动画库
const ora = require("ora");
// 定义loading
const loading = ora("正在下载模板...");
// ...
// 开始loading
loading.start();
// 开始下载模板
downloadGitRepo(templateUrl, targetAir, (error) => {
  if (error) {
    loading.fail("create template error" + error.message);
  } else {
    loading.succeed("create template success");
  }
});
// ...

支持从命令行传参数

  • 可以通过命令行参数形式直接传入项目名称和模板名称
// ...
// 如果通过命令行传入模板名称
let templateItem = templates.find(
  (template) => template.name === options.template
);
let templateUrl = templateItem ? templateItem.value : "";
if (!templateUrl) {
  const { template } = await inquirer.prompt({
    type: "list",
    name: "template",
    message: "请选择模版:",
    choices: templates, // 模版列表
  });
  console.log("模版地址:", template);
  templateUrl = template;
}
// ...

覆盖文件夹操作(补充)

  • 当前用户输入的目录所在位置已经有同名的文件夹时,应该提示用户是否覆盖,如果选了覆盖,则把原文件删除,如果选择不覆盖,就停止所有操作,退出命令行。
  • 通过existsSync方法判断目标文件夹是否存在,需要下载fs-extra依赖包
  • 如果存在,使用inquirer模块的confirm类型交互来让用户选择是否覆盖。

添加模板后,引导操作

  • 类似vitevue-cli等脚手架在从创建完项目后,都会有一个引导提示,比如,cd xxx进入文件夹,npm i安装依赖包等等

用接口获取动态模板

  • 如果新增或者删除模板,就需要修改cli脚手架代码,那么项目中的templates.js文件也要跟着修改,所以,换成接口请求的方式,增加灵活度,像githubgitlab等代码仓库网站,都有提供获取仓库信息等api,比如,githubapi.github.com返回很多接口。 => 倒数第二个是一个获取用户仓库列表信息的接口,其中{user}是用户名称参数,可以通过这个接口查询到对应github用户下所有公开的git仓库信息。如:自己的仓库信息
  • 在文件夹lib下,新建api.js文件,并在终端通过指令npm i axios -S安装依赖
  • api.js写入内容:
const axios = require("axios");
const getGitReposList = (username) => {
  return new Promise((resolve, reject) => {
    axios
      .get(`https://api.github.com/users/${username}/repos`)
      .then(function (response) {
        if (response.status === 200) {
          const list = response.data.map((item) => ({
            name: item.name,
            value: `https://github.com:${username}/${item.name}`,
          }));
          resolve(list);
        } else {
          reject(response.status);
        }
      })
      .catch(function (error) {
        reject(error);
      });
  });
};
module.exports = {
  getGitReposList,
};
  • 修改create.js文件的代码:
// ...
console.log("create success");
const getRepoLoading = ora("get templates...");
getRepoLoading.start();
const templates = await getGitReposList("xinxin2qian");
getRepoLoading.succeed("get templates success!");
// 如果通过命令行传入模板名称
let templateItem = templates.find(
  (template) => template.name === options.template
);
// ...

cli脚手架使用

安装

$ npm install -g xin-cli
# or yarn
$ yarn global add xin-cli

使用

# 创建模版
$ xin-cli create <name> [-t|--template]
# 示例
$ xin-cli create my-test -template xinxin-plus