前端脚手架,自动创建远程仓库并推送

包含命令行选择和输入配置,远程仓库拉取模板,根据配置将代码注入模板框架的代码中,自动创建远程仓库,初始化git并提交至远程仓库,方便项目开发,简化流程。

目录结构

b29f2b9701c546a9b69285873f24d60e.png

创建一个bin文件夹,添加index.js文件,在这个文件中写下#! /usr/bin/env node

在package.json文件夹下

8172e131316244b0a33e2513d977e14e.png

执行 npm link 命令,链接到本地环境中 npm link (只有本地开发需要执行这一步,正常脚手架全局安装无需执行此步骤)Link 相当于将当前本地模块链接到npm目录下,这个目录可以直接访问,所以当前包就能直接访问了。默认package.json的name为基准,也可以通过bin配置别名。link完后,npm会自动帮忙生成命令,之后可以直接执行cli xxx。

直接上代码bin/index.js

#!/usr/bin/env node

import { Command } from 'commander';
import chalk from 'chalk';
import figlet from 'figlet';
import { promises as fs } from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import Creater from '../lib/create.js';
import { getProjectName } from '../utils/index.js';  // 引入封装好的模块
import '../utils/utils.js'
// 解析 __dirname 和 __filename
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// 异步读取 JSON 文件并解析
const packageJsonPath = path.resolve(__dirname, '../package.json');
const config = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));

const program = new Command();

// 欢迎信息
console.log(chalk.green(figlet.textSync('Ti CLI', {
    horizontalLayout: 'full'
})));

program
    .command('create')
    .description('create a new project')
    .option('-f, --force', 'overwrite target directory if it exists')
    .action(async (options) => {
        // 获取工作目录
        const cwd = process.cwd();

        // 提示用户输入项目名称
        const projectName = await getProjectName(cwd);

        // 目标目录也就是要创建的目录
        const targetDir = path.join(cwd, projectName);
        const creater = new Creater(projectName, targetDir,'git密令');
        try {
            await creater.create();
        } catch (error) {
            console.error(chalk.red(`创建项目失败: ${error.message}`));
        }
    });

program
    .command('build')
    .description('build the project')
    .action(() => {
        console.log('执行 build 命令');
        // 在这里实现 build 命令的具体逻辑
    });

program
    .command('serve')
    .description('serve the project')
    .option('-p, --port <port>', 'specify port to use', '3000')
    .action((options) => {
        console.log(`Serving on port ${options.port}`);
        // 在这里实现 serve 命令的具体逻辑
    });

program.on('--help', () => {
    console.log();
    console.log(`Run ${chalk.cyan('thingjs-ti <command> --help')} to show detail of this command`);
    console.log();
});

program
    .version(`thingjs-ti-cli@${config.version}`)
    .usage('<command> [options]');

program.parse(process.argv);

git密令那块儿填写自己的。

lib/create.js

import inquirer from 'inquirer';
import { exec } from 'child_process';
import { promisify } from 'util';
import { rm, cp } from 'fs/promises'; // 使用 fs/promises 模块
import { injectMainCode } from './injectCode.js';
import { fileURLToPath } from 'url'; // 引入 fileURLToPath
import axios from 'axios';
import path from 'path'; // 引入 path 模块

const execPromise = promisify(exec);
// 使用 import.meta.url 获取当前模块的路径
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

class Creater {
    constructor(projectName, targetDir, gitlabToken) {
        this.name = projectName;
        this.dir = targetDir;
        this.gitlabToken = gitlabToken;
        this.parentGroupId = '你的群组id';
        this.subGroupId = null;
        this.options = null;
        this.isOnline = true;
    }

    async create() {
        try {
            this.isOnline = await this.chooseDownloadMode();
            const template = await this.chooseTemplate();
            if (template === '园区项目基础框架') {
                this.options = await this.chooseOptions();
                if (this.isOnline) {
                    await this.downloadTemplate(template, this.options);
                } else {
                    await this.copyTemplate(template, this.options);
                }
                console.log('项目创建完成');
            } else {
                if (this.isOnline) {
                    await this.downloadTemplate(template);
                } else {
                    await this.copyTemplate(template);
                }
                console.log('项目创建完成');
            }

            if (this.isOnline) {
                // 创建子群组
                await this.createSubGroup(this.options.projectFullName);
                // 在子群组中创建远程仓库
                await this.createRemoteRepository();
            }
        } catch (error) {
            console.error('创建项目失败:', error);
        }
    }

    async createSubGroup(subgroupName) {
        if (!this.gitlabToken) {
            throw new Error('GitLab Token 未设置,请确保已设置环境变量 GITLAB_TOKEN');
        }

        try {
            const response = await axios.post(
                `git地址`,
                {
                    name: subgroupName,
                    path: this.name,
                    parent_id: this.parentGroupId,
                    visibility: 'private' // 可以选择 'private' 或 'public'
                },
                {
                    headers: {
                        'Authorization': `Bearer ${this.gitlabToken}`,
                        'Content-Type': 'application/json'
                    }
                }
            );
            this.subGroupId = response.data.id;
            console.log(`子群组创建成功: ${subgroupName}`);
        } catch (error) {
            console.error('创建子群组失败:', error);
            throw error;
        }
    }

