本文代码首要参阅了字节开源库arco-cli的部分代码,代码是字节架构组的同学写的,学习一下大佬的代码。
同时,此文章会作为我项目的README.md 所以会继续更新,欢迎关注
怎么运用交互式的指令行东西下载项目模板
这部分代码完成了一个用户交互的 GitHub 模板下载东西。首先你需求在github上创立一个项目,然后运用下面介绍的代码就能够用指令行拉取到本地,并解压了。
它运用 enquirer
库提示用户输入库房的创立者、称号、分支、和目标目录,然后运用 downloadTemplate
函数下载模板,终究运用 fs-extra
库存储下载的文件。
print
函数封装了日志记载的函数。
代码的具体完成如下:
- 引进依靠:
fs-extra
、enquirer
、downloadTemplate
和print
。(print函数完成下面会有)
import fs from 'fs-extra';
import enquirer from 'enquirer';
import downloadTemplate from './download';
import print from './print';
- 界说接口
IRepo
和IAnswers
,用来描绘库房信息和用户输入的答案。
type IRepo = {
owner: string;
name: string;
branch: string;
};
type IAnswers = IRepo & {
targetDir?: string;
};
- 界说函数
githubDownloadUrl
,用来构造库房的下载 URL。
function githubDownloadUrl(repo: IRepo) {
return 'https://github.com/' + repo.owner + '/' + repo.name + '/archive/refs/heads/' + repo.branch + '.zip';
}
- 界说一个问题数组,包括需求提示用户输入的问题及其验证逻辑。
- questions 数组包括了四个问题目标,每个问题目标都有以下几个特点:
- type:表明问题的类型,例如输入、挑选、确认等。这儿的问题都是输入类型。
- name:表明问题发生的成果值的 key,例如当你在答复问题时输入的值会以 name 作为 key 存储在答案目标中。
- message:表明问题的提示语,例如 “请输入库房的创立者”。
- default:表明问题的默许值,假如用户没有输入答案,则运用默许值。
- validate:表明问题的验证函数,用来验证用户输入的答案是否合法。假如答案不合法,能够回来一个过错音讯,提示用户重新输入。
- 这些问题将用于提示用户输入,并依据用户输入的答案核算下载模板的 URL 和存储文件的目录。
- questions 数组包括了四个问题目标,每个问题目标都有以下几个特点:
const questions = [
{
type: 'input', // type为交互的类型
name: 'owner', // 发生的值的key,比方你输入''
message: '请输入库房的创立者(example: "lio-mengxiang")', // 提示语
default: 'lio-mengxiang',
validate(val) {
if (!val) {
return '请输入文件名'; // 验证一下输入是否不为空
}
if (fs.accessSync(val, fs.constants.F_OK)) {
return '文件已存在'; // 判别文件是否存在
} else {
return true;
}
},
},
{
type: 'input',
name: 'name',
message: '请输入库房称号(example: "react")',
default: 'react-pnpm-monorepo-subTemplate',
validate(val) {
if (!val) {
return '请输入库房名'; // 验证一下输入是否不为空
}
return true;
},
},
{
type: 'input',
name: 'branch',
message: '请输入分支名(example: "main")',
default: 'main',
validate(val) {
if (!val) {
return '请输入分支名'; // 验证一下输入是否不为空
}
return true;
},
},
{
type: 'input',
name: 'targetDir',
message: '请输入放文件的目录(默许当时目录: "./")',
default: './',
},
];
- 运用
enquirer.prompt
办法提示用户输入,并处理用户输入的答案。假如输入有误,则输出过错信息并退出程序。
enquirer
.prompt(questions)
.then((answers: IAnswers) => {
// 获取用户输入值
const owner = answers.owner;
const name = answers.name;
const branch = answers.branch;
const targetDir = answers.targetDir;
downloadTemplate({ url: githubDownloadUrl({ owner, name, branch }), targetDir });
})
.catch((err) => {
print.error(err);
process.exit(1);
});
假如用户输入的答案合法,则运用 downloadTemplate
函数下载模板并运用 fs-extra
存储文件。
import download from 'download';
import compressing from 'compressing';
import print from './print';
/**
* 下载远程项目模板的办法
*/
export default function downloadTemplate({ url, targetDir = './' }: { url: string; targetDir?: string }): Promise<any> {
print.info('download start, please wait...');
// 经过get办法下载
return download(url, targetDir)
.on('end', () => {
print.success('download done');
})
.then((stream) => {
return compressing.zip.uncompress(stream, './');
})
.catch((err) => {
print.error(err);
});
}
分组函数
例如,假设咱们有一个数组 [1, 2, 3, 4, 5, 6],假如咱们调用 group([1, 2, 3, 4, 5, 6], 2),那么这个函数会回来一个新的数组 [[1, 2], [3, 4], [5, 6]]。
export function group(array: any[], subGroupLength: number) {
let index = 0;
const newArray = [];
while (index < array.length) {
newArray.push(array.slice(index, (index += subGroupLength)));
}
return newArray;
}
node快速履行linux指令
这段代码界说了一个 execQuick
函数,它运用 spawn
子进程的方式履行一条指令。spawn
子进程的优点是比 exec
更高效,由于它不需求创立新的 shell 环境,而且不会因超出最大缓冲区的约束而导致过错。
execQuick
函数承受一条指令和一些选项作为参数,并回来一个包括指令履行成果的 Promise 目标。
- 假如用户指定了
time
选项,execQuick
会在履行完指令后打印出指令履行所花费的时刻; - 假如用户指定了
silent
选项,execQuick
会禁止打印出指令的规范输出和规范过错输出。
import { spawn } from 'child_process';
import print from './print';
/**
* spawn优于exec的点
* 1是在于不用新建shell,削减性能开支
* 2是没有maxbuffer的约束
*/
export default async function execQuick(
command: string,
options: {
cwd?: string;
time?: boolean;
silent?: boolean;
} = {}
): Promise<{ pid: number; code: number; stdout: string; stderr: string }> {
return new Promise((resolve) => {
const silent = options.silent !== false;
const begin = new Date().getTime();
const result = {
pid: null,
code: null,
stdout: '',
stderr: '',
};
const { stdout, stderr, pid } = spawn(command, {
cwd: options.cwd,
shell: true,
}).on('close', (code) => {
if (options.time) {
const end = new Date().getTime();
const waste = ((end - begin) / 1000).toFixed(2);
print.info(command, `Command executed in ${waste} ms.`);
}
if (code !== 0 && !silent) {
print.error(command, 'Command executed failed');
}
result.code = code;
resolve(result);
});
result.pid = pid;
stdout.on('data', (data) => {
const dataStr = data.toString();
if (!silent) {
print.info(dataStr);
}
result.stdout += dataStr;
});
stderr.on('data', (data) => {
const dataStr = data.toString();
if (!silent) {
print.error(dataStr);
}
result.stderr += dataStr;
});
});
}
简单的日志记载函数
这段代码界说了一个打印日志的函数,名为 log
。它运用了 chalk
库来设置日志的色彩。
log
函数承受任意数量的参数,并将它们打印到规范输出。它也界说了四个分别对应不同色彩的打印函数,分别是 log.info
、log.warn
、log.error
和 log.success
。
这些函数会把它们的参数以不同的色彩打印出来。例如,log.success('成功')
会把字符串 '成功'
以绿色打印出来。
此外,log
还界说了一个名为 log.divider
的函数,它能够打印一条分隔线,用于区分不同的日志。分隔线的色彩能够经过 level
参数来指定,默许为 'info'
。
/**
* 更改色彩
* example chalk.green('成功') 文字显现绿色
*/
import chalk from 'chalk';
type ILevel = 'info' | 'warn' | 'success' | 'error';
function print(color: string, ...args: string[]) {
if (args.length > 1) {
log(chalk[`bg${color.replace(/^w/, (w) => w.toUpperCase())}`](` ${args[0]} `), chalk[color](args.slice(1)));
} else {
log(chalk[color](...args));
}
}
function log(...args) {
console.log(...args);
}
log.info = print.bind(null, 'gray');
log.warn = print.bind(null, 'yellow');
log.error = print.bind(null, 'red');
log.success = print.bind(null, 'green');
log.chalk = chalk;
/**
* Print divider
* @param {'info' | 'warn' | 'success' | 'error'} level
*/
log.divider = (level: ILevel = 'info') => {
const logger = log[level] || log.info;
logger('---------------------------------------------------------------------------------------');
};
export default log;
判别类型函数
这些函数用于查看 JavaScript 中的目标是否归于特定的类型。例如,函数 isArray() 能够用来查看传入的目标是否为数组类型。isObject() 函数能够用来查看目标是否为目标类型,isString() 函数能够用来查看目标是否为字符串类型,以此类推。
首要基于的是一下函数去做判别
const getType = (obj) => Object.prototype.toString.call(obj).slice(8, -1);
这个函数也是大家从juqery代码里学来的,一向沿用到现在,也是极为推重的判别类型的办法,由于它十分准确。
const getType = (obj) => Object.prototype.toString.call(obj).slice(8, -1);
export function isArray(obj: any): obj is any[] {
return getType(obj) === 'Array';
}
export function isObject(obj: any): obj is { [key: string]: any } {
return getType(obj) === 'Object';
}
export function isString(obj: any): obj is string {
return getType(obj) === 'String';
}
export function isNumber(obj: any): obj is number {
return getType(obj) === 'Number' && obj === obj;
}
export function isRegExp(obj: any) {
return getType(obj) === 'RegExp';
}
export function isFile(obj: any): obj is File {
return getType(obj) === 'File';
}
export function isBlob(obj: any): obj is Blob {
return getType(obj) === 'Blob';
}
export function isUndefined(obj: any): obj is undefined {
return obj === undefined;
}
export function isFunction(obj: any): obj is (...args: any[]) => any {
return typeof obj === 'function';
}
export function isEmptyObject(obj: any): boolean {
return isObject(obj) && Object.keys(obj).length === 0;
}
精简版classnames函数
这段代码完成了一个名为cs
的函数,该函数能够将一组字符串类型的参数兼并成一个字符串,并回来兼并后的字符串。这个函数能够承受多个参数,而且支撑字符串、字符串数组、目标等多种参数类型。在兼并字符串时,会自动去除重复的字符串,并将一切字符串用空格离隔。
例如,对于以下调用:
cs('a', 'b', ['c', 'd'], { e: true, f: false }, null, undefined);
会回来字符串:
'a b c d e'
该函数能够用来代替类似的库(例如classnames
),用于兼并一组字符串并作为一个类名运用。
import { isString, isArray, isObject } from './is';
type ClassNamesArg = string | string[] | { [key: string]: any } | undefined | null | boolean;
/**
* 代替classnames库,样式兼并的办法
*/
export default function cs(...args: ClassNamesArg[]): string {
const length = args.length;
let classNames: string[] = [];
for (let i = 0; i < length; i++) {
const v = args[i];
if (!v) {
continue;
}
if (isString(v)) {
classNames.push(v);
} else if (isArray(v)) {
classNames = classNames.concat(v);
} else if (isObject(v)) {
Object.keys(v).forEach((k) => {
if (v[k]) {
classNames.push(k);
}
});
}
}
return [...new Set(classNames)].join(' ');
}
omit函数
omit函数,它承受两个参数:一个目标和一个数组。函数会回来一个新目标,该目标为传入的目标的浅复制,并删除了数组中列出的一切特点。
例如,假如传入的目标为 { a: 1, b: 2, c: 3 },数组为 [‘a’, ‘c’],则回来的目标为 { b: 2 }。
/**
* delete keys from object
*/
export default function omit<T extends Record<string | number, any>, K extends keyof T>(
obj: T,
keys: Array<K | string> // string 为了某些没有声明的特点被omit
): Omit<T, K> {
const clone = {
...obj,
};
keys.forEach((key) => {
if ((key as K) in clone) {
delete clone[key as K];
}
});
return clone;
}
获取项目文件,以指令输入的目录为根目录
这个函数界说了一个 getProjectPath() 函数。它承受一个目录途径作为参数,并回来这个目录在项目中的绝对途径。假如没有提供目录途径,默许运用当时工作目录作为目录途径。
这个函数能够用来依据相对途径获取文件在项目中的绝对途径。
例如,假如工作目录为 /home/user/project,传入目录途径为 ‘./src’,则回来值为 ‘/home/user/project/src’。
import path from 'path';
/**
* 获取项目文件,以指令输入的目录为根目录
*/
export default function getProjectPath(dir = './'): string {
return path.join(process.cwd(), dir);
}
更改主题的办法
经过更改css变量达到更换主题的目的
import { isObject } from './is';
/**
* 更换css变量的办法
*/
export function setCssVariables(variables: Record<string, any>, root = document.body) {
if (variables && isObject(variables)) {
Object.keys(variables).forEach((themKey) => {
root.style.setProperty(themKey, variables[themKey]);
});
}
}
自动化发布git脚本之检测git库房是否初始化的代码
这个函数界说了一个 checkGitRemote() 函数。它首先会运用 getGitRootPath() 函数检测当时目录是否为 Git 库房。
假如是,它会履行 git remote -v 指令,然后查看指令的输出中是否包括 push。假如包括,则打印空行;
假如不包括,则打印过错信息,并退出程序。假如检测到的当时目录不是 Git 库房,则打印过错信息,并退出程序
import execQuick from './execQuick';
import getGitRootPath from './getGitRootPath';
import print from './print';
export default async function checkGitRemote() {
if (getGitRootPath()) {
const { code, stdout } = await execQuick('git remote -v');
if (code === 0 && stdout.match('(push)')) {
print();
} else {
print.error(['publish'], '在指定 git remote 前,您无法发布代码,请手动添加 git remote。');
process.exit(1);
}
} else {
print.error(['publish'], '没有检测到 Git 库房。');
process.exit(1);
}
}
异步函数组合,是否调用下一个函数,彻底由中间件自己决定
这个函数界说了一个 compose() 函数,它承受一个包括一组中间件目标的数组作为参数。
每个中间件目标都有一个称号和一个函数。
compose() 函数会按照数组中的顺序履行每个中间件函数。每个中间件函数履行完毕后,会更新一个名为 middlewareData 的目标,该目标包括了每个中间件函数处理后的数据。
终究回来的 middlewareData 目标能够用来在多个中间件之间共享数据。
/**
* 异步函数组合,是否调用下一个函数,彻底由中间件自己决定
* @param middleware 中间件
*/
type IMiddleware = {
name: string;
fn: ({ middlewareData, next }: { middlewareData: Record<string, any>; next: () => void }) => Promise<{ data: Record<string, any> }>;
};
export default function compose(middleware: IMiddleware[]) {
let middlewareData: Record<string, any> = {};
async function dispatch(index: number) {
if (index === middleware.length) return;
const { name, fn } = middleware[index];
const { data } = await fn({
middlewareData,
next: () => {
dispatch(++index);
},
});
middlewareData = {
...middlewareData,
[name]: {
...middlewareData[name],
...data,
},
};
}
dispatch(0);
}
指令行东西怎么显现loading动画
咱们封装了在指令行东西中常用ora这个库,ora 是一个 JavaScript 库,用于在指令行中显现 loading 指示器。
它能够用来提示用户在履行异步操作时的进度和成果。例如,能够运用 ora 库在履行某个异步任务时显现一个转圈圈的 loading 指示器,并在任务完成后显现成功或失利信息。
接着看咱们封装的函数,假如函数履行成功,则 loading 指示器会显现成功信息,并将函数的回来值作为 Promise 的成功值;
假如函数履行失利,则 loading 指示器会显现失利信息,并将函数抛出的过错作为 Promise 的失利值。这个函数能够用来提示用户在履行异步操作时的进度和成果。
import ora from 'ora';
import print from './print';
export default function withOra(
promiseFn: () => Promise<any>,
{ text, successText, failText, startText }: { text: string; successText: string; failText: string; startText?: string }
) {
return new Promise((resolve, reject) => {
const spinner = ora(text).start();
startText && print.info(startText);
promiseFn()
.then((result) => {
spinner.succeed(`✅ ${successText}`);
resolve(result);
})
.catch((err) => {
spinner.fail(`❎ ${failText}`);
reject(err);
});
});
}
参阅
arco-cli