Подробное объяснение всего руководства по разработке скаффолдинга

Node.js Командная строка
Подробное объяснение всего руководства по разработке скаффолдинга

В нашем бизнесе мы можем быстро генерировать проекты vue с помощью шаблонов Vue-cli, а также мы можем разработать шаблоны cli для быстрого создания базовой бизнес-модели/архитектуры, которую мы извлекаем ежедневно. В этой статье подробно объясняется, как разрабатывать строительные леса, технические детали и задействованные ямы, а также объясняются различные сторонние пакеты, чтобы даже студенты Xiaobai могли создавать свои собственные cli в соответствии с ним.

Притворись Дафа! Поднимите стиль! ! получить повышение по службе! ! ! Выиграйте и женитесь на Бай Фумэй! ! ! ! Чего же ты ждешь? Начнем быстро~~~

感觉像做梦一样
чувствовать себя как во сне

Ближе к дому, сначала подумайте, что нам помогут сделать наши леса? Например, здесь мы реализуемvta-cli, запустивvta create my-appМы можем инициализировать набор, извлеченный нашим восходом солнца.Vue+Ts+ElementUiБазовая архитектура проекта системы RBAC. Имея эту цель, мы можем разбить шаги для достижения:

  • Поддерживает команды терминалаvta
  • vta create my-appПосле запуска команды проверьте наличие текущего имени файла
  • Перенесите элемент шаблона в Git на локальный
  • Скопируйте загруженный в данный момент ресурс на наш целевой адрес
  • Обновите содержимое файлов, таких как package.json (например, имя, автора, изменения в поле версии и т. д.).
  • Инициализировать Git в проекте для управления проектом
  • Автоматически устанавливать зависимости, необходимые для текущего проекта
  • запустить приложение

👇Давайте пошагово объясним конкретный процесс внедрения, идите в ногу с командой и не отставайте~~

✨✨Инициализировать инфраструктуру проекта

  • Сначала создайте папку, нажмите создать вручную или выполните команду в терминале:
# 终端创建项目根文件夹并进入到根文件夹
mkdir vta-cli && cd vta-cli

# 创建bin和src文件夹
mkdir bin src

# bin下创建init.js作为脚本的入口文件
cd bin && touch init.js

# 并在init.js中键入如下内容:
#!/usr/bin/env node
console.log('Hello,my bin!')

# 初始化npm的包管理文件, 根目录下执行
# 该命令会询问你很多配置参数,如果不想询问直接在后面加-y参数即可
npm init

Папка основного каталога проекта отсутствует. Давайте поговорим о конкретной функции каталога. Папка bin используется для хранения нашего файла ввода команды, init.js используется в качестве файла ввода (назовите его как хотите), а src место, где мы действительно реализуем логику команды скрипта. :

vta-cli项目目录文件夹
папка каталога проекта vta-cli
  • Далее нам нужно настроитьpackage.jsonфайл, и добавляем в него наши скриптовые команды. открыть нашpackage.jsonфайл, добавитьbinПоле:
{
    "bin": {
        "vta": "bin/init.js"
    },
}

Здесь мы определяем vta, команду сценария, которую можно запустить в терминале, то есть запуститьvtaКогда эта команда будет выполнена, программа запустит конфигурацию, которую мы настроили.bin/init.jsэтот файл сценария. Фактически, согласно механизму npm, когдаinstallКогда пакет используется, он автоматически запрашивает определенную для него команду bin и добавляет ее вnode_modules/.binВ файле это может быть выполнено как команда оболочки. Таким образом, когда ваш пакет устанавливается в локальный проект, команды в его корзине доступны для локального запуска, а при глобальной установке он становится глобальной командой для запуска.

Чтобы объяснить, это не обязательно должен быть файл js.На самом деле, в системе Linux все является файлом, и в имени нет суффикса, по крайней мере, чтобы упростить идентификацию «людей».

Важно подчеркнуть: первая строка файла init.js должна быть первой строкой, мы добавили#!/usr/bin/env nodeКод указывает рабочую среду нашего скрипта и добавляет команду узла в качестве префикса, когда мы запускаем команду vta, то есть то, что на самом деле выполняется, этоnode vta.

  • Далее, чтобы облегчить наше тестирование, нам нужно отправить этот пакет в локальную глобальную среду. Мы можем использовать следующую команду:
