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

juejin-save

v1.0.1

Published

save juejin article

Downloads

3

Readme

打造一个保存掘金文章的 cli

安装

npm i juejin-save -g

使用

juejin-save save https://xxx

效果

gif


1. 主要 package version

注意:文章重点在于打造 cli,不是 puppeteer

| package | version | 功能 | | :------------------------------------------------------------------------------------------- | :------ | :--------------------------------- | | commander | ^9.0.0 | 创建处理命令 | | inquirer | ^8.2.0 | 处理交互 | | ora | ^5.4.1 | 处理 loading | | puppeteer | ^13.3.2 | 通过 api 来控制 Chromium 或 Chrome |

2. 项目目录结构

├── bin
|  └── cli.js          // 入口文件
├── LICENSE
├── package-lock.json
├── package.json
├── puppeteer.js       // puppeteer保存文章文件
└── README.md

2. package.json 中添加bin字段

添加juejin-save命令,指定运行文件为 bin 目录 cli.js

+ {
+   "bin": {
+     "juejin-save": "bin/cli.js"
+   }
+ }

3. 主要 package api 介绍

3.1 commander

const { Command } = require("commander");
const program = new Command();

program
  .command("clone <source> [destination]")
  .description("clone a repository into a newly created directory")
  .action((source, destination) => {
    console.log("clone command called");
  });

program.parse();

3.2 inquirer

var inquirer = require("inquirer");
inquirer
  .prompt([
    /* Pass your questions in here */
  ])
  .then((answers) => {
    // Use user feedback for... whatever!!
  })
  .catch((error) => {
    if (error.isTtyError) {
      // Prompt couldn't be rendered in the current environment
    } else {
      // Something else went wrong
    }
  });

3.3 ora

import ora from "ora";

const spinner = ora("Loading unicorns").start();

setTimeout(() => {
  spinner.color = "yellow";
  spinner.text = "Loading rainbows";
}, 1000);

3.4 puppeteer

const puppeteer = require("puppeteer");

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto("https://example.com");
  await page.screenshot({ path: "example.png" });

  await browser.close();
})();

4. 主要逻辑代码

  1. bin/cli.js
#!/usr/bin/env node

const inquirer = require("inquirer");
const ora = require("ora");
const { Command } = require("commander");
const { puppeteerInit, saveToHtml, saveToMd, saveToPdf } = require(path.resolve(
  __dirname,
  "../puppeteer"
));

const program = new Command();
const spinner = ora();

// 交互式询问
async function handlePrompt() {
  return await inquirer.prompt([
    {
      name: "autoCreateFolder",
      message: `Automatically create folders?`,
      type: "confirm",
    },
    //...
  ]);
}

// 询问过后的处理,开始puppeteer初始化
async function AfterePrompt(articleUrl, answers) {
  spinner.color = "yellow";
  spinner.start("puppeteer intial...");

  const obj = await puppeteerInit(articleUrl, answers);

  spinner.stopAndPersist({
    symbol: chalk.green("✓"),
    text: chalk.green("puppeteer init ok"),
  });

  return obj;
}

// 导出文件
async function exportFile(arg) {
  const { page, outMdFilePath, outPdfFilePath, outHtmlfFilePath } = arg;
  await saveToMd(page, outMdFilePath);
  await saveToPdf(page, outPdfFilePath);
  await saveToHtml(page, outHtmlfFilePath);
}

// 第一步:创建命令
program
  .version(require(path.resolve(__dirname, "../package.json")).version)
  .command("save  <article-url>")
  .description("save https://xxx")
  .action(async (articleUrl) => {
    // 第二步:交互式询问
    const answers = await handlePrompt(articleUrl);
    // 第三步:拿到交互结果
    const data = await AfterePrompt(articleUrl, answers);
    // 第四步:导出文件
    await exportFile(data);

    process.exit(1);
  });

program.parse();
  1. puppeteer.js
const puppeteer = require("puppeteer");

// 保存html
async function saveToHtml(page, outHtmlfFilePath) {
  // ...
}

// 保存markdown
async function saveToMd(page, outMdFilePath) {
  // ...
}

// 保存pdf
async function saveToPdf(page, outPdfFilePath) {
  // ...
}

// puppeteer初始化
async function puppeteerInit(href) {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  page.setViewport({
    width: 1920,
    height: 1080,
  });

  await page.goto(href, {
    waitUntil: "domcontentloaded",
    referer: href,
  });
  await page.waitForTimeout(3000); // 确保页面加载完毕

  return {
    browser,
    page,
  };
}

module.exports = {
  puppeteerInit,
  saveToHtml,
  saveToMd,
  saveToPdf,
};

5. 本地测试

  1. 在项目根目录执行
npm link

执行完之后,成功提示:

added 1 package, and audited 3 packages in 1s

found 0 vulnerabilities

也可以在本机的 npm 全局安装里找到一个软链接,如图:

  1. 在任意目录打开命令行,执行
juejin-save save  https://juejin.cn/post/xxxx

不出意外,可以看到,多出了一个文件夹,文章被保存在文件夹里面了。

6. 参考资料

  1. 手写一个合格的前端脚手架

  2. 实现 CLI 常用工具包 - 终端交互相关