教你搭建自己的命令式脚手架、拉取自己的git项目模板(新的项目不用再在老的项目做迁移删除等)

363 阅读2分钟

环境 mac OS ventura 13.3.1

好啦有人就问了,有的公司新的项目都是根据业务需求来进行技术选型。至少我不是这样,有的公司技术一般都定型了的比如技术框架react或vue。重复的项目模板,不同的项目逻辑。各种中后台管理系统、h5、小程序、后端等。我们把相应的项目配置都配好,大底的webpack、 vite配置、还有工程化配置。根据自己公司开发流程来。进行一个脚本拓展。create-react-app也是这样。我们为啥不弄自己的呢?
  1. 提前准备一个空文件夹,或创建一个空文件夹
$ mkdir create-my-projectmk && cd create-my-project
  1. 在创建文件夹npm init 生成package.json
$ npm init
  1. 在package.json文件里添加自定义命令,以及支持es写法(很多包最新版现在已经支持es6写法了)
"type": "module",
"bin": {
    "create-tokyo-project": "./bin/index.js"
 }
  1. 下载下面依赖(我用的pnpm这个包可以用npm、yarn都可以)
  2. chalk(给打印的文字着色)、commander(自定义命令参数、获取命令行参数)、download-git-repo(下载远程git项目模板使用方式可以看看文档)、inquirer(提供交互式命令)、log-symbols(提供图标)、ora(提供加载效果)