# 终端运行命令(需在当前项目根目录下)
npm link

Обратите внимание, что ссылка npm — это когда наш текущий пакет связан с локальным глобальным, точно так же, как когда мы устанавливаем зависимости.-gПараметры упакованы в глобальную среду, которая используется для облегчения нашего тестирования во время локальной разработки, что позволяет нам автоматически обновлять во время разработки. если не ясноnpm link的Друзья, вы можете пойти на официальный сайт NPM, чтобы проверитьnpm link的Продолжайте учиться его использовать.

Однако, когда я хочу сказать, многие мелкие партнеры могут наступить на яму в этой области:

  • Во-первых, лучше всего изменить ваш зеркальный источник npm на зеркальный источник самого npm (если вы укажете Taobao и т. д.), особенно когда вам нужно опубликовать репозиторий npm, это не удастся.
  • Во-вторых, должно бытьpackage.jsonв конфигурации把node_modulesДождитесь удаления неактуальных папок (или укажите, что нам нужно), либо можно пройти мимо.gitignoreВы также можете игнорировать файл конфигурации или.npmrcЖдать. Вы можете установить его где угодно, потому что значение конфигурации npm имеет набор последовательных правил.Если вам интересно, вы можете перейти к документации npm, чтобы проверить. Вот какpackage.jsonКонфигурация файла:
{
    "files": [
        "./bin",
        "./src"
    ],
}

Указав директорию папки с файлами в файле package.json, мы сообщаем npm, какие настоящие файлы нам действительно следует включить.Например, нам нужно толькоbin和srcпапка, некоторые файлы по умолчанию, такие какpackage.jsonА, некоторые другие базовые файлы конфигурации, даже если вы их не добавите, будут включены по умолчанию. Это также, когда мы публикуем этот пакет вnpmЧто нужно настроить, так это какие файлы нужно опубликовать в репозиторий npm.

Обратите внимание, что также можно передать исключенные поля,exclude. Однако во многих случаях может быть удобнее указать, какие файлы нам нужны!Опять же, node_modules должны быть исключены, иначе npm-линк будет очень медленным и велика вероятность сбоя, будьте осторожны, чтобы не наступить на яму~~

说的似乎很有道理表情包
Кажется разумным сказать
  • тестовая команда
# 终端运行
vta

# 那么脚本执行后,便会看到终端的输出
# 说明脚本执行成功了

Опять же, в первую строку init.js необходимо добавить песочные палочки следующим образом:

#!/usr/bin/env node
console.log('运行测试')

❤️❤️ Решения для интерфейса командной строки

commander.jsэто полное решение для интерфейса командной строки nodejs. Может помочь нам определить различные команды/параметры командной строки и многое другое. Например, мы хотим определить команду create или -v в качестве параметра для запроса номера версии и т.д. Тогда давайте посмотрим, как его использовать:

  • Установить
cnpm install commander -S
  • Введение в использование
// 在init.js中引入
const { Command } = require('commander');
// 导入当前根目录下的package.json文件,
// 为了获取对应的字段值,比如版本version
const package = require('../package');
// 初始化
const program = new Command();
  • Определите информацию описания команды версии и команды справки
// 
// 如此,
program
  .version(package.version, '-v, --version', 'display version for vta-cli')
  .usage('<command> [options]');
// 

Вызвав метод версии, чтобы определить функцию версии командной строки командной строки, мы можем ввести командную строкуvta -vПолучить информацию о текущей версии.

Вызов метода использования - это определить название текста приглашения нашей вспомогательной команды (справку), которая аналогична ощущению определения заголовка таблицы, как показано на рисунке ниже, когда мы вводим VTA -H, это отображается в определенной части синей коробки:

版本信息演示代码
Демонстрационный код с информацией о версии

Обратите внимание, что здесьversion方法Третий параметр — это содержание описания, которое мы определяем, как показано в красной части рисунка выше.helpПо умолчанию также это значение

  • Определить аргументы командной строки
/**
 * 定义vta的参数
 */ 
program
  .option('-y, --yes', 'run default action')
  .option('-f, --force', 'force all the question');
  
