https://github.com/sunny-117/cli
cli
https://github.com/sunny-117/cli
cli core
Last synced: 3 months ago
JSON representation
cli
- Host: GitHub
- URL: https://github.com/sunny-117/cli
- Owner: Sunny-117
- Created: 2023-09-27T12:06:16.000Z (about 2 years ago)
- Default Branch: main
- Last Pushed: 2024-09-20T06:52:38.000Z (about 1 year ago)
- Last Synced: 2025-03-25T19:39:25.199Z (7 months ago)
- Topics: cli, core
- Language: JavaScript
- Homepage: https://juejin.cn/post/7363607004348989479
- Size: 424 KB
- Stars: 2
- Watchers: 2
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# 前端脚手架教程
> 本项目收录在掘金专栏《15天带你精通现代前端工具链生态》https://juejin.cn/column/7287224080172302336
## 前言
> 为什么要自己做脚手架?
虽然`vue-cli`,`CRA`等前端脚手架已经非常方便好用,特别是`vue-cli`,可以快速灵活的创建各种各样搭配的项目初始化模板,但其实,还是不太够用。比如,要么可能希望自定义一些webpack配置的内容,要么是希望在原有初始化项目的基础上,加入自定义的一些内容。比如公司项目手册中规定的工程化的内容,一些前端工具链的内容,api的封装,依赖的安装等等,不论是使用哪种已有的脚手架,都还需要在我们初始化项目的时候,配置一大堆内容。
当然,上面说的这些,我们完全也能手动操作,把以前保存的模板copy过来,或者自己从以前的git上下载,直接用就行。这当然也是没什么大问题的。
但是,如果我们把这个步骤放在自定义脚手架上,当然一方面可以大大的减轻项目初始化的工作量,同时,在公司项目管理中,在项目初始阶段就做到了规范化和统一。
当然,最关键的,自己动手做一个脚手架,并不复杂,但是在别人不知道的情况下,装X感却是满分。对于我们学习来说,也能加强自己对nodejs和一些边缘知识的了解,丰富我们的知识体系。
其实如果仅仅做一个最简单的脚手架,几十行代码足矣。其实无非就是把我们常用的工程化的模板,通过自定义脚手架的交互,从git上clone下来即可。如果公司之前就已经有准备好的工程化初始化模板,我们做的事情,无非就是从以前的手动选择,通过编写代码,变成自动选择了而已。
所以,基本的步骤,也就下面几步:
1、用户交互选择对应的模板
2、通过git下载对应的模板到本地,并根据用户交互完成配置更新与相关依赖安装
3、美化
当然,我们还可以将自定义脚手架可以发布到npm,可以像vue-cli一样全局安装之后直接使用
## 第三方依赖
- [download-git-repo](https://www.npmjs.com/package/download-git-repo):下载并提取git仓库
- [commander](https://www.npmjs.com/package/commander):解析命令和参数,处理命令行输入的命令
- [inquirer](https://www.npmjs.com/package/inquirer):常见交互式命令行用户界面的集合
- [shelljs](https://www.npmjs.com/package/shelljs):基于 Node.js API 的 Unix shell 命令的可移植**(Windows/Linux/OS X)实现**
- [fs-extra](https://www.npmjs.com/package/fs-extra):fs的扩展,提供了非常多的便利API,并且继承了fs所有方法和为fs方法添加了promise的支持
- [chalk](https://www.npmjs.com/package/chalk):美化终端输出,提供了多种终端输出颜色选择
- [figlet](https://www.npmjs.com/package/figlet):终端标题美化
- [ora](https://www.npmjs.com/package/ora):终端显示下载动画
- [table](https://www.npmjs.com/package/table):在终端用表格形式展示数据## 创建脚手架执行文件
创建工程文件夹,并初始化`package.json`文件
```shell
mkdir dy-cli
cd dy-cli
npm init -y
```为了引入方便,模块化默认使用ESM,所以在`package.json`中添加`"type": "module"`
创建入口可执行文件`index.js`
```javascript
#!/usr/bin/env nodeconsole.log('hello dy-cli')
```> `#!`是Linux和Unix以及各种脚本中出现在文件最开头的序列。当它出现在文本文件的第一行时,类Unix操作系统的程序加载器会分析`#!`后的内容,**将这些内容作为解释器指令,并调用对应的解释器来执行脚本**。
>
> `Shebang`的名字来自于`Sharp`和`bang`,或`hash bang`的缩写,指代`Shebang`中`#!`两个符号的典型Unix名称。
>
> Unix术语中,`#`号通常称为`sharp`(如`C#`称为`C Sharp`),`hash`或`mesh`(网、洞);而叹号`!`则常常称为`bang`。了解了`Shebang`之后就可以理解,**增加这一行是为了指定用node执行脚本文件**。简单的理解,就是输入命令后,会有在一个新建的shell中执行指定的脚本,在执行这个脚本的时候,我们需要来指定这个脚本的解释程序是node。
可是不同用户或者不同的脚本解释器有可能安装在不同的目录下,系统如何知道要去哪里找你的解释程序呢? `/usr/bin/env`就是告诉系统可以在`PATH`目录中查找。 所以配置`#!/usr/bin/env node`, 就是解决了**不同的用户node路径不同的问题,可以让系统动态的去查找node来执行你的脚本文件**。
在 `package.json` 中增加 `bin` 字段:
`bin`属性用来将可执行文件加载到全局环境中,指定了`bin`字段的npm包,一旦在全局安装,就会被加载到全局环境中,可以通过别名来执行该文件。如果非全局安装,那么会自动连接到项目的`node_module/.bin`目录中
```javascript
"bin": {
"dy-cli": "./index.js"
},
```现在,我们可以暂时先使用 `npm link` 命令把这个文件映射到全局后, 就可以在任意目录下的命令行中输入` dy-cli` 执行我们的 `index.js` 脚本文件
输入 `npm list -g` 可以查看已安装的全局模块
> `npm link`可以帮助我们模拟包安装后的状态,它会在系统中做一个快捷方式映射,让本地的包就好像install过一样,可以直接使用。在MAC中,我们在终端可以直接敲命令,其实是在执行`/usr/local/bin`目录下的脚本。这个目录,其实保存的就是下载的全局命令。
>
> 当我们在`npm install -g`的时候,其实是将相关文件安装在`/usr/local/lib/node_modules`目录下,同时,在`/usr/local/bin`目录下会有一个映射脚本,将其指向`/usr/local/lib/node_modules`下的真实文件。
>
> 而`npm link`也是做类似的事情,只不过在`/usr/local/lib/node_modules`里存的不是真实的文件,而是存了一个快捷方式,指向你当前执行`npm link`的目录。如果开发的是node包,则执行的命令名和真实执行的文件入口,会通过项目的`package.json`里`bin`的配置来获取。接下来我们无非只需要完成两个步骤:
1、通过nodejs代码拉取git repository
2、通过命令行交互,做出不同选择,拉取不同模板
把这两步跑通,然后把这两个步骤放入到`dy-cli`命令中运行,我们的脚手架其实就出来了
## 通过node拉取git repository
[download-git-repo](https://www.npmjs.com/package/download-git-repo)
### 安装
```javascript
npm i download-git-repo
```### API
```javascript
download(repository, destination, options, callback)
```下载一个 **git** `repository` 到 `destination` 文件夹,配置参数 `options`, 和 `callback回调`.
### 基本使用
```javascript
import download from 'download-git-repo'
download('yingside/webpack-template', 'test', function (err) {
console.log(err ? 'Error' : 'Success')
})
```git拉取肯定是需要花费时间的,因此,我们可以通过Promise做一下简单封装
```javascript
import download from 'download-git-repo'
const clone = (remote, name, option=false) => {
console.log("正在拉取项目......")
return new Promise((resolve, reject) => {
download(remote, name, option, err =>{
if (err) {
console.error(err);
reject(err)
return
}
console.log("拉取成功")
resolve();
})
})
}await clone("yingside/webpack-template", "test")
```## 美化
[ora](https://www.npmjs.com/package/ora):终端显示下载动画
[chalk](https://www.npmjs.com/package/chalk):美化终端输出,提供了多种终端输出颜色选择
[figlet](https://www.npmjs.com/package/figlet):终端标题美化
之前的代码感觉卡在那边,如果下载时间过久,不知道是不是在继续执行,我们可以添加简单的Loading动画效果和字体颜色,让终端界面生动起来
```javascript
import download from 'download-git-repo'
import ora from "ora";
import chalk from "chalk";const clone = (remote, name, option=false) => {
const spinner = ora('正在拉取项目......').start();
return new Promise((resolve, reject) => {
download(remote, name, option, err =>{
if (err) {
spinner.fail(chalk.red(err));
reject(err)
return
}
spinner.succeed(chalk.green('拉取成功'))
resolve();
})
})
}
```也可以把之前在index.js中的打印语句换成美化标题
```javascript
import figlet from 'figlet';
import chalk from 'chalk';console.log('\r\n' + chalk.greenBright.bold(figlet.textSync('dy-cli', {
font: 'Standard',
horizontalLayout: 'default',
verticalLayout: 'default',
width: 80,
whitespaceBreak: true
})));
console.log(`\r\nRun ${chalk.cyan(`dy-cli --help`)} for detailed usage of given command\r\n`)
```## 解析命令行指令参数
[commander](https://www.npmjs.com/package/commander)
### 安装
```javascript
npm i commander
```### 引入
```javascript
const { program } = require('commander')program.version('1.0.0');
// 利用commander解析命令行输入,必须写在所有内容最后面
program.parse(process.argv)
```可以在终端运行命令:
```shell
$ dy-cli -V
```默认是大写的`-V`,当然我们也能设置
```javascript
program.version('1.0.0','-v, --version');
```### [Custom event listeners](https://www.npmjs.com/package/commander#custom-event-listeners)
```javascript
program
.name("dy-cli")
.description("自定义脚手架")
.usage(" [options]")
.on('--help', () => {
console.log('\r\n' + chalk.greenBright.bold(figlet.textSync('dy-cli', {
font: 'Standard',
horizontalLayout: 'default',
verticalLayout: 'default',
width: 80,
whitespaceBreak: true
})));
console.log(`\r\nRun ${chalk.cyan(`dy-cli --help`)} for detailed usage of given command\r\n`)
})
```### [Commands](https://www.npmjs.com/package/commander#commands)
```javascript
program
.command('create ')
.description('创建新项目')
.option('-t, --template [template]', '输入模板名称创建项目')
.option('-f, --force', '强制覆盖本地同名项目')
.option('-i, --ignore', '忽略项目相关描述,快速创建项目')
.action((name, option) => {
console.log(name)
console.log(option)
})```
我们可以创建一些模板便于查看,这些模板其实就是已经上传到github的模板工程
**constants.js**
```javascript
// constants.js
export const templates = [
{
name: 'webpack-template',
value: 'yingside/webpack-template',
desc: '基于webpack5的vue3项目模板'
},
{
name: 'vue-cli-template',
value: 'yingside/vue-cli-template',
desc: '基于vue-cli4的vue3项目模板'
},
{
name: 'vite-template',
value: 'yingside/vite-template',
desc: '基于vite的vue3 + 前端工具链项目模板'
}
];
```添加查看所有模板的`command`命令`list`
```javascript
import { templates } from './constants.js';......其他代码省略
program
.command('list')
.description('查看所有可用模板')
.action(() => {
console.log(chalk.yellowBright('模板列表'));
templates.forEach((temp, index) => {
console.log(`(${index + 1}) | ${temp.name} | ${temp.value} | ${temp.desc}`)
})
})
```### ESM引入json文件
在nodejs的commonjs模块化下引入json文件很方便
```javascript
const pkg = require("./package.json");
```但是在ESM模块化下直接引入json文件,会报错:
```javascript
import pkg from './package.json'node:internal/errors:478
ErrorCaptureStackTrace(err);
^
TypeError [ERR_IMPORT_ASSERTION_TYPE_MISSING] ......
```可以使用下面简单的方式引入:
```javascript
import pkg from './package.json' assert {type: 'json'}
```当然这么做会报出警告:
```javascript
(node:8490) ExperimentalWarning: Importing JSON modules is an experimental feature.
```我们可以使用下面两种方式之一引入json文件
```javascript
import { readFile } from 'fs/promises';
const pkg = JSON.parse(
await readFile(
new URL('./package.json', import.meta.url)
)
);
```或者
```javascript
import { createRequire } from "module";
const require = createRequire(import.meta.url);
const pkg = require("./package.json");
```其中,`import.meta.url`可以在ESM中方便地获取当前模块的绝对路径
> - `process.cwd()`: `cwd` 是 "current working directory" 的缩写,**表示当前工作目录**。`process.cwd()` 返回 Node.js 进程当前的工作目录的路径。
> - `__dirname`: `__dirname` **是当前模块所在的目录的绝对路径**。它是由 Node.js 在每个模块中注入的特殊变量。换句话说,它只能在 Node.js 的模块系统中使用,例如 CommonJS 模块或使用 `require` 进行导入的模块。
> - `import.meta.url` 是 ECMAScript 模块(ESM)中的一个属性,用于**获取当前模块文件的 URL 绝对地址**。它只能在原生支持 ESM 的环境中使用,如现代的浏览器 或者 Node.js 支持的ESM模式。如果觉得上面的方式麻烦,也能引入`fs-extra`库,直接帮我们解决读取json文件的问题
### [fs-extra](https://www.npmjs.com/package/fs-extra)
这个库其实就是对nodejs自带的fs库的增强,并且也自带了fs库的方法。比如上面读取json文件的处理,我们就可以直接通过`fs-extra`增强的方法去进行处理(虽然这个增强方法其实还是使用了`fs/promises`的`readFile`方法,只是帮我进行了封装而已)
```javascript
const pkg = fs.readJsonSync(new URL('./package.json', import.meta.url))
program.version(pkg.version, '-v, --version');
```当然`fs-extra`还有很多好用的方法,我们在后面的代码中再继续使用
### Unicode字符美化
我们可以在一些关键位置,加上一些Unicode字符来进行美化,使得一些提示更加显眼,终端页面也不用显得那么死板。我们常用的`Emoji`表情,其实就是是`Unicode`字符的一种
[Unicode 官方网站](https://home.unicode.org/)
[Unicode 检索PDF](https://unicode.org/charts/PDF/)
**logSymbols.js**
```javascript
// logSymbols.jsimport chalk from "chalk";
const main = {
info: chalk.blue("ℹ"),
success: chalk.green("✔"),
warning: chalk.yellow("⚠"),
error: chalk.red("✖"),
star: chalk.cyan("✵"),
arrow: chalk.yellow("➦")
};export default main
```但是有一些终端可能并不支持Unicode字符,所以我们最好判断一下
**utils.js**
```javascript
// utils.js
export function isUnicodeSupported() {
// 操作系统平台是否为 win32(Windows)
if (process.platform !== "win32") {
// 判断 process.env.TERM 是否为 'linux',
// 这表示在 Linux 控制台(内核)环境中。
return process.env.TERM !== "linux"; // Linux console (kernel)
}return (
Boolean(process.env.CI) || // 是否在持续集成环境中
Boolean(process.env.WT_SESSION) || // Windows 终端环境(Windows Terminal)中的会话标识
Boolean(process.env.TERMINUS_SUBLIME) || // Terminus 插件标识
process.env.ConEmuTask === "{cmd::Cmder}" || // ConEmu 和 cmder 终端中的任务标识
process.env.TERM_PROGRAM === "Terminus-Sublime" ||
process.env.TERM_PROGRAM === "vscode" || // 终端程序的标识,可能是 'Terminus-Sublime' 或 'vscode'
process.env.TERM === "xterm-256color" ||
process.env.TERM === "alacritty" || // 终端类型,可能是 'xterm-256color' 或 'alacritty'
process.env.TERMINAL_EMULATOR === "JetBrains-JediTerm" // 终端仿真器的标识,可能是 'JetBrains-JediTerm'
);
}
```**logSymbols.js**
```javascript
import { isUnicodeSupported } from "./utils.js";
import chalk from "chalk";const main = {
info: chalk.blue("ℹ"),
success: chalk.green("✔"),
warning: chalk.yellow("⚠"),
error: chalk.red("✖"),
star: chalk.cyan("✵"),
arrow: chalk.yellow("➦")
};const fallback = {
info: chalk.blue("i"),
success: chalk.green("√"),
warning: chalk.yellow("‼"),
error: chalk.red("×"),
star: chalk.cyan("*"),
arrow: chalk.yellow("->")
};const logSymbols = isUnicodeSupported() ? main : fallback;
export default logSymbols;
```这样在界面上,我们可以稍微修改一下
```javascript
// index.js
import logSymbols from './logSymbols.js';
import { templates } from './constants.js';
......其他代码省略
program
.command('list')
.description('查看所有可用模板')
.action(() => {
console.log(chalk.yellowBright(logSymbols.star,'模板列表'));
templates.forEach((temp, index) => {
console.log(`(${index + 1}) | ${temp.name} | ${temp.value} | ${temp.desc}`)
})
})
```### 列表美化
直接打印模板列表显得参差不齐,可以直接使用表格进行处理
[table](https://www.npmjs.com/package/table)
```javascript
import logSymbols from './logSymbols.js';
import { templates } from './constants.js';
import { table } from 'table';
......其他代码省略
program
.command('list')
.description('查看所有可用模板')
.action(() => {
// 转换为二维数组
const data = templates.map(item => [chalk.bold.yellowBright(item.name), item.value, item.desc]);
data.unshift([chalk.yellowBright("模板名称"), chalk.yellowBright("模板地址"), chalk.yellowBright("模板描述")]);
const config = {
header: {
alignment: 'center',
content: chalk.yellowBright(logSymbols.star + ' 模板列表'),
},
}
console.log(table(data,config));
})
```接下来,就需要`create ` 这个command命令做点事情了,也就是在函数中要做相关处理
### [shelljs](https://www.npmjs.com/package/shelljs)
ShellJS 是基于 Node.js API 的 Unix shell 命令的可移植**(Windows/Linux/OS X)实现。**简单来说,我们可以在nodejs中执行命令行代码,比如执行command命令的时候,看看终端是否可以运行
**安装**
```javascript
npm i shelljs
```**initAction.js**
```javascript
import shell from "shelljs";
import logSymbols from './logSymbols.js';
const initAction = async (name, option) => {
if (!shell.which("git")) {
console.log(logSymbols.error, "对不起,运行脚本必须先安装git!");
shell.exit(1);
}
// 验证name输入是否合法
if (name.match(/[\u4E00-\u9FFF`~!@#$%&^*[\]()\\;:<.>/?]/g)) {
console.log(logSymbols.error, "项目名称存在非法字符!");
return;
}
}
``````javascript
program
.command('create ')
.description('创建新项目')
.option('-t, --template [template]', '输入模板名称创建项目')
.option('-f, --force', '强制覆盖本地同名项目')
.option('-i, --ignore', '忽略项目相关描述,快速创建项目')
.action(initAction)
```## 命令行交互
### 安装
```javascript
npm i inquirer
```### 询问`confirm`
创建单独的模块处理交互相关代码
**interactive.js**
```javascript
// interactive.jsimport inquirer from 'inquirer'
/**
* @param {string} message 询问提示语句
* @returns {boolean} 返回结果
*/
export const inquirerConfirm = async (message) => {
const answer = await inquirer.prompt({
name: 'confirm',
type: 'confirm',
message
});
return answer
}
``````javascript
// initAction
import chalk from "chalk";
import fs from "fs-extra";
import { inquirerConfirm } from "./interactive.js";......其他代码省略
// 验证name是否存在
if (fs.existsSync(name) && !option.force) {
console.log(logSymbols.error, `已存在项目文件夹${chalk.yellow(name)}`);const answer = await inquirerConfirm(`是否删除${chalk.yellow(name)}文件夹?`)
console.log(answer)
}
```### 删除文件夹
在utils模块中创建删除文件夹的函数
```javascript
// utils.jsimport path from 'path';
import fs from "fs-extra";
import ora from "ora";
import chalk from "chalk";
import logSymbols from './logSymbols.js';const appDirectory = fs.realpathSync(process.cwd());
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);export async function removeDir(dir) {
const spinner = ora({
text: `正在删除文件夹${chalk.cyan(dir)}`,
color: "yellow",
}).start();try {
await fs.remove(resolveApp(dir));
spinner.succeed(chalk.greenBright(`删除文件夹${chalk.cyan(dir)}成功`));
}
catch (err) {
spinner.fail(chalk.redBright(`删除文件夹${chalk.cyan(dir)}失败`));
console.log(err);
return;
}
}
```**完整代码:**
```javascript
// initAction.js// 验证是否存在${name}同名文件夹,如果存在
// 1. 如果没有-f --force选项,提示用户是否删除同名文件夹
// 2. 如果有-f --force选项,直接删除同名文件夹
if (fs.existsSync(name) && !option.force) {
console.log(logSymbols.warning, `已经存在项目文件夹${chalk.yellowBright(name)}`);
//询问是否删除文件夹
const answer = await inquirerConfirm(`是否删除文件夹${chalk.yellowBright(name)}?`);
console.log(answer)
if (answer.confirm) {
//删除
await removeDir(name);
}
else {
console.log(logSymbols.error, chalk.redBright(`对不起,项目创建失败,存在同名文件夹,${chalk.yellowBright(name)}`));
return;
}
}
else if (fs.existsSync(name) && option.force) {
console.log(logSymbols.warning, `已经存在项目文件夹${chalk.yellowBright(name)},强制删除`);
//删除
await removeDir(name);
}
```### 列表`choose`
**interactive.js**
```javascript
// interactive.js
/**
* @param {string} message 询问提示语句
* @param {Array} choices 选择列表
* @param {string} type 列表类型
* @returns {Object} 选择结果
*/
export const inquirerChoose = async (message,choices,type='list') => {
const answer = await inquirer.prompt({
name: 'choose',
type,
message,
choices
});
return answer
}
```### 获取远程git模板
```javascript
let repository = '';
if (option.template) {
const template = templates.find(template => template.name === option.template);
if (!template) {
console.log(logSymbols.error, `不存在模板 ${chalk.yellow(option.template)}`);
console.log(`\r\n运行${logSymbols.arrow} ${chalk.cyan(`dy-cli list`)} 查看所有可用模板\r\n`)
return;
}
repository = template.value;
}
else {
// 选择远程git项目模板
const answer = await inquirerChoose('请选择项目模板:',templates);
// console.log(answer)
repository = answer.choose;
}
```接下来,就是我们之前已经写过的拉取`git repository`了
### gitClone
创建`gitClone.js`文件,下载远程`git repository`
```javascript
// gitClone.jsimport download from 'download-git-repo'
import ora from "ora";
import chalk from "chalk";const gitClone = (remote, name, option=false) => {
const spinner = ora('正在拉取项目…').start();
return new Promise((resolve, reject) => {
download(remote, name, option, err =>{
if (err) {
spinner.fail(chalk.red(err));
reject(err)
return
}
spinner.succeed(chalk.green('拉取成功'))
resolve();
})
})
}export default gitClone
```在`initAction.js`文件中调用
```javascript
// 下载远程git项目模板
try {
await gitClone(repository, name);
} catch (err) {
console.log(logSymbols.error, err);
shell.exit(1); // 下载失败直接退出
return;
}
```### 输入`input`
下载完成之后,我们可以修改下载项目的`package.json`文件,添加一些自定义内容,首先至少要和用户进行输入交互
```javascript
//interactive.js/**
* @param {string} message 询问提示语句
* @returns 输入结果
*/
export const inquirerInput = async (message) => {
const answer = await inquirer.prompt({
name: 'input',
type: 'input',
message
});
return answer
}/**
* @param {Array} messages 询问提示语句数组
* @returns {Object} 结果对象
*/
export const inquirerInputs = async (messages) => {
const answers = await inquirer.prompt(messages.map(msg => {
return {
name: msg.name,
type: 'input',
message: msg.message
}
}));
return answers
}
``````javascript
// 是否忽略项目相关描述
if (!option.ignore) {
// 输入提问
const answers = await inquirerInputs(messages);
console.log(answers);
}
```接下来,当然就需要修改`package.json`文件了
### 修改`package.json`
```javascript
/**
* @param {string} name 文件夹名称
* @param {Object} info 修改信息对象
*/
export async function changePackageJson(name, info) {
try {
const pkg = await fs.readJson(resolveApp(`${name}/package.json`))Object.keys(info).forEach(item => {
if (item === 'name') {
// 如果未输入项目名,则使用默认创建的项目名,也就是文件夹的名字
pkg[item] = info[item] && info[item].trim() ? info[item] : name
}
else if (item === 'keywords' && info[item] && info[item].trim()) {
pkg[item] = info[item].split(',')
}
else if (info[item] && info[item].trim()) {
pkg[item] = info[item]
}
})// console.log(pkg)
await fs.writeJson(resolveApp(`${name}/package.json`), pkg, { spaces: 2 });
} catch (err) {
console.log(logSymbols.error, chalk.red(err));
}
}
```调用:
```diff
// 是否忽略项目相关描述
if (!option.ignore) {
// 输入提问
const answers = await inquirerInputs(messages);
console.log(answers);
+ await changePackageJson(name,answers);
}
```### `node_modules`安装
下载完成之后,我们可以直接通过shell命令进入到下载好的项目中,进行`node_modules`安装
```javascript
// utils.jsexport function npmInstall(dir) {
const spinner = ora('正在安装依赖......').start();if (shell.exec(`cd ${shell.pwd()}/${dir} && npm install --force -d`).code !== 0) {
console.log(logSymbols.error, chalk.yellow('自动安装依赖失败,请手动安装'));
shell.exit(1)
}
spinner.succeed(chalk.green('~~~依赖安装成功~~~'))
spinner.succeed(chalk.green('~~~项目创建完成~~~'))
shell.exit(1)
}
```**调用:**
```diff
// 是否忽略项目相关描述
if (!option.ignore) {
// 输入提问
const answers = await inquirerInputs(messages);
console.log(answers);
await changePackageJson(name,answers);
}+ npmInstall(name);
```## 发布到npm
当然,首先你需要在 **[npmjs](https://www.npmjs.com/)** 官网注册账号
常用命令:
- `npm whoami` 检测当前登录状态
- `npm config ls` 显示当前 npm 配置信息
- `npm addUser` 、`npm login` 登录
- `npm config set registry 链接地址` 切换源地址
- `npm publish` 发布> **注意1:**必须使用npm源镜像才能发布,如果使用的是阿里源等镜像,需要切换成源镜像才能发布 `https://registry.npmjs.org/`
>
> **注意2:**发布名称读取的是**package.json中的name**,并且,npmjs上已经有很多很多内容,**package不能重名**。所以名字尽量不要太简单,不然发布会报403错误