Skip to content

Commit

Permalink
feat: 完成重构升级函数的架构: (#24)
Browse files Browse the repository at this point in the history
  • Loading branch information
uaenaTzx committed Mar 14, 2024
1 parent d3590b7 commit fd78acf
Show file tree
Hide file tree
Showing 9 changed files with 335 additions and 8 deletions.
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"chalk": "^4.0.0",
"commander": "^12.0.0",
"fs-extra": "^11.2.0",
"minimist": "^1.2.8",
"ora": "^5.4.1",
"tar": "^6.2.0"
},
Expand Down
27 changes: 21 additions & 6 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,33 @@

import { Command } from "commander";
import chalk from "chalk";
import minimist from "minimist";

import { getPackageJsonInfo, createApp } from "./utils";
import createAppTest from "./utils/createAppTest";

const program = new Command();

program
.version(chalk.greenBright(getPackageJsonInfo("../../package.json", true).version))
.arguments("<project-name>")
.description("Create a directory for your project files")
.option("-f, --force", "Overwrite target directory if it exists")

program
.command('create <project-name>')
.description('Create a directory for your project files')
.option('-f, --force', 'Overwrite target directory if it exists')
.action((name, options) => {
createApp(name, options);
})
.parse(process.argv);
// createApp(name, options);
createAppTest(name, options);
});

program
.command('add <plugin> [pluginOptions]')
.description('Install a plugin and invoke its generator in an existing project')
.option('--registry <url>', 'Specify an npm registry URL for installing dependencies (npm only)')
.allowUnknownOption()
.action((plugin) => {
const pluginOptions = minimist(process.argv.slice(3));
// addPlugin(plugin, pluginOptions)
});

program.parse(process.argv)
60 changes: 60 additions & 0 deletions packages/core/src/utils/configMap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
interface configMap {
[key: string]: {
files: string[];
npm: string[];
};
}

// todo: 与 select 存在字段耦合,后续考虑抽象出来
// todo: 部分映射需要补充
const template: configMap = {
"common-lib": {
files: [],
npm: [],
},
vue: {
files: [],
npm: ["vue"],
},
react: {
files: [],
npm: ["react", "react-dom", "@types/react", "@types/react-dom"],
},
};

const buildTool: configMap = {
webpack: {
files: [],
npm: ["webpack"],
},
vite: {
files: [],
npm: ["vite"],
},
rollup: {
files: [],
npm: ["rollup"],
},
};
const plugins: configMap = {
Babel: {
files: [],
npm: ["@babel/core", "@babel/preset-env", "babel-loader"],
},
TypeScript: {
files: [],
npm: ["typescript", "@types/node", "ts-loader"],
},
Eslint: {
files: [],
npm: [],
},
Prettier: {
files: [],
npm: [],
},
};

const mapForPreset = { template, buildTool, plugins };