/**
 * 可以通过判断,当用户输入了对应的这些参数时,
 * 我们可以做一些操作:
 */
if (program.force) {
    // do something..
}

С помощью метода option определите параметры нашей командной строки, чем如vta -f, Эквивалентноvta --force. Обратите внимание, что первый параметр определяет параметры командной строки, включая короткое имя (1 символ) и длинное имя, не более. Второй параметр — это содержание описания определения. Уведомление,Для оценивающей части кода можно использовать только длинные имена, а короткие имена нельзя оценивать, например.program.f.

  • создать подкоманду

Создание подкоманд является важной частью, например, мы используемvue create my-appПри создании проекта,createявляется подкомандой команды vue,my-appявляются параметрами команды. Здесь мы также определяем подкоманду:

/**
 * 调用command方法,创建一个create命令,
 * 同时create命令后面必须跟一个命令参数
 * 如果你在终端运行vta create不加名称,则会报错提示用户
 */
program.command('create <name>')
  // 定义该命令的描述
  .description('create a vta template project')
  // 为该命令指定一些参数
  // 最后我们都可以解析到这些参数,然后根据参数实现对应逻辑
  .option('-f, --force', '忽略文件夹检查,如果已存在则直接覆盖')
  /**
   * 最后定义我们的实现逻辑
   * source表示当前定义的name参数
   * destination则是终端的cmd对象,可以从中解析到我们需要的内容
   */
  .action((source, destination) => {
    /**
     * 比如我们这里把实现逻辑放在了另一个文件中去实现,
     * 方便代码解耦,
     * 因为destination参数比较杂乱,其实还是在此处先解析该参数对应再传入使用吧
     * 可以定义一个解析的工具函数
     */
    new CreateCommand(source, destination)
  });

Как показано, см. нижеdestinationЧто такое объект?Или много контента. На что нам нужно обратить внимание, так это на эту часть красного прямоугольника, здесь находится список всех параметров команды, которую мы определили, мы варьируем список, берем значение синей части на рисунке, решаем--Последняя часть затем используется в качестве ключа для соответствия всему объекту cmd, а ее значением является значение параметра, введенного пользователем.

cmd对象展示
отображение объекта cmd

Например, может быть определена вспомогательная функция синтаксического анализа:

/**
 * parseCmdParams
 * @description 解析用户输入的参数
 * @param {} cmd Cammander.action解析出的cmd对象
 * @returns { Object } 返回一个用户参数的键值对象
 */
exports.parseCmdParams = (cmd) => {
  if (!cmd) return {}
  const resOps = {}
  cmd.options.forEach(option => {
    const key = option.long.replace(/^--/, '');
    if (cmd[key] && !isFunction(cmd[key])) {
      resOps[key] = cmd[key]
    }
  })
  return resOps
}

Реализация вышеуказанного метода разбора аналогична реализации нашего vue-cli.

  • полный разбор
/**
 * 切记parse方法的调用,一定要program.parse()方式,
 * 而不是直接在上面的链式调用之后直接xxx.parse()调用,
 * 不然就会作为当前command的parse去处理了,从而help命令等都与你的预期不符合了
 */
try {
  program.parse(process.argv);
} catch (error) {
  console.log('err: ', error)
}

Наконец, его нужно разобрать, иначе вы не сможете получить соответствующие параметры.program.parse(process.argv), то есть соответствующие команды и другие поведения выполняться не будут. Запомнить! Запомнить! Запомнить! ! ! Для получения более подробных команд, пожалуйста, запроситекомандирская документация.

🌞 Проверьте целевой путь

Из приведенных выше шагов мы видим, что мы определилиvta create <name>команда, т.е. когда мы запускаемvta create my-appКогда команда выполняется, она инициализирует определенныйCreateCommandсорт. Давайте посмотрим, как реализовать эту логику: сначала мы создаемsrc/command/CreateCommand.jsЭтот файл для реализации нашей логики:

/**
 * class 项目创建命令
 *
 * @description
 * @param {} source 用户提供的文件夹名称
 * @param {} destination 用户输入的create命令的参数
 */
