项目: cfft-从模板cli
创建文件在本文中,我解释了为什么我创建了用于根据模板生成文件的CLI,我必须解决的问题以及我学到了什么。
您会发现有关构建CLI的许多有用信息:
- 如何设置应用程序,
- 如何构建彩色记录仪,
- 如何读取命令行参数,
- 如何使用CLI提出问题,
- 如何创建配置文件,
- 如何根据当前终端位置找到配置文件,
- 如何检索软件包版本
- 如何为CLI创建表格,
- 如何将您的软件包发布到NPM。
关于CFFT
那么,什么是CFFT CLI?
这是一个简单但功能强大的节点CLI,可用于从模板中生成文件列表。它是一个文件生成器。无论框架如何,我们都可以使用它。
功能:
•使用终端创建自定义文件结构,
•搜索并替换 - 用自定义文本或文件名替换文件内容,
•创建多个模板,
•将选项直接设置为 cli参数或回答CLI问题或:
•SET 默认通过使用 .config json 文件进行每个模板的文件 - cfft.config.json 。。
有关更多信息,请参见npm软件包。
为什么我建造它?
我是一个完整的堆栈开发人员,专注于前端,目前正在从事基于React的项目。我还使用和/或理解其他框架和语言,例如node.js,nest.js,next.js,angular,.net等。例如,Angular具有一个很好的CLI,可以帮助您创建组件使用交互式命令行接口和其他文件。它为您节省了很多时间。我决定构建类似的东西,以便在我的React项目上使用,但不仅限制了这一点 - 我想创建独立于框架的东西。我构建了它是为了节省重复任务上花费的开发时间,因为我自己复制/粘贴并重复重命名相同的文件结构。
让我们构建应用程序!
注意:这不是整个应用程序。我只是在解释其中的一些有趣的部分。检查GitHub查看代码。
设置应用程序
我决定使用以下语言和软件包:
- node.js这是一个节点CLI应用程序,因此我们需要在计算机上安装一个节点,
- typescript用于类型安全,
- esbuild是一个易于配置的捆绑包,
- jest and ts-jest â for testing,
- arg用于阅读命令行参数,
- chalk用于彩色控制台日志,
- cli-table用于在游戏机中编写表格,
- inquirer提问。
开始开发,创建一个新项目,通过创建 package.json (npm init
命令)初始化新节点项目,并安装CLI所需的软件包。如果您想使用git,请使用git init
命令初始化它。
下一步是创建启动(可执行)文件并捆绑应用程序。
在您的源目录中创建一个新文件:
/src/index.ts
const run = () => {
console.log('Hello World');
};
run();
创建 esbuild.js 在根文件夹中以配置捆绑:
esbuild.js
require("esbuild")
.build({
entryPoints: ["src/index.ts"],
bundle: true,
outdir: "./dist",
platform: "node",
loader: { ".ts": "ts" },
minify: process.env.NODE_ENV !== "development",
external: ["fs", "path", "util"],
})
.catch(() => process.exit(1));
- 入口点进入输入文件的路径(从哪里开始),
- 捆绑包应该捆绑
- OUTDIR
- loader 我们告诉Bundler我们要使用Typescript,
- 缩小通过使其较小的代码优化我们的代码
- 外部 - 从捆绑包中排除什么(我们不需要 fs , path 和 util util 由于这些已经包含在节点中)。
市场上还有许多其他捆绑包,例如 webpack , swc , turbopack 和 parcel ,但是我决定使用 esbuild ,因为很容易配置与Typescript一起使用。
记录器
日志对于任何命令行应用程序都非常重要。这是该应用程序与用户通信的方式。
我倾向于创建充满表情符号的彩色消息,以使控制台更有趣。例如:
为了使我的日志着色,我使用了一个chalk软件包并创建了一个Logger实用程序:
/src/logger.ts
const log = (...message: any[]) => write(...message);
const success = (...message: any[]) => write(chalk.green(...message));
const warning = (...message: any[]) => write(chalk.yellow(...message));
const info = (...message: any[]) => write(chalk.blue(...message));
const error = (...message: any[]) => write(chalk.red(...message));
const debug = (...message: any[]) =>
isDebug() ? write(chalk.whiteBright("🔧 [DEBUG]", ...message)) : undefined;
const write = (...message: any[]): void => {
const shouldWrite = process.env.APP_ENV !== "test";
if (shouldWrite) {
console.log(...message);
}
};
export default { success, warning, info, error, log, debug };
读取命令行参数
阅读CLI参数也是每个CLI的重要组成部分。这是为应用程序提供其他选项的一种方法。例如:
cfft --template css --fileName my-styles
在这里,我们告诉我们的CLI使用 css 模板并创建 myStyles 文件结构。
可以在不安装任何其他软件包的情况下提取命令行参数:
var arguments = process.argv;
console.log(arguments);
,但我决定安装arg软件包。这是一个不错的实用程序,它将命令行参数存储在对象中,允许您指定别名,为您进行验证以及更多。
要指示允许的选项,类型和别名,您需要创建一个对象并将其传递给ARG函数:
import arg from "arg";
export const CLI_ARGS_TYPE = {
"--fileName": String,
"--dirPath": String,
"--template": String,
"--templatePath": String,
"--shouldReplaceFileName": String,
"--fileNameTextToBeReplaced": String,
"--shouldReplaceFileContent": String,
"--textToBeReplaced": String,
"--replaceTextWith": String,
"--searchAndReplaceSeparator": String,
"--debug": String,
"--version": Boolean,
"--help": Boolean,
// Aliases
"-n": "--fileName",
"-t": "--template",
"-v": "--version",
};
console.log(arg(CLI_ARGS_TYPE));
如果运行以下命令:
cfft -n MyFile --template MyTemplate
结果将是:
{ _: [], '--fileName': 'MyFile', '--template': 'MyTemplate' }
注意:有关更多信息,请参见GitHub上的/src/options文件夹。
通过问问题填写丢失的选项
我们的CLI应该对用户友好。如果我们的用户忘记了指定一些选项,我们应该要求他这样做。
我们可以使用inquirer软件包轻松实现这一目标。此软件包允许您提出不同类型的问题,例如输入问题(开放文本问题),确认问题(是/否)等。
。例如,提出一个简单的输入问题可以像这样写:
import inquirer from "inquirer";
(async () => {
const answer = await inquirer.prompt([
{
type: "input",
name: "fileName",
message: "Enter file name:",
},
]);
console.log(answer);
})();
对于某些问题,您也可以设置默认值:
import inquirer from "inquirer";
(async () => {
const answer = await inquirer.prompt([
{
type: "input",
name: "fileName",
message: "Enter file name:",
default: "MyFile",
},
]);
console.log(answer);
})();
注意:有关更多信息,请参见GitHub上的/src/questions.ts和/src/options。
创建一个配置文件
拥有一个配置文件,用户可以在其中设置一些默认值对于CLI应用程序非常有用。您以前可以看到,在撰写本文时,CFFT CLI有10个以上不同的选择。我们的用户可能会忘记填充它们,或者他们每次都想为其模板使用相同的值。为了允许他们指定这些值,我们需要一个配置文件。
我在实施此功能时面临两个问题:
- 如何知道配置在哪里位于项目的根源?
- 如何知道它是否存在?
当我有上述问题时,我通常将其分为多个步骤:
- 如何知道当前终端路径?
- 如何知道配置文件是在父目录中还是在祖父母目录中,等等? ?
- 如何知道何时停止搜索?
cfft从当前终端路径上的模板创建文件。要知道该路径,您可以使用以下node.js函数:
process.cwd()
要检查我们的文件是否存在于当前路径上,我们应该尝试阅读它。如果存在,那是成功的,如果没有,该函数将引发异常,因此,在这种情况下,我们在父文件夹中检查。
清楚地搜索父目录,我们需要递归。
,最后,如果当前路径与以前的路径相同,则意味着我们到达了最后一个父目录,并且我们的文件不存在。
这是结合所解释的所有内容的函数:
export const findConfig = async (
pathArg = ".",
previousPath?: string
): Promise<Config> => {
const searchPath = path.join(process.cwd(), pathArg, CONFIG_FILE_NAME);
const folderPath = resolve(path.join(process.cwd(), pathArg));
const currentPath = resolve(searchPath);
if (currentPath === previousPath) return null as any;
Logger.debug(`Searching for config. Path: ${currentPath}`);
try {
const file = await promisify(fs.readFile)(searchPath);
Logger.debug(`Config file found: ${currentPath}`);
const config = JSON.parse(file.toString()) as Config;
config.folder = folderPath;
config.path = currentPath;
return config;
} catch (error) {
return await findConfig(path.join(pathArg, ".."), currentPath);
}
};
注意:有关配置的更多信息,请参见GitHub上的/src/config文件夹。
检索软件包版本
向我们的用户展示当前的软件包版本可能非常有用。目的是在提供--version
或-v
参数时显示它。这很容易实现:
require("./package.json").version
- -HELP命令
CLI的另一个非常有用且用户友好的命令是--help
命令。此命令应帮助我们的用户使用我们的CLI。这是桌子的理想场所。我安装了cli-table包。
我决定显示三列:命令,别名和描述。
const table = new Table({
head: ["Command", "Alias", "Description"],
style: {
head: new Array(3).fill("cyan"),
},
});
let rows = [
["--fileName", "-n", "File name to be used"],
["--dirPath", "", "Path to the location where to generate files"],
["--template", "-t", "Name of the template to use"],
["--templatePath", "", "Path to the specific template folder"],
["—-shouldReplaceFileName", "", "Should or not CLI replace a file name",],
["--fileNameTextToBeReplaced", "", "Wich part of the file name should be replaced",],
["--shouldReplaceFileContent", "", "Should or not CLI replace a file content",],
["--textToBeReplaced", "", "Text to be replaced separated by a search and replace separator",],
["--replaceTextWith", "", "Text to be used for search and replace separated by a separator",],
["--searchAndReplaceSeparator", "", "Custom separator for search and replace"],
["--version", "-v", "Show the current version of the package"],
["--debug", "", "Show additional logs"],
];
table.push(...rows);
console.log(table.toString());
当我们执行help命令(cfft --help
)时,我们应该看到以下结果(如果终端看起来不相同,则调整终端的大小):
注意:检查/src/help.ts以获取有关GitHub的更多信息。
在本地测试您的代码
有了所有这些功能,我们的CLI几乎已经准备好了。但是在我们的发展过程中,在全球发布之前,我们想对其进行测试。
通过将以下行添加到package.json文件(请参阅package.json):
是可能的
"bin": {
"cfft-dev": "dist/index.js"
}
bin 是您添加全局命令的位置。我们基本上告诉Node,当我们执行cfft-dev
命令时,它应该运行dist/index.js
文件(这是我们在应用程序构建上创建的文件)。
非常重要的步骤是将以下代码行添加到您的可执行文件( index.ts )为第一行:
#!/usr/bin/env node
此行告诉节点这是一个节点可执行文件。
下一步是在本地安装命令。导航终端到您的软件包的位置。json,并执行以下命令:
npm link
瞧!您现在可以在本地测试您的CLI:
cfft-dev --version
发布到NPM
最后,但并非最不重要!我们的CLI完成了,我们希望允许其他人使用它。让我们发布到NPM。
在/dist 文件夹中创建另一个package.json。填写所有必要的字段 - 作者, repository ,add 关键字,版本,描述等。请参阅package.json < /p>
,但是,添加bin命令非常重要
"bin": {
"cfft": "index.js"
}
这样做,我们的CLI准备出版。导航到您的/dist 文件夹,执行npm publish
并为自己感到骄傲!恭喜! ðªð(注意:关注npm上的文档)。
谢谢! ð