Snippet, uma extensão para VSCode e CLI. Parte 2





Bom dia amigos!



Enquanto desenvolvia o Modern HTML Starter Template, pensei em expandir sua usabilidade. Naquela época, as opções para seu uso se limitavam a clonar o repositório e baixar o arquivo. É assim que o trecho de HTML e a extensão para Microsoft Visual Studio Code - HTML Template , bem como a interface de linha de comando - create-modern-template apareceram . É claro que essas ferramentas estão longe de ser perfeitas e irei refiná-las o máximo que puder. No entanto, no processo de criação deles, aprendi algumas coisas interessantes que quero compartilhar com você.



O snippet e a expansão foram abordados na primeira parte . Nesta parte, daremos uma olhada na CLI.



Se você está interessado apenas no código-fonte, aqui está o link para o repositório .



Oclif



Oclif é uma estrutura Heroku para construir interfaces de linha de comando.



Vamos usá-lo para criar um truque que fornece a capacidade de adicionar, atualizar, excluir tarefas e visualizar sua lista.



O código-fonte do projeto está aqui . Também existe um CLI para verificar a funcionalidade do site por URL.



Instale o oclif globalmente:



npm i -g oclif / yarn global add oclif

      
      





Oclif fornece a capacidade de criar CLIs de comando único e multi-comando. Precisamos de uma segunda opção.



Criamos um projeto:



oclif multi todocli

      
      





  • o argumento multi diz ao oclif para criar uma interface multi-comando
  • todocli - nome do projeto






Adicione os comandos necessários:



oclif command add
oclif command update
oclif command remove
oclif command show

      
      





O arquivo src / comandos / hello.js pode ser excluído.



Usaremos lowdb como banco de dados local . Também usaremos giz para personalizar as mensagens exibidas no terminal . Instale essas bibliotecas:



npm i chalk lowdb / yarn add chalk lowdb

      
      





Crie um arquivo db.json vazio no diretório raiz. Este será nosso repositório de tarefas.



No diretório src, crie um arquivo db.js com o seguinte conteúdo:



const low = require('lowdb')
const FileSync = require('lowdb/adapters/FileSync')
const adapter = new FileSync('db.json')
const db = low(adapter)

//   todos        db.json
db.defaults({ todos: [] }).write()

//    
const Todo = db.get('todos')

module.exports = Todo

      
      





Editando o arquivo src / command / add.js:



//   
const { Command } = require('@oclif/command')
const Todo = require('../db')
const chalk = require('chalk')

class AddCommand extends Command {
  async run() {
    //     
    const { argv } = this.parse(AddCommand)
    try {
      //     
      await Todo.push({
        id: Todo.value().length,
        //       ,
        //  
        task: argv.join(' '),
        done: false
      }).write()
      //    
      this.log(chalk.green('New todo created.'))
    } catch {
      //    
      this.log(chalk.red('Operation failed.'))
    }
  }
}

//  
AddCommand.description = `Adds a new todo`

//      
AddCommand.strict = false

//  
module.exports = AddCommand

      
      





Editando o arquivo src / command / update.js:



const { Command } = require('@oclif/command')
const Todo = require('../db')
const chalk = require('chalk')

class UpdateCommand extends Command {
  async run() {
    //   
    const { id } = this.parse(UpdateCommand).args
    try {
      //    id   
      await Todo.find({ id: parseInt(id, 10) })
        .assign({ done: true })
        .write()
      this.log(chalk.green('Todo updated.'))
    } catch {
      this.log('Operation failed.')
    }
  }
}

UpdateCommand.description = `Marks a task as done by id`

//     
UpdateCommand.args = [
  {
    name: 'id',
    description: 'todo id',
    required: true
  }
]

module.exports = UpdateCommand

      
      





O arquivo src / command / remove.js tem a seguinte aparência:



const { Command } = require('@oclif/command')
const Todo = require('../db')
const chalk = require('chalk')

class RemoveCommand extends Command {
  async run() {
    const { id } = this.parse(RemoveCommand).args
    try {
      await Todo.remove({ id: parseInt(id, 10) }).write()
      this.log(chalk.green('Todo removed.'))
    } catch {
      this.log(chalk.red('Operation failed.'))
    }
  }
}