class Creator {
  constructor(source, destination, ops = {}) {
    this.source = source
    this.cmdParams = parseCmdParams(destination)
    this.RepoMaps = Object.assign({
      repo: RepoPath, // 配置文件中放置的远程地址常量
      temp: path.join(__dirname, '../../__temp__'),
      target: this.genTargetPath(this.source)
    }, ops);
    this.gitUser = {};
    this.spinner = ora();
    this.init();
  }
  
  // 其他实例方法
  // ...
}

// 最终导出这个class
module.exports = Creator;

Давайте посмотрим, для чего мы используем этот конструктор.Во-первых, чтобы присвоить параметры, переданные во время создания экземпляра, объекту this для последующего использования в других методах экземпляра. а затем определитьRepoMapsСвойства задают некоторые из наших основных параметров, например, адрес шаблона проекта.repo, адрес шаблона проекта временно хранится в нашем локальном проекте clitemp, и, наконец, целевой адрес, по которому нам нужно установить проектtaregt. Потому что проект в конечном итоге будет установлен в место по адресу, где работает терминал, а ваш пакет скаффолдинга установлен по другому адресу.

Затем определяется gitUser для хранения git-информации пользователя, и соответствующая информация будет получена путем автоматического выполнения команд позже, а затем, наконец, мы запишем эту информацию в файл package.json.

this.spinner = ora();Это создание экземпляра диаграммы хризантемы.Когда мы выполняем команду, мы можем вызвать метод this.spinner, чтобы повернуть хризантему!

Давайте реализуем этот метод инициализации init:

// 初始化函数
async init() {
    try {
      // 检查目标路径文件是否正确
      await this.checkFolderExist();
      // 拉取git上的vue+ts+ele的项目模板
      // 存放在临时文件夹中
      await this.downloadRepo();
      // 把下载下来的资源文件,拷贝到目标文件夹
      await this.copyRepoFiles();
      // 根据用户git信息等,修改项目模板中package.json的一些信息
      await this.updatePkgFile();
      // 对我们的项目进行git初始化
      await this.initGit();
      // 最后安装依赖、启动项目等!
      await this.runApp();
    } catch (error) {
      console.log('')
      log.error(error);
      exit(1)
    } finally {
      this.spinner.stop();
    }
}

Как видно из приведенных выше комментариев к коду, наш метод init предназначен для одновременного вызова и выполнения ряда операций. Наконец, давайте взглянем на файл конфигурации:

exports.InquirerConfig = {
  // 文件夹已存在的名称的询问参数
  folderExist: [{
    type: 'list',
    name: 'recover',
    message: '当前文件夹已存在,请选择操作:',
    choices: [
      { name: '创建一个新的文件夹', value: 'newFolder' },
      { name: '覆盖', value: 'cover' },
      { name: '退出', value: 'exit' },
    ]
  }],
  // 重命名的询问参数
  rename: [{
    name: 'inputNewName',
    type: 'input',
    message: '请输入新的项目名称: '
  }]
}

// 远程Repo地址
// 大家开发阶段,如果没有自己的项目,可以先调用我的这个地址练习
// 也可以随便一个地址练习都可以
exports.RepoPath = 'github:chinaBerg/vue-typescript-admin'

Позже мы увидим, как эта серия методов может быть реализована?

🌞 Инструмент гирляндной диаграммы для терминала

Прежде всего, давайте представим нашу маленькую хризантему! Когда мы выполняем различные операции, такие как извлечение данных шаблона и т. д., будет определенное ожидание фактического процесса, затем во время этого процесса ожидания у нас может быть небольшая хризантема в терминале, так что Это улучшит работу пользователя и сообщит ему, что текущий скрипт выполняется и загружается, как показано на рисунке (маленькая хризантема слева):

转动的ora菊花图
Вращающаяся диаграмма хризантемы ора

oraЭто инструмент диаграммы хризантемы, используемый терминалом Давайте посмотрим, как его использовать!

  • Установить
cnpm install ora -S
  • использовать
const ora = require('ora');

// ora参数创建spinner文字内容
// 也可以传递一个对象,设置spinner的周期、颜色等
// 调用start方法启动,最终返回一个实例
const spinner = ora('Loading start')

// 开启菊花转转
spinner.start();

// 停止
spinner.stop()

