Byte front-end architecture group engineering code snippets

The code in this article mainly refers to part of the code of the Byte open source library arco-cli. The code was written by students from the Byte architecture group. Learn the code from the big guys.

How to download project templates using interactive command line tools

This part of the code implements a user-interactive GitHub template download tool. First, you need to create a project on github, and then use the code introduced below to pull it to your local computer using the command line and unzip it.

It uses the enquirer library to prompt the user for the creator, name, branch, and target directory of the repository, then uses the downloadTemplate function to download the template, and finally uses the fs-extra library to store the downloaded file. The print function encapsulates the logging function.

The specific implementation of the code is as follows:

Introduce dependencies: fs-extra, enquirer, downloadTemplateand print. (The print function implementation will be shown below)
import fs from 'fs-extra';
import enquirer from 'enquirer';
import downloadTemplate from './download';
import print from './print';

Define interfaces IRepoand IAnswers, used to describe warehouse information and user-entered answers.
type IRepo = { owner: string; name: string; branch: string; }; type IAnswers = IRepo & { targetDir?: string; };






Define a function githubDownloadUrlto construct the download URL of the warehouse.
function githubDownloadUrl(repo: IRepo) { return 'https://github.com/' + repo.owner + '/' + repo.name + '/archive/refs/heads/' + repo.branch + '.zip' ; }

Defines an array of questions, containing the questions that need to be prompted for user input and their validation logic.
The questions array contains four question objects, and each question object has the following properties: - type: Indicates the type of the question, such as input, selection, confirmation, etc. The issues here are all about input types. - name: the key that represents the result value generated by the question, for example, the value you enter when answering the question will be stored in the answer object with name as the key. - message: A prompt indicating the question, such as "Please enter the creator of the warehouse". - default: Indicates the default value of the question, if the user does not enter an answer, the default value is used. - validate: Indicates the validation function of the question, used to verify whether the answer entered by the user is legal. If the answer is invalid, an error message can be returned, prompting the user to re-enter.
These questions will be used to prompt the user for input, and based on the answers entered by the user, the URL to download the template and the directory to store the file will be calculated.
const questions = [
{ type: 'input', // type is the type of interaction name: 'owner', // the key of the generated value, for example, if you enter '' message: 'Please enter the creator of the warehouse (example: " lio-mengxiang”)', // Prompt default: 'lio-mengxiang', validate(val) { if (!val) { return 'Please enter the file name'; // Verify that the input is not empty } if ( fs.accessSync(val, fs.constants.F_OK)) { return 'the file already exists'; // determine whether the file exists










} else { return true; } }, }, { type: 'input', name: 'name', message: 'Please enter the warehouse name (example: "react")', default: 'react-pnpm-monorepo-subTemplate ', validate(val) { if (!val) { return 'Please enter the warehouse name'; // Verify that the input is not empty } return true; }, }, { type: 'input', name: 'branch' , message: 'Please enter a branch name (example: "main")', default: 'main', validate(val) { if (!val) { return 'Please enter a branch name'; // Verify that the input is not empty } return true; }, }, { type: 'input',






























name: 'targetDir',
message: 'Please enter the directory where the file is placed (default current directory: "./")',
default: './',
},
];
Use enquirer.promptthe method to prompt the user for input and process the answer entered by the user . If the input is incorrect, an error message is output and the program exits.
enquirer
.prompt(questions)
.then((answers: IAnswers) => { // Get user input 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); }); if If the answer entered by the user is legal, use the downloadTemplate function to download the template and use fs-extra to store the file. import download from 'download';













import compressing from ‘compressing’;
import print from ‘./print’;

/**

  • Method to download remote project template
    */
    export default function downloadTemplate({ url, targetDir = './' }: { url: string; targetDir?: string }): Promise { print.info('download start, please wait...' ); // Download via get method return download(url, targetDir) .on('end', () => { print.success('download done'); }) .then((stream) => { return compressing .zip.uncompress(stream, './'); }) .catch((err) => { print.error(err); }); }












Grouping function
For example, assuming we have an array [1, 2, 3, 4, 5, 6], if we call group([1, 2, 3, 4, 5, 6], 2), then this function will return A new array [[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 quickly executes linux commands.
This code defines an execQuick function, which uses spawn subprocesses to execute a command. The advantage of spawning a subprocess is that it is more efficient than exec because it does not require the creation of a new shell environment and does not cause errors due to exceeding the maximum buffer limit.
The execQuick function accepts a command and some options as parameters, and returns a Promise object containing the result of the command execution.
If the user specifies the time option, execQuick will print out the time it took to execute the command after executing the command;
if the user specifies the silent option, execQuick will prohibit printing the standard output and standard error output of the command.
import { spawn } from 'child_process';
import print from './print';

/**

  • The point that spawn is better than exec

  • 1 is that there is no need to create a new shell and reduce performance overhead.

  • 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;
    });
    });
    }

Simple logging function
This code defines a function for printing logs, named log. It uses the chalk library to set the color of the log.
The log function takes any number of arguments and prints them to standard output. It also defines four printing functions corresponding to different colors, namely log.info, log.warn, log.error and log.success.
These functions print their arguments in different colors. For example, log.success('success') will print the string 'success' in green.
In addition, log also defines a function called log.divider, which can print a dividing line to distinguish different logs. The color of the divider can be specified by the level parameter, which defaults to 'info'.

/**

  • change color
  • example chalk.green('success') text displays 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]}), chalkcolor);
} else {
log(chalkcolor);
}
}

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;

Type functions
These functions are used to check whether an object in JavaScript is of a specific type. For example, the function isArray() can be used to check whether the object passed in is of array type. The isObject() function can be used to check whether an object is of type Object, the isString() function can be used to check whether an object is of type String, and so on.
It is mainly based on the following functions to make judgments

const getType = (obj) => Object.prototype.toString.call(obj).slice(8, -1);

This function is also learned from the jquery code, and it has been used until now. It is also a highly respected method of judging the type, because it is very accurate.

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;
}

A simplified version of the classnames function.
This code implements a function named cs, which can combine a set of string type parameters into a string and return the combined string. This function can accept multiple parameters and supports multiple parameter types such as strings, string arrays, and objects. When merging strings, duplicate strings are automatically removed and all strings are separated by spaces.

For example, for the following call:

cs(‘a’, ‘b’, [‘c’, ‘d’], { e: true, f: false }, null, undefined);

will return the string:

‘a b c d e’

This function can be used as an alternative to similar libraries (such as classnames) to combine a set of strings and use them as a class name.

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 function
omit function, it accepts two parameters: an object and an array. The function returns a new object that is a shallow copy of the object passed in, with all properties listed in the array removed.

For example, if the object passed in is { a: 1, b: 2, c: 3 } and the array is ['a', 'c'], the returned object is { 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;
    }

Get the project file, with the directory entered by the command as the root directory.
This function defines a getProjectPath() function. It accepts a directory path as a parameter and returns the absolute path of this directory in the project. If no directory path is provided, the current working directory is used as the directory path by default.

This function can be used to get the absolute path of the file in the project based on the relative path.

For example, if the working directory is /home/user/project and the incoming directory path is './src', the return value is '/home/user/project/src'.

import path from ‘path’;

/**

  • Get the project file, take the directory entered by the command as the root directory
    */
    export default function getProjectPath(dir = './'): string { return path.join(process.cwd(), dir); }

How to change the theme
by changing css variables

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]);
    });
    }
    }