    async createRemoteRepository() {
        if (!this.gitlabToken) {
            throw new Error('GitLab Token 未设置,请确保已设置环境变量 GITLAB_TOKEN');
        }

        if (!this.subGroupId) {
            throw new Error('子群组 ID 未设置,请确保子群组已创建');
        }

        try {
            const response = await axios.post(
                'git地址',
                {
                    name: `${this.name}-web`,
                    namespace_id: this.subGroupId,
                    visibility: 'private' // 可以选择 'private' 或 'public'
                },
                {
                    headers: {
                        'Authorization': `Bearer ${this.gitlabToken}`,
                        'Content-Type': 'application/json'
                    }
                }
            );
            const repoUrl = response.data.ssh_url_to_repo;
            console.log(`远程仓库创建成功: ${repoUrl}`);

            // 初始化本地 Git 仓库并创建初始提交
            await execPromise(`cd ${this.dir} && git init`);
            await execPromise(`cd ${this.dir} && git add .`);
            await execPromise(`cd ${this.dir} && git commit -m "Initial commit"`);

            // 添加远程仓库并推送初始提交
            await execPromise(`cd ${this.dir} && git remote add origin ${repoUrl}`);
            await execPromise(`cd ${this.dir} && git push -u origin main`);

        } catch (error) {
            console.error('创建远程仓库失败:', error);
            throw error;
        }
    }

    async chooseOptions() {
        // 用户输入项目全称
        const { projectFullName } = await inquirer.prompt({
            type: 'input',
            name: 'projectFullName',
            message: '请输入项目全称(git群组名称):',
            validate: (input) => input ? true : '项目全称不能为空'
        });
        return {projectFullName };
    }

    async chooseTemplate() {
        const answers = await inquirer.prompt({
            type: 'list',
            name: 'template',
            message: '请选择项目模板:',
            choices: ['园区标准项目模板', '园区项目基础框架'],
            default: '园区项目基础框架'
        });
        return answers.template;
    }

    async chooseDownloadMode() {
        const answers = await inquirer.prompt({
            type: 'list',
            name: 'downloadMode',
            message: '请选择下载模式:',
            choices: ['在线', '离线'],
            default: '离线'
        });
        return answers.downloadMode === '在线';
    }

    async handleOptions(options) {
        await injectMainCode(this.dir, { needLogin: options.needLogin, width: options.width, height: options.height });
    }

    async downloadTemplate(template, options) {
        const repoUrls = {
            '园区标准项目模板': '模板git地址',
            '园区项目基础框架': '模板git地址'
        };

        const repoUrl = repoUrls[template];
        if (!repoUrl) {
            throw new Error(`未知的模板: ${template}`);
        }

        try {
            console.log(`正在下载模板: ${repoUrl}`);
            await execPromise(`git clone ${repoUrl} ${this.dir}`);

            // 删除 .git 文件夹
            await rm(`${this.dir}/.git`, { recursive: true, force: true });

            if (template === '园区项目基础框架') {
                await this.handleOptions(options);
            }
        } catch (error) {
            console.error('模板下载失败:', error);
            throw error;
        }
    }

    async copyTemplate(template, options) {
        const templates = {
            '园区标准项目模板': path.resolve(__dirname, '../framework/ti-campus-template'),
            '园区项目基础框架': path.resolve(__dirname, '../framework/ti-project-template')
        };

        const templatePath = templates[template];
        if (!templatePath) {
            throw new Error(`未知的模板: ${template}`);
        }

        try {
            console.log(`正在复制模板: ${templatePath}`);
            await cp(templatePath, this.dir, { recursive: true });

            if (template === '园区项目基础框架') {
                await this.handleOptions(options);
            }
        } catch (error) {
            console.error('模板复制失败:', error);
            throw error;
        }
    }
}

export default Creater;

注意替换git地址,请求的git接口可以自己看看gitlab文档,parentGroupId是指群组id,各个公司不一样的,可以根据自己的来。

lib/injectCode.js这个主要是向模板框架中注入配置项代码

import path from 'path';
import fs from 'fs/promises';
import prettier from 'prettier';

async function injectMainCode(targetDir, options) {
    const mainTsPath = path.join(targetDir, 'src', 'main.ts');
    let loginCode ='';
    if(!options.needLogin){
        loginCode =‘你的代码’
    }else{
        loginCode = `你的代码`    
    }
    try {
        // 清空 main.ts 文件内容
        await fs.writeFile(mainTsPath, '', 'utf-8');
        // 读取 main.ts 文件
        let mainTsContent = await fs.readFile(mainTsPath, 'utf-8');

        if (!mainTsContent.includes('initLogin()')) {
            mainTsContent += loginCode;

            // 使用 Prettier 格式化代码
            const formattedContent = await prettier.format(mainTsContent, { parser: 'typescript' });

            await fs.writeFile(mainTsPath, formattedContent, 'utf-8');
            console.log('已向 main.ts 中注入登录功能代码');
        }
    } catch (error) {
        console.error('更新 main.ts 失败:', error);
        throw error;
    }
}

export { injectMainCode };

utils/utils.js这里封装了一个工具函数

// 自定义 findLastIndex 函数
if (!Array.prototype.findLastIndex) {
    Array.prototype.findLastIndex = function (predicate, thisArg) {
        for (let i = this.length - 1; i >= 0; i--) {
            if (predicate.call(thisArg, this[i], i, this)) {
                return i;
            }
        }
        return -1;
    };
}

好了这就结束了,这就是一个很基础很简单的脚手架,可能日常工作需要更复杂更全面的,可以继续在上面叠加,我相信你看完起码觉的这也没什么难的了。

 

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秋名山大前端

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值