// 设置文案,后者菊花的color
spinner.text = '正在安装项目依赖文件,请稍后...';
spinner.color = 'green';

// 显示转成功的状态
spinner.succeed('package.json更新完成');

Обратите внимание, что цвет копии все еще должен быть поддержан мелом. Мел будет введен позже. Предыдущая картинка демонстрирует фактическое использование:

Более подробную информацию о пользователях см.ора документация.

🌛 Красочная консоль

мел — это инструмент, который позволяет нашей консоли распечатывать различные цвета/фоны, чтобы мы могли четко различать различные подсказки, как показано на следующем рисунке (просто спросите, дерзкий ли вы???):

chalk效果图
меловые изображения
  • Установить
# 终端运行
cnpm i chalk -S
  • использовать
const chalk = require('chalk');

// 比如,这里定义一个log对象
exports.log = {
  warning(msg = '') {
    console.warning(chalk.yellow(`${msg}`));
  },
  error(msg = '') {
    console.error(chalk.red(`${msg}`));
  },
  success(msg = '') {
    console.log(chalk.green(`${msg}`));
  }
}

Например, выше мы инкапсулировали простейший метод журнала, который используется для отображения цветного содержимого при печати различных типов информации. Еще один момент, давайте поговорим о том, как использовать его с ora, упомянутой выше:

const chalk = require('chalk');
const ora = require('ora');

const spinner = ora('Loading start')

// 开启菊花转转
spinner.start(chalk.yellow('打印一个yellow色的文字'));

Использование относительно простое, нечего сказать, больше использования все ещеОбратитесь к документацииБар!

✨fs-дополнительная файловая операция

Прежде чем подробно описать, как реализован каждый шаг, давайте поговорим о библиотеке для работы с файлами, используемой в cli. У самой Node есть операции fs, так зачем нам вводитьfs-extraЧто с библиотекой? Это потому, что он может полностью заменить библиотеку fs, избавляя от необходимостиmkdirp``rimraf``ncpПодождите, пока библиотека будет установлена ​​и представлена. Он используется для файловых операций, таких как копирование, чтение и удаление, и предоставляет больше функций и так далее.

  • Установить
cnpm install fs-extra

Конкретные методы API см. в документации.fs-extra, что будет упомянуто позже при объяснении конкретной реализации каждого шага.

✨ Проверьте, является ли папка законной

пока мы не побежимvta create my-appПришло время, и мы должны рассмотреть это в это время.Если папка с таким именем уже существует в текущем местоположении, то мы должны не перезаписывать ее напрямую, а предоставить пользователю выбор, например перезаписать, создать заново новую папку Exit, как показано ниже:

Затем выполните соответствующую операцию в соответствии с различными вариантами выбора пользователя. Давайте посмотрим на конкретную реализацию этой проверки папки:

checkFolderExist() {
    return new Promise(async (resolve, reject) => {
      const { target } = this.RepoMaps
      // 如果create附加了--force或-f参数,则直接执行覆盖操作
      if (this.cmdParams.force) {
        await fs.removeSync(target)
        return resolve()
      }
      try {
        // 否则进行文件夹检查
        const isTarget = await fs.pathExistsSync(target)
        if (!isTarget) return resolve()

        const { recover } = await inquirer.prompt(InquirerConfig.folderExist);
        if (recover === 'cover') {
          await fs.removeSync(target);
          return resolve();
        } else if (recover === 'newFolder') {
          const { inputNewName } = await inquirer.prompt(InquirerConfig.rename);
          this.source = inputNewName;
          this.RepoMaps.target = this.genTargetPath(`./${inputNewName}`);
          return resolve();
        } else {
          exit(1);
        }
      } catch (error) {
        log.error(`[vta]Error:${error}`)
        exit(1);
      }
    })
  }

Конкретное объяснение:

  1. Мы определяем этот метод, который возвращает объект Promise.
  2. Мы считаем, что пользователь печатаетvta create my-appВы добавили его после-fПараметр, если параметр добавлен, говорит нам игнорировать проверку и вернуться прямо назад, что является операцией переопределения по умолчанию. позвонивfs.removeSync(target);метод удаления файлов, которые необходимо перезаписать;
  3. В противном случае нам нужно реализовать логику реализации проверки папок. пройти черезawait fs.pathExistsSync(target)Логика состоит в том, чтобы определить, существует ли уже имя текущей папки, если нет, разрешение указывает программе выполнить программу после успешной проверки папки.
  4. Если у него такое же имя, он предложит пользователю и позволит пользователю выбрать операцию. Далее объясняется, как взаимодействовать в командной строке.