$  pnpm i chalk commander download-git-repo inquirer log-symbols ora
  1. 在项目创建bin文件,在bin文件创建index.js(由package.json里面的bin自定义命令指向的路径文件)
  2. 在bin/index.js文件添加以下代码就打工告成了(记住一定要在首行添加#!/usr/bin/env node 这样是告诉计算机识别你什么类型环境代码我们就是node),下面看代码很容易理解了。没多少代码!
#!/usr/bin/env node
import logSymbols from 'log-symbols'
import download from 'download-git-repo'
import chalk from 'chalk'
import { Command } from 'commander';
import inquirer from 'inquirer';
import fs from 'fs';
import ora from 'ora';
import path from 'path';
const prompt = inquirer.prompt
const program = new Command();
const spinner = ora()

//github为自己搭建配置的github项目地址 :注意这里的github格式,看文档!看文档!
const choices = [
    {
        name: 'web',
        message: 'web中后台管理项目',
        templates: [{
            name: 'vite-react-web', message: '基于vite搭建的react web项目',
            github: 'github:FiveTokyo/vite-react-web'
        }]
    },
    { name: 'app', message: '手机app', templates: [{ name: 'vite-react-web', message: '基于vite搭建的react web项目', github: '' }] },
    { name: 'ssr', message: 'SSR官网', templates: [{ name: 'vite-react-web', message: '基于vite搭建的react web项目', github: '' }] },
    { name: 'electron', message: 'electron桌面端应用', templates: [{ name: 'vite-react-web', message: '基于vite搭建的react web项目', github: '' }] },
    { name: 'wx', message: 'wx微信小程序', templates: [{ name: 'taro', message: '基于vite搭建的react web项目', github: '' }] },
    { name: 'h5', message: 'h5移动端项目', templates: [{ name: 'vite-react-h5', message: '基于vite搭建的react web项目', github: '' }] },
    { name: 'nestjs', message: 'nest后端项目', templates: [{ name: 'nestjs', message: '基于vite搭建的react web项目', github: '' }] }
]
const args = program.version('0.0.1', '-v', '--version')
    .action(async (opts, { args }) => {
        let projectType = args[0]; //获取终端命令的第一个参数,比如我用自定义命令 create-tokyo-project web 它就会获取参数web
        const findType = choices.find(config => config.name === projectType)
        if (!projectType || !findType) {
            if (!findType && projectType) console.log(chalk.red(logSymbols.warning, `没有找到 ${projectType} 项目类型!请重新选择!`))
            const answer = await prompt([
                {
                    name: 'configs',
                    message: '请选择创建所属类型项目',
                    type: 'rawlist',
                    validate: (value) => {
                        return value.length > 0 ? true : '请选择一个'
                    },
                    default: [choices[0].name],
                    choices: choices.map((config, i) => {
                        return {
                            value: config.name,
                            name: config.message,
                        }
                    }),
                },
            ])
            projectType = answer.configs
        }
        
![截屏2023-04-19 14.25.52.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8dc587029f9b4bb3ba9f32f935fa7757~tplv-k3u1fbpfcp-watermark.image?)
        //找到选择的项目类型
        const targetType = choices.find((choice) => choice.name === projectType)

        const targetTemplateConfig = await prompt([
            {
                name: 'configs',
                message: `请选择 ${chalk.blue(targetType.message)} 项目模板`,
                type: 'rawlist',
                validate: (value) => {
                    return value.length > 0 ? true : '请选择一个'
                },
                default: [choices[0].name],
                choices: targetType.templates.map((config, i) => {
                    return {
                        value: config.name,
                        name: config.message,
                    }
                }),
            },
        ])

        const targetTemplate = targetType.templates.find(config => config.name === targetTemplateConfig.configs)

        const projectName = await prompt([
            {
                name: 'name',
                message: `请输入项目名称`,
                type: 'input',
                validate: (value) => {
                    return value.length > 0 ? true : '请输入合理的项目名称'
                },
                default: targetTemplate.name,
            },
        ])

        if (!targetTemplate.github) {
            return console.log(logSymbols.error, chalk.red('github是无效地址,请联系开发人员补充'))
        }
        try {
            const dir = path.join(process.cwd(), projectName.name);
            if (fs.existsSync(dir)) {
                console.log(chalk.red('已存在该目录文件!请删除该文件或者换个文件名'))
                return
            }
            spinner.start(`正在拉取 ${targetTemplate.github} 模板中...😎`)
            downloadTemplate({ repository: targetTemplate.github, projectName: projectName.name })
        } catch (error) {
            spinner.fail(logSymbols.error, chalk.red(error.message));
        }


    }).parse(process.argv)


/**
* @description: 下载模板
* @param {type} 
* @return: 
*/
const downloadTemplate = function ({ repository, version = '0.0.2', projectName }) {
    // repository模板地址  projectName项目名称 // clone 是否是克隆
    download(repository, projectName, function (err) {
        console.log(err ? '模板加载错误' : '模板加载结束');
        if (err !== 'Error') {
            editFile({ version, projectName });
        }
    })
};

const editFile = function ({ version, projectName }) {
    try {
        // 读取文件
        fs.readFile(`${process.cwd()}/${projectName}/package.json`, (err, data) => {
            if (err) throw err;
            // 获取json数据并修改项目名称和版本号
            let _data = JSON.parse(data.toString())
            _data.name = projectName
            _data.version = version
            let str = JSON.stringify(_data, null, 4);
            // 写入文件
            fs.writeFile(`${process.cwd()}/${projectName}/package.json`, str, function (err) {
                if (err) throw err;
            })
            spinner.succeed();
            console.log(logSymbols.success, chalk.green('拉取github模板成功!😎'))
            console.log(chalk.gray('\n 请开始你的项目吧'))
            console.log(chalk.gray(`\n cd ${projectName} `))
            console.log(chalk.gray(`\n pnpm i \n `))
        });
    } catch (error) {
        console.log(logSymbols.error, chalk.red('\n 修改文件失败', error.message))
    }

};




最后我们测试一下

  1. 在刚刚创建项目目录终端npm link
$ npm link
  1. 再用上面我们自定义的命令在终端试试
  2. create-tokyo-project (web)

截屏2023-04-19 14.26.14.png

我们来选择第一个,只有第一个才有我测试的github模板 截屏2023-04-19 14.27.26.png

截屏2023-04-19 14.28.15.png

默认名字也可以自定义项目名,默认填写会从远程拉模板代码了。这里稍等一会

截屏2023-04-19 14.29.21.png

项目拉取成功啦 截屏2023-04-19 14.37.16.png 2.点击获取项目地址