cpu: あなたと彼らの違いは何ですか?
対話型コマンドライン ツールを使用してプロジェクト テンプレートをダウンロードする方法
コードのこの部分は、ユーザー インタラクティブな GitHub テンプレート ダウンロード ツールを実装します。まず、github でプロジェクトを作成し、次に説明するコードを使用して、コマンド ラインでローカルにプルし、解凍する必要があります。
ライブラリを使用して enquirer
ユーザーにリポジトリの作成者、名前、ブランチ、およびターゲット ディレクトリの入力を求め、 downloadTemplate
関数を使用してテンプレートをダウンロードし、最後に fs-extra
ライブラリを使用してダウンロードしたファイルを保存します。 print
関数はロギング関数をラップします。
コードの具体的な実装は次のとおりです。
依存関係を導入します:
fs-extra
、enquirer
、downloadTemplate
および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';
}
复制代码
ユーザー入力を要求する必要がある質問とその検証ロジックを含む一連の質問を定義します。
type: 入力、選択、確認など、質問の種類を示します。ここでの問題は、すべて入力タイプに関するものです。
name: 質問によって生成された結果値を表すキー。たとえば、質問に回答するときに入力した値は、name をキーとして回答オブジェクトに格納されます。
メッセージ: 「倉庫の作成者を入力してください」などの質問を示すプロンプト。
デフォルト: 質問のデフォルト値を示します。ユーザーが回答を入力しない場合、デフォルト値が使用されます。
validate: ユーザーが入力した回答が正当かどうかを検証するために使用される、質問の検証機能を示します。回答が無効な場合は、エラー メッセージが返され、ユーザーに再入力を求めることができます。
question 配列には 4 つの question オブジェクトが含まれ、各 question オブジェクトには次の属性があります。
これらの質問は、ユーザーに入力を促すために使用され、ユーザーが入力した回答に基づいて、テンプレートをダウンロードする URL とファイルを保存するディレクトリが計算されます。
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) を呼び出すと、この関数はnew [[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;
}
复制代码
ノードは Linux コマンドをすばやく実行します
このコードは、 サブプロセスを使用してコマンドを実行するexecQuick
関数 を定義します。 subprocess の利点は 、新しいシェル環境を作成する必要がなく、最大バッファーの制限を超えてエラーが発生しないため、 よりも効率的であるということです。spawn
spawn
exec
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
この関数は任意の数の引数を取り、それらを標準出力に出力します。また、異なる色に対応する 4 つの印刷関数 ( log.info
、log.warn
、 、log.error
および ) も定義していますlog.success
。
これらの関数は、引数を異なる色で出力します。たとえば、log.success('成功')
文字列は '成功'
緑色で印刷されます。
さらに、log
is defined という関数があり 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() 関数を使用して、オブジェクトが Object 型であるかどうかを確認できます。isString() 関数を使用して、オブジェクトが String 型であるかどうかを確認できます。
主に以下の機能による判断
const getType = (obj) => Object.prototype.toString.call(obj).slice(8, -1);
复制代码
この機能もジュケリーコードから学び、今まで使われてきましたが、型の判別方法としても非常に精度が高いため、非常に重宝されています。
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;
}
复制代码
Lite バージョンのクラス名関数
このコードは、cs
string 型の一連の引数を 1 つの文字列に結合し、結合された文字列を返す関数を実装しています。この関数は複数のパラメーターを受け入れることができ、文字列、文字列配列、オブジェクトなどの複数のパラメーター タイプをサポートします。文字列をマージすると、重複する文字列が自動的に削除され、すべての文字列がスペースで区切られます。
たとえば、次の呼び出しの場合:
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(' ');
}
复制代码
関数を省略
オブジェクトと配列の 2 つのパラメーターを受け入れる 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);
}
复制代码
コマンド ライン ツールがロード アニメーションを表示する方法
コマンド ライン ツールで一般的に使用される ora ライブラリをカプセル化します。ora は、コマンド ラインでロード インジケータを表示するための JavaScript ライブラリです。
非同期操作の進行状況と結果をユーザーに通知するために使用できます。たとえば、 ora ライブラリを使用して、非同期タスクの実行中に回転ロード・インジケータを表示し、タスクの完了後に成功または失敗の情報を表示できます。
関数が正常に実行されると、読み込みインジケーターに成功メッセージが表示され、関数の戻り値が Promise の成功値として使用されます。
関数の実行に失敗した場合、読み込みインジケーターは失敗メッセージを表示し、関数によってスローされたエラーを 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);
});
});
}
复制代码
著者: Meng Xiang_Chengdu
リンク: https://juejin.cn/post/7176935575302668346
ソース: Nuggets