RemoveCommand.description = `Removes a task by id`

RemoveCommand.args = [
  {
    name: 'id',
    description: 'todo id',
    required: true
  }
]

module.exports = RemoveCommand

      
      





Por fim, edite o arquivo src / comandos / show.js:



const { Command } = require('@oclif/command')
const Todo = require('../db')
const chalk = require('chalk')

class ShowCommand extends Command {
  async run() {
    //        id
    const res = await Todo.sortBy('id').value()
    //        
    //    
    if (res.length) {
      res.forEach(({ id, task, done }) => {
        this.log(
          `[${
            done ? chalk.green('DONE') : chalk.red('NOT DONE')
          }] id: ${chalk.yellowBright(id)}, task: ${chalk.yellowBright(task)}`
        )
      })
    //     
    } else {
      this.log('There are no todos.')
    }
  }
}

ShowCommand.description = `Shows existing tasks`

module.exports = ShowCommand

      
      





Estando no diretório raiz do projeto, execute o seguinte comando:



npm link / yarn link

      
      









Em seguida, realizamos várias operações.







Bem. Tudo funciona conforme o esperado. Tudo o que resta é editar package.json e README.md, e você pode publicar o pacote no registro npm.



DIY CLI



Nossa CLI em funcionalidade será semelhante a criar-reagir-app ou vue-cli . No comando create, ele irá criar um projeto no diretório de destino contendo todos os arquivos necessários para o funcionamento da aplicação. Além disso, ele fornecerá a capacidade de inicializar opcionalmente o git e instalar dependências.



O código-fonte do projeto está aqui .



Crie um diretório e inicialize o projeto:



mkdir create-modern-template
cd create-modern-template
npm init -y / yarn init -y

      
      





Instale as bibliotecas necessárias:



yarn add arg chalk clear esm execa figlet inquirer listr ncp pkg-install

      
      





  • arg - uma ferramenta para analisar argumentos de linha de comando
  • limpar - uma ferramenta para limpar o terminal
  • esm é uma ferramenta que fornece suporte ao módulo ES6 em Node.js
  • execa é uma ferramenta para executar automaticamente algumas operações comuns (vamos usá-la para inicializar o git)
  • figlet - uma ferramenta para enviar texto personalizado para o terminal
  • inquiridor - uma ferramenta para trabalhar com a linha de comando, em particular, permite que você faça perguntas ao usuário e analise suas respostas
  • Listra - uma ferramenta para criar uma lista de tarefas e visualizar sua execução no terminal
  • ncp - ferramenta para copiar arquivos e diretórios
  • pkg-install - uma ferramenta para instalar programaticamente as dependências do projeto


No diretório raiz, crie um arquivo bin / create (sem extensão) com o seguinte conteúdo:



#!/usr/bin/env node

require = require('esm')(module)

require('../src/cli').cli(process.argv)

      
      





Editando package.json:



"main": "src/main.js",
"bin": "bin/create"

      
      





O comando de criação é registrado.



Crie um diretório src / template e coloque os arquivos do projeto lá, que serão copiados para o diretório de destino.



Crie um arquivo src / cli.js com o seguinte conteúdo:



//   
import arg from 'arg'
import inquirer from 'inquirer'
import { createProject } from './main'

//    
// --yes  -y    git   
// --git  -g   git
// --install  -i   
const parseArgumentsIntoOptions = (rawArgs) => {
  const args = arg(
    {
      '--yes': Boolean,
      '--git': Boolean,
      '--install': Boolean,
      '-y': '--yes',
      '-g': '--git',
      '-i': '--install'
    },
    {
      argv: rawArgs.slice(2)
    }
  )

  //    
  return {
    template: 'template',
    skipPrompts: args['--yes'] || false,
    git: args['--git'] || false,
    install: args['--install'] || false
  }
}