❤️ Взаимодействие с командной строкой

Говоря о взаимодействии с командной строкой, упоминается библиотека для сравнения программ.inquirer, которая представляет собой библиотеку для взаимодействия с командной строкой в ​​среде узла, поддерживающую такие операции, как одиночный выбор, множественный выбор, пользовательский ввод, запрос подтверждения и т. д.

  • Установить
cnpm i inquirer -S
  • использовать
const inquirer = require('inquirer');

// 定义询问的参数
// type表示询问的类型,是单选、多选、确认等等
// name可以理解为当前交互的标识符,其值为交互的结果
const InquirerConfig = {
  // 文件夹已存在的名称的询问参数
  folderExist: [{
    type: 'list',
    name: 'recover',
    message: '当前文件夹已存在,请选择操作:',
    choices: [
      { name: '覆盖', value: 'cover' },
      { name: '创建一个新的文件夹', value: 'newFolder' },
      { name: '退出', value: 'exit' },
    ]
  }],
  // 重命名的询问参数
  rename: [{
    name: 'inputNewName',
    type: 'input',
    message: '请输入新的项目名称: '
  }]
}

// 使用
// 通过当前标识符获取交互的结果
// 比如,如下是一个单选的演示
const { recover } = await inquirer.prompt(InquirerConfig.folderExist);

// 如果用户选中的是“覆盖”选项
if (recover === 'cover') {
  await fs.removeSync(target);
  return resolve();
// 如果用户选中的是“创建新文件夹”选中
} else if (recover === 'newFolder') {
  // 再次创建一个用户输入的交互操作
  // 让用户输入新的文件夹名称
  const { inputNewName } = await inquirer.prompt(InquirerConfig.rename);
  this.RepoMaps.target = this.genTargetPath(`./${inputNewName}`);
  return resolve();
// 如果用户选的是“退出”选项
} else {
  exit(1);
}
  1. Если пользователь решит перезаписать, мы удалим папку и изменим
  2. Если пользователь решит создать новую папку, то мы даем терминал для ввода и позволяем пользователю ввести имя новой папки. После завершения пользовательского ввода мы обновляем целевой адрес цели.
  3. Если пользователь решит выйти, мы можем вызвать метод process.exit для выхода из программы текущего узла.

❤️ Вытащите git и другой код удаленного склада

После завершения мониторинга папок пришло время загрузить ресурсы нашего проекта на git. Скачать ресурсы, через которые мы прошлиdownload-git-repoреализуется этой библиотекой.

  • Установить
cnpm install download-git-repo -S
  • использовать
const path = require('path');
const downloadRepo = require('download-git-repo');

  // 下载repo资源
  downloadRepo() {
    // 菊花转起来~
    this.spinner.start('正在拉取项目模板...');
    const { repo, temp } = this.RepoMaps
    return new Promise(async (resolve, reject) => {
      // 如果本地临时文件夹存在,则先删除临时文件夹
      await fs.removeSync(temp);
      /**
       * 第一个参数为远程仓库地址,注意是类型:作者/库
       * 第二个参数为下载到的本地地址,
       * 后面还可以继续加一个配置参数对象,最后一个是回调函数,
       */
      download(repo, temp, async err => {
        if (err) return reject(err);
        // 菊花变成对勾
        this.spinner.succeed('模版下载成功');
        return resolve()
      })
    })
  }

Основная логика заключается в том, чтобы загрузить ресурс в наше текущее местоположение временной папки.Если временная папка уже существует, сначала удалите временную папку.

👍Скопируйте ресурсы на целевой адрес и удалите ненужные файлы

Через загрузку ресурсов на git выше мы загрузили его во временный файл в директорию cli, далее нам также нужно переместить ресурсы в указанное нами место и удалить ненужные ресурсы. Итак, здесь мы будем инкапсулировать публичную функцию в utlis для копирования ресурсов:

  • Инкапсуляция функции копирования