Automatically publish the code of git script to detect whether the git warehouse is initialized.
This function defines a checkGitRemote() function. It first uses the getGitRootPath() function to detect whether the current directory is a Git repository.

If so, it executes the git remote -v command and then checks whether the command's output contains push. If included, print a blank line;

If it is not included, an error message is printed and the program exits. If the detected current directory is not a Git repository, an error message will be printed and the program will exit.

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'], 'You cannot publish the code before specifying git remote, please add git remote manually.'); process.exit(1); } } else { print.error(['publish'], 'No Git repository detected.'); process.exit(1); } }












Asynchronous function composition, whether to call the next function is completely decided by the middleware itself.
This function defines a compose() function, which accepts an array containing a set of middleware objects as a parameter.

Each middleware object has a name and a function.

The compose() function executes each middleware function in the order in the array. After each middleware function is executed, an object named middlewareData will be updated, which contains the data processed by each middleware function.

The final returned middlewareData object can be used to share data between multiple middlewares.

/**

  • Asynchronous function combination, whether to call the next function is completely decided by the middleware itself
  • @param middleware 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);
}

How command line tools display loading animations
We encapsulate the ora library that is commonly used in command line tools. ora is a JavaScript library used to display loading indicators in the command line.
It can be used to prompt users about the progress and results of asynchronous operations. For example, you can use the ora library to display a rotating loading indicator when executing an asynchronous task, and display success or failure information after the task is completed.
Next, look at the function we encapsulated. If the function is executed successfully, the loading indicator will display the success message and the return value of the function will be used as the success value of the Promise. If the function fails to execute, the loading indicator will
display the failure message and the function will be returned. The error thrown is the failure value of the Promise. This function can be used to prompt the user about the progress and results of an asynchronous operation.
import ora from 'ora';
import print from './print';

export default function withOra(
promiseFn: () => Promise,
{ 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);
  });

});
}
Reference

arco-cli:https://github.com/arco-design/arco-cli

Guess you like

Origin blog.csdn.net/longxiaobao123/article/details/132777942