//   
const promptForMissingOptions = async (options) => {
  //     --yes  -y
  if (options.skipPrompts) {
    return {
      ...options,
      git: false,
      install: false
    }
  }

  // 
  const questions = []

  //      git
  if (!options.git) {
    questions.push({
      type: 'confirm',
      name: 'git',
      message: 'Would you like to initialize git?',
      default: false
    })
  }

  //      
  if (!options.install) {
    questions.push({
      type: 'confirm',
      name: 'install',
      message: 'Would you like to install dependencies?',
      default: false
    })
  }

  //   
  const answers = await inquirer.prompt(questions)

  //    
  return {
    ...options,
    git: options.git || answers.git,
    install: options.install || answers.install
  }
}

//        
export async function cli(args) {
  let options = parseArgumentsIntoOptions(args)

  options = await promptForMissingOptions(options)

  await createProject(options)
}

      
      





O arquivo src / main.js tem a seguinte aparência:



//   
import path from 'path'
import chalk from 'chalk'
import execa from 'execa'
import fs from 'fs'
import Listr from 'listr'
import ncp from 'ncp'
import { projectInstall } from 'pkg-install'
import { promisify } from 'util'
import clear from 'clear'
import figlet from 'figlet'

//        
const access = promisify(fs.access)
const copy = promisify(ncp)

//  
clear()

//     HTML - 
console.log(
  chalk.yellowBright(figlet.textSync('HTML', { horizontalLayout: 'full' }))
)

//   
const copyFiles = async (options) => {
  try {
    // templateDirectory -    ,
    // targetDirectory -  
    await copy(options.templateDirectory, options.targetDirectory)
  } catch {
    //    
    console.error('%s Failed to copy files', chalk.red.bold('ERROR'))
    process.exit(1)
  }
}

//   git
const initGit = async (options) => {
  try {
    await execa('git', ['init'], {
      cwd: options.targetDirectory,
    })
  } catch {
    //    
    console.error('%s Failed to initialize git', chalk.red.bold('ERROR'))
    process.exit(1)
  }
}

//   
export const createProject = async (options) => {
  //     
  options.targetDirectory = process.cwd()

  //     
  const fullPath = path.resolve(__filename)

  //       
  const templateDir = fullPath.replace('main.js', `${options.template}`)

  options.templateDirectory = templateDir

  try {
    //     
    //  R_OK -    
    await access(options.templateDirectory, fs.constants.R_OK)
  } catch {
    //    
    console.error('%s Invalid template name', chalk.red.bold('ERROR'))
    process.exit(1)
  }

  //   
  const tasks = new Listr(
    [
      {
        title: 'Copy project files',
        task: () => copyFiles(options),
      },
      {
        title: 'Initialize git',
        task: () => initGit(options),
        enabled: () => options.git,
      },
      {
        title: 'Install dependencies',
        task: () =>
          projectInstall({
            cwd: options.targetDirectory,
          }),
        enabled: () => options.install,
      },
    ],
    {
      exitOnError: false,
    }
  )

  //  
  await tasks.run()

  //    
  console.log('%s Project ready', chalk.green.bold('DONE'))

  return true
}

      
      





Conectamos a CLI (estando no diretório raiz):



yarn link

      
      





Crie o diretório de destino e o projeto:



mkdir test-dir
cd test-dir
create-modern-template && code .

      
      













Perfeitamente. CLI pronto para publicar.



Publicar um pacote no registro npm



Para poder publicar pacotes, primeiro você precisa criar uma conta no registro npm .



Em seguida, você precisa efetuar login executando o comando npm login e especificando seu e-mail e senha.



Depois disso, editamos o package.json e criamos os arquivos .gitignore, .npmignore, LICENSE e README.md (consulte o repositório do projeto).



Empacotamos os arquivos do projeto usando o comando npm package. Pegamos o arquivo create-modern-template.tgz. Publicamos esse arquivo executando o comando npm publish create-modern-template.tgz.



Receber um erro ao publicar um pacote geralmente significa que um pacote com o mesmo nome já existe no registro npm. Para atualizar um pacote, você precisa alterar a versão do projeto em package.json, criar o arquivo TGZ novamente e enviá-lo para publicação.



Depois que um pacote é publicado, ele pode ser instalado como qualquer outro pacote usando npm i / yarn add.







Como você pode ver, criar a CLI e publicar o pacote no registro npm é simples.



Espero que você tenha encontrado algo interessante para você. Obrigado pela atenção.



All Articles