/**
 * copyFiles 拷贝下载的repo资源
 * @param { string } tempPath 待拷贝的资源路径(绝对路径)
 * @param { string } targetPath 资源放置路径(绝对路径)
 * @param { Array<string> } excludes 需要排除的资源名称(会自动移除其所有子文件)
 */
exports.copyFiles = async (tempPath, targetPath, excludes = []) => {
  const removeFiles = ['./git', './changelogs']
  // 资源拷贝
  await fs.copySync(tempPath, targetPath)

  // 删除额外的资源文件
  if (excludes && excludes.length) {
    await Promise.all(excludes.map(file => async () =>
      await fs.removeSync(path.resolve(targetPath, file))
    ));
  }
}
  • передача
// 拷贝repo资源
async copyRepoFiles() {
  const { temp, target } = this.RepoMaps
  await copyFiles(temp, target, ['./git', './changelogs']);
}

Здесь мы удаляем сам проект, содержащий./git,./changelogsи другие файлы, потому что это то, что нужно проекту git, а нам это на самом деле не нужно.

👍 Автоматически обновлять файл package.json

С помощью вышеуказанных операций мы скопировали ресурс на наш целевой адрес. Затем мы также хотим автоматически обновить имя, версию, автора и другие поля в package.json до того, что нам нужно, что нам делать?

/**
 * updatePkgFile
 * @description 更新package.json文件
 */
async updatePkgFile() {
  // 菊花转起来!
  this.spinner.start('正在更新package.json...');
  // 获取当前的项目内的package.json文件的据对路径
  const pkgPath = path.resolve(this.RepoMaps.target, 'package.json');
  // 定义需要移除的字段
  // 这些字段本身只是git项目配置的内容,而我们业务项目是不需要的
  const unnecessaryKey = ['keywords', 'license', 'files']
  // 调用方法获取用户的git信息
  const { name = '', email = '' } = await getGitUser();

  // 读取package.json文件内容
  const jsonData = fs.readJsonSync(pkgPath);
  // 移除不需要的字段
  unnecessaryKey.forEach(key => delete jsonData[key]);
  // 合并我们需要的信息
  Object.assign(jsonData, {
    // 以初始化的项目名称作为name
    name: this.source,
    // author字段更新成我们git上的name
    author: name && email ? `${name} ${email}` : '',
    // 设置非私有
    provide: true,
    // 默认设置版本号1.0.0
    version: "1.0.0"
  });
  // 将更新后的package.json数据写入到package.json文件中去
  await fs.writeJsonSync(pkgPath, jsonData, { spaces: '\t' });
  // 停止菊花
  this.spinner.succeed('package.json更新完成!');
}

В этом фрагменте комментарии к коду написаны очень четко, прочитав его, вы должны знать логику процесса! ! ! Что касается логики получения информации о пользователе git, то она будет объяснена позже! ! !

🌟 Получить информацию о Git

Теперь давайте посмотрим, как получить информацию о git, мы определяем публичный метод getGitUser:

/**
 * getGitUser
 * @description 获取git用户信息
 */
exports.getGitUser = () => {
  return new Promise(async (resolve) => {
    const user = {}
    try {
      const [name] = await runCmd('git config user.name')
      const [email] = await runCmd('git config user.email')
      // 移除结尾的换行符
      if (name) user.name = name.replace(/\n/g, '');
      if (email) user.email = `<${email || ''}>`.replace(/\n/g, '')
    } catch (error) {
      log.error('获取用户Git信息失败')
      reject(error)
    } finally {
      resolve(user)
    }
  });
}

Мы все знаем, что если вы хотите просмотреть информацию пользователя git в терминале, вам просто нужно ввестиgit config user.nameВот и все,git config user.emailАдрес электронной почты пользователя может быть получен. Так можем ли мы также выполнить такую ​​команду в скрипте, чтобы получить его?

Так что осталось, как выполнять команды оболочки в терминале?

✨✨скрипт node выполняет указанную команду оболочки

узел выполняет команды сценария, запуская подпроцесс,описание дочернего_процессаЭто метод, предоставляемый узлом для запуска дочернего процесса. Итак, мы можем инкапсулировать метод для выполнения дочернего процесса:

// node的child_process可以开启一个进程执行任务
const childProcess = require('child_process');


/**
 * runCmd
 * @description 运行cmd命令
 * @param { string } 待运行的cmd命令
 */ 
const runCmd = (cmd) => {
  return new Promise((resolve, reject) => {
    childProcess.exec(cmd, (err, ...arg) => {
      if (err) return reject(err)
      return resolve(...arg)
    })
  })
}

Таким образом, описанная выше операция получения сведений о git на самом деле вызывает метод, позволяющий узлу запустить подпроцесс для запуска нашей команды git, а затем вернуть результат.

❤️Инициализировать файлы git

// 初始化git文件
  async initGit() {
    // 菊花转起来
    this.spinner.start('正在初始化Git管理项目...');
    // 调用子进程,运行cd xxx的命令进入到我们目标文件目录
    await runCmd(`cd ${this.RepoMaps.target}`);
    
    // 调用process.chdir方法,把node进程的执行位置变更到目标目录
    // 这步很重要,不然会执行失败(因为执行位置不对)
    process.chdir(this.RepoMaps.target);
    
    // 调用子进程执行git init命令,辅助我们进行git初始化
    await runCmd(`git init`);
    // 菊花停下来
    this.spinner.succeed('Git初始化完成!');
}

Эта часть также называется методом, который мы инкапсулировали для выполнения команды git. Но будь осторожен,process.chdir(this.RepoMaps.target);Измените место выполнения процесса или создайте исключение, если смена каталога не удалась (например, если указанный каталог не существует). Этот шаг очень важен, помните! ! Запомнить! ! ! Подробности можно узнатьописание процесса.chdir

🌟 Установить зависимости

Наконец, нам нужно автоматически затемнить зависимости проекта. Суть в том, чтобы вызвать подпроцесс для выполнения команды npm. Здесь мы напрямую указываем зеркальный источник с помощью Taobao, а друзья также могут расширяться, указывать npm, yarn и другие зеркальные источники по выбору пользователя и наслаждаться! ! !

// 安装依赖
  async runApp() {
    try {
      this.spinner.start('正在安装项目依赖文件,请稍后...');
      await runCmd(`npm install --registry=https://registry.npm.taobao.org`);
      await runCmd(`git add . && git commit -m"init: 初始化项目基本框架"`);
      this.spinner.succeed('依赖安装完成!');

      console.log('请运行如下命令启动项目吧:\n');
      log.success(`   cd ${this.source}`);
      log.success(`   npm run serve`);
    } catch (error) {
      console.log('项目安装失败,请运行如下命令手动安装:\n');
      log.success(`   cd ${this.source}`);
      log.success(`   npm run install`);
    }
  }

Наконец-то 👍👍👍

Исходный адрес git для формирования шаблонов vta-cli, заинтересованные друзья могут проверить реализацию кода. Вы также можете использовать vta-cli для быстрой инициализацииVue+Ts+ElementUi的RBAC后台管理系统的基础架构。 Установитьvta-cliМетоды:

# 安装cli
npm i vta-cli -g

# 初始化项目
vta create my-app

vue-typescript-adminШаблон проекта скоро будет доработан! ! ! Вы также можете вносить свой код вместе~~

Объяснение разработки cli в основном закончено! ! ! Вышеизложенное охватывает общие технические решения по внедрению и внимание к деталям.Проект может быть запущен безболезненно~~ Заинтересованные друзья могут следить за пакетом своего собственного клиентского интерфейса, извлекать общие решения для бизнес-сценариев и улучшать свои собственные разработки.Будьте эффективны! Наконец-то я твой старый друг Ленг Хаммер, добро пожаловать 👏👏 Нравится👍👍 Избранное💗💗 О~~~

Ставьте лайк👍, добавляйте в избранное👋 и делитесь, чтобы не потеряться! ! ! Вы можете взять его и развить, когда вам это нужно.

❤️❤️❤️ Обновить пусто

Этот адрес будет зарезервирован в качестве отображаемого адреса для более отличных библиотек, связанных с разработкой скаффолдинга в будущем, и будет продолжать обновляться в будущем~~

👍👍👍 Рекомендуется другим статьям автора

В этой статье используетсяmdniceнабор текста