export { mapForPreset };
2 changes: 1 addition & 1 deletion packages/core/src/utils/createApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export default async function createApp(matter: string, options: { force: boolea
// 下载 npm 包解压,获取目标模板导入文件,并删除一些无用的代码文件
getNpmPackage(projectLink.get(projectType) as string, projectType, rootDirectory);

// 注入 lint 规则
// 注入 commitlint 规则
if (commitLint === true) {
createCommitlint(rootDirectory);
}
Expand Down
92 changes: 92 additions & 0 deletions packages/core/src/utils/createAppTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { resolveApp } from "@laconic/utils";
import fs from "fs-extra";
import { execSync, exec } from "child_process";
import { confirm } from "@clack/prompts";
import chalk from "chalk";

import { removeDirectory } from "./fileController";
import { projectSelect } from "./select";
import isGitInstalled from "./checkGitInstallation";
import { createPackageJson } from "./createFile";
import { createFiles } from "./createFiles";
import { type Preset, getFilesForProject, getNpmForPackage } from "./preset";
import createSuccessInfo from "./createSuccessInfo";

// 设置输入模式为原始模式
process.stdin.setRawMode(true);

// 监听键盘输入,避免选择阶段需要多次 Ctrl+C 退出
process.stdin.on("data", (key) => {
// 检测到 Ctrl+C
if (key[0] === 3) {
console.log("⌨️ Ctrl+C pressed - Exiting the program");
process.exit(1);
}
});

// 模板创建主函数
export default async function createAppTest(projectName: string, options) {
const rootDirectory = resolveApp(projectName);

// 创建项目文件夹
if (fs.existsSync(rootDirectory) && !options.force) {
const shouldContinue = await confirm({
message:
"Whether to overwrite a file with the same name that exists in the current directory ?",
});

// 删除已存在文件并创建新文件
if (shouldContinue === true) {
removeDirectory(projectName, true);
} else process.exit(1);

execSync(`mkdir ${rootDirectory}`);
}

// 获取用户选择预设
const preset: Preset = await projectSelect();

// 创建package.json
await createFiles(rootDirectory, {
"package.json": JSON.stringify(createPackageJson(preset.template, projectName)), // 具体字段待重构
});

// 拉取模板
// todo: 新模板未开发,先模拟过程
console.log("Creating a project...");

// 初始化 Git 仓库
if (isGitInstalled()) exec("git init", { cwd: rootDirectory });

// todo: 插件未开发,先模拟过程
// 安装插件至 package.json
preset.plugins.forEach(async (plugin) => {
console.log(plugin, "installed");
// 进入仓库
// await execSync(`npm install ${plugin}`)
});

// 运行生成器创建项目所需文件和结构
console.log(chalk.blue(`🚀 Invoking generators...`));
const fileList = getFilesForProject(preset);
console.log("fileList", fileList);
fileList.forEach(async (file) => {
await createFiles(rootDirectory, {
[file]: "", // todo: 写入的内容还待设计,考虑修改 configMap 的 files 为对象
});
});

// 安装附加依赖
// todo: npm 安装逻辑需要等待设置包管理工具,目前默认 npm,后续优化
// todo: configMap 的 npm 也需要改为对象,传入包依赖模式(-S,-D)
const npmList = getNpmForPackage(preset);
console.log("npmList", npmList);

// 其他剩余操作,如创建 md 文档,或其他首位操作
console.log("📄 Generating README.md...");
await createFiles(rootDirectory, {
"README.md": "",
});

createSuccessInfo(projectName, "npm");
}
19 changes: 19 additions & 0 deletions packages/core/src/utils/createFiles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

import fs from "fs";
import path from "path";

/**
* @description 生成一系列指定的文件
* @param dir 生成目录
* @param files 文件名
* @example await createFiles(dir, {'.tsconfig': tsConfig })
*/

async function createFiles(dir, files) {
Object.keys(files).forEach((name) => {
const filePath = path.join(dir, name)
fs.writeFileSync(filePath, files[name])
})
}

export { createFiles }
73 changes: 73 additions & 0 deletions packages/core/src/utils/preset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { mapForPreset } from "./configMap";

interface Preset {
template: string;
buildTool: string;
plugins: Record<string, any>;
}

/**
* @description 处理用户预设
* @param template 模板
* @param buildTool 构建工具
* @param plugins 插件列表
* @return 用户预设
*/
// todo: 目前没有使用到 defaultPreset,后续考虑加入默认配置
const getPreset = (template: string, buildTool: string, plugins: string[]): Preset => {
const preset: Preset = {
template,
buildTool,
plugins: {},
};

// todo: 插件配置目前设置为空,且没有使用情况,后续优化
plugins.forEach((plugin) => {
preset.plugins[plugin] = {};
});

return preset;
};

const defaultPreset: Preset = {
template: "common-lib",
buildTool: "webpack",
plugins: {
eslint: {},
},
// todo: 更多配置随构建需要添加
};

const { template, buildTool, plugins } = mapForPreset;

/**
* @description 根据配置映射生成预设相关的文件
* @param preset 用户预设
*/
const getFilesForProject = (preset: Preset) => {
let fileList = [...template[preset.template].files, ...buildTool[preset.buildTool].files];

// 独立处理 plugins
Object.keys(preset.plugins).forEach((item) => {
fileList = [...fileList, ...plugins[item].files];
});

return fileList;
};

/**
* @description 根据配置映射生成预设相关的依赖
* @param preset 用户预设
*/
const getNpmForPackage = (preset: Preset) => {
let npmList = [...template[preset.template].npm, ...buildTool[preset.buildTool].npm];

// 独立处理 plugins
Object.keys(preset.plugins).forEach((item) => {
npmList = [...npmList, ...plugins[item].npm];
});

return npmList;
};

export { Preset, getPreset, defaultPreset, getFilesForProject, getNpmForPackage };
64 changes: 64 additions & 0 deletions packages/core/src/utils/select.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { multiselect, select, intro } from "@clack/prompts";
import chalk from "chalk";

import { getPreset } from "./preset";

interface Responses {
template: string;
buildTool: string;
plugins: string[];
}

/**
* @description 终端交互,获取用户的项目预设
* @returns 返回用户的项目预设 Responses
*/
async function projectSelect() {
const responses: Responses = {
template: "",
buildTool: "",
plugins: [],
};

intro(chalk.green(" create-you-app "));

// 选择模板预设
responses.template = (await select({
message: "Pick a template please",
options: [
{ value: "common-lib", label: "common-lib" },
{ value: "vue", label: "vue" },
{ value: "react", label: "react" },
],
})) as string;

// 选择构建工具
responses.buildTool = (await select({
message: "Pick a build tools for your project",
options: [
{ value: "webpack", label: "webpack" },
{ value: "vite", label: "vite" },
{ value: "rollup", label: "rollup" },
],
})) as string;

// 选择插件
responses.plugins = (await multiselect({
message: `Pick plugins for your project.(${chalk.greenBright(
"<space>",
)} select, ${chalk.greenBright("<a>")} toggle all, ${chalk.greenBright(
"<i>",
)} invert selection,${chalk.greenBright("<enter>")} next step)`,
options: [
{ value: "Babel", label: "Babel" },
{ value: "TypeScript", label: "TypeScript" },
{ value: "Eslint", label: "Eslint" },
{ value: "Prettier", label: "Prettier" },
],
required: false,
})) as string[];

return getPreset(responses.template, responses.buildTool, responses.plugins);
}

export { projectSelect };
5 changes: 4 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit fd78acf

Please sign in to comment.